Skip to content

Commit

Permalink
Fixed generating sources.
Browse files Browse the repository at this point in the history
Signed-off-by: AraHaan <[email protected]>
  • Loading branch information
AraHaan committed Apr 2, 2021
1 parent ef7c9e9 commit 50b70b9
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 59 deletions.
2 changes: 1 addition & 1 deletion GenerationFailedException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ protected GenerationFailedException(SerializationInfo info, StreamingContext con
{
}
}
}
}
15 changes: 4 additions & 11 deletions GeneratorOptions.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
namespace GitBuildInfo.SourceGenerator
{
/// <summary>
/// Describes the options that feed into code generation.
/// </summary>
using System.Text.Json.Serialization;

public record GeneratorOptions
{
/// <summary>
/// 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.
/// </summary>
/// <value>The type to use to apply the attribute to embed the git information in that is within the assembly it is being applied to.</value>
[JsonPropertyName("AssemblyType")]
public string? AssemblyType { get; set; }

/// <summary>
/// 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.
/// </summary>
/// <value>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.</value>
[JsonPropertyName("IsGeneric")]
public bool IsGeneric { get; set; }
}
}
6 changes: 2 additions & 4 deletions GitBuildInfo.SourceGenerator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<Version>1.0.3</Version>
<PackageReleaseNotes>Replaced property inits with setters on GeneratorOptions.</PackageReleaseNotes>
<Version>1.0.4</Version>
<PackageReleaseNotes>Fixed generating sources.</PackageReleaseNotes>
<Company>Els_kom org.</Company>
<Authors>Els_kom org.</Authors>
<Copyright>Copyright (c) 2021</Copyright>
Expand Down Expand Up @@ -39,9 +39,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.10.0-1.final" />
<PackageReference Include="Nullable" Version="1.3.0" />
<PackageReference Include="System.Reflection.Metadata" Version="6.0.0-preview.2.21154.6" />
<PackageReference Include="System.Text.Json" Version="6.0.0-preview.2.21154.6" />
<PackageReference Include="System.Memory" Version="4.5.4" PrivateAssets="none" />
</ItemGroup>

<Import Project="$(MSBuildProjectName).targets" />
Expand Down
4 changes: 1 addition & 3 deletions GitBuildInfo.SourceGenerator.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@
<copyright>Copyright (c) 2021</copyright>
<repository type="git" url="https://github.com/Elskom/GitBuildInfo.SourceGenerator.git" />
<dependencies>
<group targetFramework=".NETStandard2.0">
<dependency id="System.Memory" version="4.5.4" include="All" />
</group>
<group targetFramework=".NETStandard2.0" />
</dependencies>
</metadata>
<files>
Expand Down
16 changes: 16 additions & 0 deletions GitInfo.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}
157 changes: 117 additions & 40 deletions SourceGenerator.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Source Generator for dumping git build information into a assembly level attribute on the compilation.
Expand All @@ -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<GeneratorOptions>(
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<GitInfo>(
jsonStr2,
new JsonSerializerOptions
{
AllowTrailingCommas = true,
Expand All @@ -51,6 +63,7 @@ public void Execute(GeneratorExecutionContext context)
}

var splitted = options!.AssemblyType!.Contains(".") ? options!.AssemblyType.Split('.') : new string[] { };
var splitted2 = new Span<string>(splitted, 0, splitted.Length - 1);
var splittedLen = splitted.Length;
var usingStr = new StringBuilder();
var gitinformationNamespace = "Elskom.Generic.Libs";
Expand All @@ -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,
$"// <autogenerated/>{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<AttributeArgumentSyntax>(
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("// <autogenerated/>"), SyntaxFactory.LineFeed } : Array.Empty<SyntaxTrivia>()),
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;
}
}
}
1 change: 1 addition & 0 deletions build/GitBuildInfo.SourceGenerator.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@

<ItemGroup>
<AdditionalFiles Include="GitBuildInfo.json" Condition="Exists('GitBuildInfo.json')" />
<AdditionalFiles Include="GitInfo.json" Condition="Exists('GitInfo.json')" />
</ItemGroup>
</Project>
100 changes: 100 additions & 0 deletions build/GitBuildInfo.SourceGenerator.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<UsingTask TaskName="GitBuildInfo.GitInfoTask"
TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<OutputPath Required="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Using Namespace="System.ComponentModel" />
<Using Namespace="System.Diagnostics" />
<Using Namespace="System.IO" />
<Using Namespace="System.Text" />
<Using Namespace="Microsoft.Build.Framework" />
<Using Namespace="Microsoft.Build.Utilities" />
<Code Type="Class" Language="cs">
<![CDATA[
namespace GitBuildInfo
{
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
/// <summary>
/// A MSBuild task that generates the msbuild information for an assembly.
///
/// Note: use in the BeforeBuild target.
/// </summary>
public class GitInfoTask : Task
{
/// <summary>
/// Gets or sets the generated output file path.
/// </summary>
[Required]
public string OutputPath { get; set; }
/// <inheritdoc/>
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.";
}
}
}
}]]>
</Code>
</Task>
</UsingTask>

<Target Name="BeforeCompile">
<GitInfoTask OutputPath="$(MSBuildProjectDirectory)\GitInfo.json" />
</Target>
</Project>

0 comments on commit 50b70b9

Please sign in to comment.