diff --git a/GenerationFailedException.cs b/GenerationFailedException.cs index 4bbda78..cfd6579 100644 --- a/GenerationFailedException.cs +++ b/GenerationFailedException.cs @@ -25,4 +25,4 @@ protected GenerationFailedException(SerializationInfo info, StreamingContext con { } } -} \ No newline at end of file +} diff --git a/GeneratorOptions.cs b/GeneratorOptions.cs index 40ac406..53bfb9f 100644 --- a/GeneratorOptions.cs +++ b/GeneratorOptions.cs @@ -1,20 +1,13 @@ namespace GitBuildInfo.SourceGenerator { - /// - /// Describes the options that feed into code generation. - /// + using System.Text.Json.Serialization; + public record GeneratorOptions { - /// - /// Gets the type to use to apply the attribute to embed the git information in that is within the assembly it is being applied to. - /// - /// The type to use to apply the attribute to embed the git information in that is within the assembly it is being applied to. + [JsonPropertyName("AssemblyType")] public string? AssemblyType { get; set; } - /// - /// Gets if the type specified in AssemblyType is a generic type, by default this is set to false to indicate that the type is not a generic type. - /// - /// If the type specified in AssemblyType is a generic type, by default this is set to false to indicate that the type is not a generic type. + [JsonPropertyName("IsGeneric")] public bool IsGeneric { get; set; } } } diff --git a/GitBuildInfo.SourceGenerator.csproj b/GitBuildInfo.SourceGenerator.csproj index 33613b0..78beb65 100644 --- a/GitBuildInfo.SourceGenerator.csproj +++ b/GitBuildInfo.SourceGenerator.csproj @@ -5,8 +5,8 @@ netstandard2.0 latest enable - 1.0.3 - Replaced property inits with setters on GeneratorOptions. + 1.0.4 + Fixed generating sources. Els_kom org. Els_kom org. Copyright (c) 2021 @@ -39,9 +39,7 @@ - - diff --git a/GitBuildInfo.SourceGenerator.nuspec b/GitBuildInfo.SourceGenerator.nuspec index bec20e8..da037af 100644 --- a/GitBuildInfo.SourceGenerator.nuspec +++ b/GitBuildInfo.SourceGenerator.nuspec @@ -14,9 +14,7 @@ Copyright (c) 2021 - - - + diff --git a/GitInfo.cs b/GitInfo.cs new file mode 100644 index 0000000..e1decad --- /dev/null +++ b/GitInfo.cs @@ -0,0 +1,16 @@ +namespace GitBuildInfo.SourceGenerator +{ + using System.Text.Json.Serialization; + + public record GitInfo + { + [JsonPropertyName("GitHead")] + public string? GitHead { get; set; } + + [JsonPropertyName("CommitHash")] + public string? CommitHash { get; set; } + + [JsonPropertyName("GitBranch")] + public string? GitBranch { get; set; } + } +} diff --git a/SourceGenerator.cs b/SourceGenerator.cs index e8d3468..ba8de23 100644 --- a/SourceGenerator.cs +++ b/SourceGenerator.cs @@ -1,15 +1,14 @@ namespace GitBuildInfo.SourceGenerator { using System; - using System.ComponentModel; - using System.Diagnostics; - using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Text.Json; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Syntax; + using Microsoft.CodeAnalysis.Text; /// /// Source Generator for dumping git build information into a assembly level attribute on the compilation. @@ -30,15 +29,28 @@ public void Execute(GeneratorExecutionContext context) return; } + _ = context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.projectdir", out var projectdir); var gitBuildInfoJsonFile = context.AdditionalFiles .FirstOrDefault(af => string.Equals(Path.GetFileName(af.Path), "GitBuildInfo.json", StringComparison.OrdinalIgnoreCase)); - if (gitBuildInfoJsonFile is null) + var gitInfoJsonFile = context.AdditionalFiles + .FirstOrDefault(af => string.Equals(Path.GetFileName(af.Path), "GitInfo.json", StringComparison.OrdinalIgnoreCase)); + if (gitBuildInfoJsonFile is null || gitInfoJsonFile is null) { return; } - + + var jsonStr = gitBuildInfoJsonFile.GetText(context.CancellationToken)!.ToString(); var options = JsonSerializer.Deserialize( - gitBuildInfoJsonFile.GetText(context.CancellationToken)!.ToString(), + jsonStr, + new JsonSerializerOptions + { + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }); + var jsonStr2 = gitInfoJsonFile.GetText(context.CancellationToken)!.ToString(); + var gitInfo = JsonSerializer.Deserialize( + jsonStr2, new JsonSerializerOptions { AllowTrailingCommas = true, @@ -51,6 +63,7 @@ public void Execute(GeneratorExecutionContext context) } var splitted = options!.AssemblyType!.Contains(".") ? options!.AssemblyType.Split('.') : new string[] { }; + var splitted2 = new Span(splitted, 0, splitted.Length - 1); var splittedLen = splitted.Length; var usingStr = new StringBuilder(); var gitinformationNamespace = "Elskom.Generic.Libs"; @@ -59,50 +72,114 @@ public void Execute(GeneratorExecutionContext context) // skip the last value. if (value != splitted[splittedLen - 1]) { - _ = usingStr.Append(value != splitted[0] ? "." : value); + _ = usingStr.Append(value != splitted[splittedLen - 2] ? $"{value}." : value); } } - context.AddSource("GitAssemblyInfo.g.cs", - string.Format( - CultureInfo.InvariantCulture, - $"// {0}{1}{0}{0}[assembly: GitInformationAttribute(\"{2}\", \"{3}\", \"{4}\", typeof({5}{6}))]{0}", - Environment.NewLine, - splittedLen > 0 && !string.Equals( - gitinformationNamespace, - usingStr.ToString(), - StringComparison.Ordinal) ? $"using {usingStr};{Environment.NewLine}using {gitinformationNamespace};" : $"using {gitinformationNamespace};", - this.RunGit("describe --all --always --dirty", Directory.GetParent(gitBuildInfoJsonFile.Path).FullName), - this.RunGit("rev-parse --short HEAD", Directory.GetParent(gitBuildInfoJsonFile.Path).FullName), - this.RunGit("name-rev --name-only HEAD", Directory.GetParent(gitBuildInfoJsonFile.Path).FullName), - splittedLen > 0 ? splitted[splittedLen - 1] : options!.AssemblyType, - options!.IsGeneric ? "<>" : string.Empty)); + var generated = GenerateCode( + options, + splitted2.ToArray(), + gitinformationNamespace, + gitInfo!.GitHead!, + gitInfo.CommitHash!, + gitInfo.GitBranch!, + splittedLen > 0 ? splitted[splittedLen - 1] : options!.AssemblyType); + context.AddSource("GitAssemblyInfo.g.cs", SourceText.From(generated.ToFullString(), Encoding.UTF8)); } - private string RunGit(string arguments, string workingDirectory) + private static CompilationUnitSyntax GenerateCode(GeneratorOptions options, string[] usings, string originalnamespace, string arg1, string arg2, string arg3, string typeName) + => SyntaxFactory.CompilationUnit().WithUsings( + SyntaxFactory.List( + string.Equals(string.Join(".", usings), originalnamespace, StringComparison.Ordinal) + ? new UsingDirectiveSyntax[] { + AddUsing(new string[] { "Elskom", "Generic", "Libs" }, true) + } + : new UsingDirectiveSyntax[] { + AddUsing(new string[] { "Elskom", "Generic", "Libs" }, true), + AddUsing(usings, false) + })) + .WithAttributeLists( + SyntaxFactory.SingletonList( + SyntaxFactory.AttributeList( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("GitInformationAttribute")) + .WithArgumentList( + SyntaxFactory.AttributeArgumentList( + SyntaxFactory.SeparatedList( + MakeAttributeArgumentList(options, new string[] { arg1, arg2, arg3 }, typeName)))))) + .WithOpenBracketToken( + SyntaxFactory.Token( + SyntaxFactory.TriviaList(SyntaxFactory.LineFeed), + SyntaxKind.OpenBracketToken, + SyntaxFactory.TriviaList())) + .WithTarget( + SyntaxFactory.AttributeTargetSpecifier(SyntaxFactory.Token(SyntaxKind.AssemblyKeyword)) + .WithColonToken( + SyntaxFactory.Token( + SyntaxFactory.TriviaList(), + SyntaxKind.ColonToken, + SyntaxFactory.TriviaList(SyntaxFactory.Space)))) + .WithCloseBracketToken( + SyntaxFactory.Token( + SyntaxFactory.TriviaList(), + SyntaxKind.CloseBracketToken, + SyntaxFactory.TriviaList(SyntaxFactory.LineFeed))))); + + private static UsingDirectiveSyntax AddUsing(string[] strings, bool autogeneratedheader) { - using var pro1 = new Process(); - pro1.StartInfo.FileName = "git"; - pro1.StartInfo.Arguments = arguments; - pro1.StartInfo.RedirectStandardOutput = true; - pro1.StartInfo.UseShellExecute = false; - pro1.StartInfo.CreateNoWindow = true; - pro1.StartInfo.WorkingDirectory = workingDirectory; - try + NameSyntax? qualifiedName = null; + for (var index = 0; index < strings.Length; index++) { - _ = pro1.Start(); - var git_out = pro1.StandardOutput.ReadToEnd(); - pro1.WaitForExit(); - - // handle all cases of possible endlines. - git_out = git_out.Replace("\r\n", string.Empty); - git_out = git_out.Replace("\n", string.Empty); - return git_out.Replace("\r", string.Empty); + if (index == 0 && strings.Length > 1) + { + qualifiedName = SyntaxFactory.QualifiedName( + SyntaxFactory.IdentifierName(strings[index]), + SyntaxFactory.IdentifierName(strings[index + 1])); + index++; + } + else + { + qualifiedName = strings.Length == 1 + ? SyntaxFactory.IdentifierName(strings[index]) + : SyntaxFactory.QualifiedName(qualifiedName!, SyntaxFactory.IdentifierName(strings[index])); + } } - catch (Win32Exception) + + return SyntaxFactory.UsingDirective(qualifiedName!) + .WithUsingKeyword(SyntaxFactory.Token( + SyntaxFactory.TriviaList(autogeneratedheader ? new[] { SyntaxFactory.Comment("// "), SyntaxFactory.LineFeed } : Array.Empty()), + SyntaxKind.UsingKeyword, + SyntaxFactory.TriviaList(SyntaxFactory.Space))) + .WithSemicolonToken(SyntaxFactory.Token( + SyntaxFactory.TriviaList(), + SyntaxKind.SemicolonToken, + SyntaxFactory.TriviaList(SyntaxFactory.LineFeed))); + } + + private static SyntaxNodeOrToken[] MakeAttributeArgumentList(GeneratorOptions options, string[] args, string typeName) + { + var lst = new SyntaxNodeOrToken[7]; + var lstIndex = 0; + foreach (var arg in args) { - return "Not a git clone or git is not in Path."; + lst[lstIndex] = SyntaxFactory.AttributeArgument( + SyntaxFactory.LiteralExpression( + SyntaxKind.StringLiteralExpression, + SyntaxFactory.Literal(arg))); + lstIndex++; + lst[lstIndex] = SyntaxFactory.Token( + SyntaxFactory.TriviaList(), + SyntaxKind.CommaToken, + SyntaxFactory.TriviaList(SyntaxFactory.Space)); + lstIndex++; } + + lst[lstIndex] = SyntaxFactory.AttributeArgument( + SyntaxFactory.TypeOfExpression( + options.IsGeneric + ? SyntaxFactory.GenericName(typeName) + : SyntaxFactory.IdentifierName(typeName))); + return lst; } } } diff --git a/build/GitBuildInfo.SourceGenerator.props b/build/GitBuildInfo.SourceGenerator.props index 4c560c0..e46acb9 100644 --- a/build/GitBuildInfo.SourceGenerator.props +++ b/build/GitBuildInfo.SourceGenerator.props @@ -2,5 +2,6 @@ + diff --git a/build/GitBuildInfo.SourceGenerator.targets b/build/GitBuildInfo.SourceGenerator.targets new file mode 100644 index 0000000..b729887 --- /dev/null +++ b/build/GitBuildInfo.SourceGenerator.targets @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + /// A MSBuild task that generates the msbuild information for an assembly. + /// + /// Note: use in the BeforeBuild target. + /// + public class GitInfoTask : Task + { + /// + /// Gets or sets the generated output file path. + /// + [Required] + public string OutputPath { get; set; } + + /// + public override bool Execute() + { + this.RunGit("describe --all --always --dirty", out var git_out1); + this.RunGit("rev-parse --short HEAD", out var git_out2); + this.RunGit("name-rev --name-only HEAD", out var git_out3); + var outputData = $"{{{Environment.NewLine} \"GitHead\": \"{git_out1}\",{Environment.NewLine} \"CommitHash\": \"{git_out2}\",{Environment.NewLine} \"GitBranch\": \"{git_out3}\"{Environment.NewLine}}}"; + // patch 112019: only print the getting build info from git message from the initial call to this task. + // all other calls will not print anything to avoid spamming up the build output. + try + { + if (!File.Exists(this.OutputPath) || (File.Exists(this.OutputPath) && !string.Equals(outputData, File.ReadAllText(this.OutputPath), StringComparison.Ordinal))) + { + this.Log.LogMessage(MessageImportance.High, "Getting build info from git"); + File.WriteAllText(this.OutputPath, outputData); + } + } + catch (IOException) + { + // catch I/O error from being unable to open the file for checking it's contents. + } + return true; + } + + private void RunGit(string arguments, out string git_out) + { + using var pro1 = new Process(); + pro1.StartInfo.FileName = "git"; + pro1.StartInfo.Arguments = arguments; + pro1.StartInfo.RedirectStandardOutput = true; + pro1.StartInfo.UseShellExecute = false; + pro1.StartInfo.CreateNoWindow = true; + pro1.StartInfo.WorkingDirectory = Path.GetFullPath(this.OutputPath).Replace(Path.GetFileName(this.OutputPath), string.Empty); + try + { + _ = pro1.Start(); + git_out = pro1.StandardOutput.ReadToEnd(); + pro1.WaitForExit(); + // handle all cases of possible endlines. + git_out = git_out.Replace("\r\n", string.Empty); + git_out = git_out.Replace("\n", string.Empty); + git_out = git_out.Replace("\r", string.Empty); + } + catch (Win32Exception) + { + git_out = "Not a git clone or git is not in Path."; + } + } + } +}]]> + + + + + + + +