From e3cd90f63e600d271d5c7b4b7c879ccb1ebf483a Mon Sep 17 00:00:00 2001 From: Tom Glastonbury <1101693+tg73@users.noreply.github.com> Date: Wed, 10 Mar 2021 14:55:16 +0000 Subject: [PATCH 1/2] Allow custom fields to be added to the generated ThisAssembly class. --- .../AssemblyInfoTest.cs | 65 +++- .../AssemblyVersionInfo.cs | 320 ++++++++++++------ .../build/Nerdbank.GitVersioning.targets | 1 + 3 files changed, 275 insertions(+), 111 deletions(-) diff --git a/src/NerdBank.GitVersioning.Tests/AssemblyInfoTest.cs b/src/NerdBank.GitVersioning.Tests/AssemblyInfoTest.cs index 4137da6b..f8fc5fb2 100644 --- a/src/NerdBank.GitVersioning.Tests/AssemblyInfoTest.cs +++ b/src/NerdBank.GitVersioning.Tests/AssemblyInfoTest.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.Build.Utilities; using Nerdbank.GitVersioning.Tasks; using Xunit; @@ -18,6 +19,25 @@ public void FSharpGenerator(bool? thisAssemblyClass) info.AssemblyCompany = "company"; info.AssemblyFileVersion = "1.3.1.0"; info.AssemblyVersion = "1.3.0.0"; + info.AdditionalThisAssemblyFields = + new TaskItem[] + { + new TaskItem( + "CustomString1", + new Dictionary() { { "String", "abc" } } ), + new TaskItem( + "CustomString2", + new Dictionary() { { "String", "" } } ), + new TaskItem( + "CustomString3", + new Dictionary() { { "String", "" }, { "EmitIfEmpty", "true" } } ), + new TaskItem( + "CustomBool", + new Dictionary() { { "Boolean", "true" } } ), + new TaskItem( + "CustomTicks", + new Dictionary() { { "Ticks", "637509805729817056" } } ), + }; info.CodeLanguage = "f#"; if (thisAssemblyClass.HasValue) @@ -49,11 +69,15 @@ namespace AssemblyInfo [] #endif type internal ThisAssembly() = - static member internal AssemblyVersion = ""1.3.0.0"" - static member internal AssemblyFileVersion = ""1.3.1.0"" static member internal AssemblyCompany = ""company"" - static member internal IsPublicRelease = false + static member internal AssemblyFileVersion = ""1.3.1.0"" + static member internal AssemblyVersion = ""1.3.0.0"" + static member internal CustomBool = true + static member internal CustomString1 = ""abc"" + static member internal CustomString3 = """" + static member internal CustomTicks = new System.DateTime(637509805729817056L, System.DateTimeKind.Utc) static member internal IsPrerelease = false + static member internal IsPublicRelease = false static member internal RootNamespace = """" do() " : "")}"; @@ -72,6 +96,25 @@ public void CSharpGenerator(bool? thisAssemblyClass) info.AssemblyFileVersion = "1.3.1.0"; info.AssemblyVersion = "1.3.0.0"; info.CodeLanguage = "c#"; + info.AdditionalThisAssemblyFields = + new TaskItem[] + { + new TaskItem( + "CustomString1", + new Dictionary() { { "String", "abc" } } ), + new TaskItem( + "CustomString2", + new Dictionary() { { "String", "" } } ), + new TaskItem( + "CustomString3", + new Dictionary() { { "String", "" }, { "EmitIfEmpty", "true" } } ), + new TaskItem( + "CustomBool", + new Dictionary() { { "Boolean", "true" } } ), + new TaskItem( + "CustomTicks", + new Dictionary() { { "Ticks", "637509805729817056" } } ), + }; if (thisAssemblyClass.HasValue) { @@ -100,11 +143,15 @@ public void CSharpGenerator(bool? thisAssemblyClass) [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] #endif internal static partial class ThisAssembly {{ - internal const string AssemblyVersion = ""1.3.0.0""; - internal const string AssemblyFileVersion = ""1.3.1.0""; internal const string AssemblyCompany = ""company""; - internal const bool IsPublicRelease = false; + internal const string AssemblyFileVersion = ""1.3.1.0""; + internal const string AssemblyVersion = ""1.3.0.0""; + internal const bool CustomBool = true; + internal const string CustomString1 = ""abc""; + internal const string CustomString3 = """"; + internal static readonly System.DateTime CustomTicks = new System.DateTime(637509805729817056L, System.DateTimeKind.Utc); internal const bool IsPrerelease = false; + internal const bool IsPublicRelease = false; internal const string RootNamespace = """"; }} " : "")}"; @@ -154,11 +201,11 @@ public void VisualBasicGenerator(bool? thisAssemblyClass) #Else Partial Friend NotInheritable Class ThisAssembly #End If - Friend Const AssemblyVersion As String = ""1.3.0.0"" - Friend Const AssemblyFileVersion As String = ""1.3.1.0"" Friend Const AssemblyCompany As String = ""company"" - Friend Const IsPublicRelease As Boolean = False + Friend Const AssemblyFileVersion As String = ""1.3.1.0"" + Friend Const AssemblyVersion As String = ""1.3.0.0"" Friend Const IsPrerelease As Boolean = False + Friend Const IsPublicRelease As Boolean = False Friend Const RootNamespace As String = """" End Class " : "")}"; diff --git a/src/Nerdbank.GitVersioning.Tasks/AssemblyVersionInfo.cs b/src/Nerdbank.GitVersioning.Tasks/AssemblyVersionInfo.cs index 8ad58eba..8d869850 100644 --- a/src/Nerdbank.GitVersioning.Tasks/AssemblyVersionInfo.cs +++ b/src/Nerdbank.GitVersioning.Tasks/AssemblyVersionInfo.cs @@ -88,6 +88,25 @@ public class AssemblyVersionInfo : Task public bool EmitThisAssemblyClass { get; set; } = true; + /// + /// Specify additional fields to be added to the ThisAssembly class. + /// + /// + /// Field name is given by %(Identity). Provide the field value by specifying exactly one metadata value that is %(String), %(Boolean) or %(Ticks) (for UTC DateTime). + /// If specifying %(String), you can also specify %(EmitIfEmpty) to determine if a class member is added even if the value is an empty string (default is false). + /// + /// + /// + /// + /// + /// + /// + /// + /// ]]> + /// + public ITaskItem[] AdditionalThisAssemblyFields { get; set; } + #if NET461 public override bool Execute() { @@ -154,46 +173,31 @@ private CodeTypeDeclaration CreateThisAssemblyClass() // CodeDOM doesn't support static classes, so hide the constructor instead. thisAssembly.Members.Add(new CodeConstructor { Attributes = MemberAttributes.Private }); - // Determine information about the public key used in the assembly name. - string publicKey, publicKeyToken; - bool hasKeyInfo = this.TryReadKeyInfo(out publicKey, out publicKeyToken); + var fields = this.GetFieldsForThisAssembly(); - // Define the constants. - thisAssembly.Members.AddRange(CreateFields(new Dictionary - { - { "AssemblyVersion", this.AssemblyVersion }, - { "AssemblyFileVersion", this.AssemblyFileVersion }, - { "AssemblyInformationalVersion", this.AssemblyInformationalVersion }, - { "AssemblyName", this.AssemblyName }, - { "AssemblyTitle", this.AssemblyTitle }, - { "AssemblyProduct", this.AssemblyProduct }, - { "AssemblyCopyright", this.AssemblyCopyright }, - { "AssemblyCompany", this.AssemblyCompany }, - { "AssemblyConfiguration", this.AssemblyConfiguration }, - { "GitCommitId", this.GitCommitId }, - }).ToArray()); - thisAssembly.Members.AddRange(CreateFields(new Dictionary + foreach (var pair in fields) + { + switch (pair.Value.Value) { - { "IsPublicRelease", this.PublicRelease }, - { "IsPrerelease", !string.IsNullOrEmpty(this.PrereleaseVersion) }, - }).ToArray()); + case string stringValue: + if (pair.Value.EmitIfEmpty || !string.IsNullOrEmpty(stringValue)) + { + thisAssembly.Members.Add(CreateField(pair.Key, stringValue)); + } + break; - if (long.TryParse(this.GitCommitDateTicks, out long gitCommitDateTicks)) - { - thisAssembly.Members.AddRange(CreateCommitDateProperty(gitCommitDateTicks).ToArray()); - } + case bool boolValue: + thisAssembly.Members.Add(CreateField(pair.Key, boolValue)); + break; - if (hasKeyInfo) - { - thisAssembly.Members.AddRange(CreateFields(new Dictionary - { - { "PublicKey", publicKey }, - { "PublicKeyToken", publicKeyToken }, - }).ToArray()); - } + case long ticksValue: + thisAssembly.Members.AddRange(CreateField(pair.Key, ticksValue).ToArray()); + break; - // These properties should be defined even if they are empty. - thisAssembly.Members.Add(CreateField("RootNamespace", this.RootNamespace)); + default: + throw new NotImplementedException(); + } + } return thisAssembly; } @@ -227,25 +231,6 @@ private IEnumerable CreateAssemblyAttributes() } } - private static IEnumerable CreateFields(IReadOnlyDictionary namesAndValues) - { - foreach (var item in namesAndValues) - { - if (!string.IsNullOrEmpty(item.Value)) - { - yield return CreateField(item.Key, item.Value); - } - } - } - - private static IEnumerable CreateFields(IReadOnlyDictionary namesAndValues) - { - foreach (var item in namesAndValues) - { - yield return CreateField(item.Key, item.Value); - } - } - private static CodeMemberField CreateField(string name, T value) { return new CodeMemberField(typeof(T), name) @@ -255,10 +240,32 @@ private static CodeMemberField CreateField(string name, T value) }; } - private static IEnumerable CreateCommitDateProperty(long ticks) + private static IEnumerable CreateField(string name, long ticks) { + if ( string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentNullException(nameof(name)); + } + // internal static System.DateTime GitCommitDate {{ get; }} = new System.DateTime({ticks}, System.DateTimeKind.Utc);"); - yield return new CodeMemberField(typeof(DateTime), "gitCommitDate") + + // For backing field name, try to use name with first char converted to lower case, or otherwise suffix with underscore. + string fieldName = null; + var char0 = name[0]; + + if ( char.IsUpper( char0) ) + { + fieldName = + name.Length == 1 + ? new string(char.ToLowerInvariant(char0), 1) + : new string(char.ToLowerInvariant(char0), 1) + name.Substring(1); + } + else + { + fieldName = name + "_"; + } + + yield return new CodeMemberField(typeof(DateTime), fieldName) { Attributes = MemberAttributes.Private, InitExpression = new CodeObjectCreateExpression( @@ -273,7 +280,7 @@ private static IEnumerable CreateCommitDateProperty(long ticks) { Attributes = MemberAttributes.Assembly, Type = new CodeTypeReference(typeof(DateTime)), - Name = "GitCommitDate", + Name = name, HasGet = true, HasSet = false, }; @@ -282,7 +289,7 @@ private static IEnumerable CreateCommitDateProperty(long ticks) new CodeMethodReturnStatement( new CodeFieldReferenceExpression( null, - "gitCommitDate"))); + fieldName))); yield return property; } @@ -370,60 +377,169 @@ private void GenerateAssemblyAttributes() } } - private void GenerateThisAssemblyClass() + private List> GetFieldsForThisAssembly() { - this.generator.StartThisAssemblyClass(); - // Determine information about the public key used in the assembly name. string publicKey, publicKeyToken; bool hasKeyInfo = this.TryReadKeyInfo(out publicKey, out publicKeyToken); // Define the constants. - var fields = new Dictionary - { - { "AssemblyVersion", this.AssemblyVersion }, - { "AssemblyFileVersion", this.AssemblyFileVersion }, - { "AssemblyInformationalVersion", this.AssemblyInformationalVersion }, - { "AssemblyName", this.AssemblyName }, - { "AssemblyTitle", this.AssemblyTitle }, - { "AssemblyProduct", this.AssemblyProduct }, - { "AssemblyCopyright", this.AssemblyCopyright }, - { "AssemblyCompany", this.AssemblyCompany }, - { "AssemblyConfiguration", this.AssemblyConfiguration }, - { "GitCommitId", this.GitCommitId }, - }; - var boolFields = new Dictionary + var fields = new Dictionary { - { "IsPublicRelease", this.PublicRelease }, - { "IsPrerelease", !string.IsNullOrEmpty(this.PrereleaseVersion) }, + { "AssemblyVersion", (this.AssemblyVersion, false) }, + { "AssemblyFileVersion", (this.AssemblyFileVersion, false) }, + { "AssemblyInformationalVersion", (this.AssemblyInformationalVersion, false) }, + { "AssemblyName", (this.AssemblyName, false) }, + { "AssemblyTitle", (this.AssemblyTitle, false) }, + { "AssemblyProduct", (this.AssemblyProduct, false) }, + { "AssemblyCopyright", (this.AssemblyCopyright, false) }, + { "AssemblyCompany", (this.AssemblyCompany, false) }, + { "AssemblyConfiguration", (this.AssemblyConfiguration, false) }, + { "GitCommitId", (this.GitCommitId, false) }, + // These properties should be defined even if they are empty strings: + { "RootNamespace", (this.RootNamespace, true) }, + // These non-string properties are always emitted: + { "IsPublicRelease", (this.PublicRelease, true) }, + { "IsPrerelease", (!string.IsNullOrEmpty(this.PrereleaseVersion), true) }, }; if (hasKeyInfo) { - fields.Add("PublicKey", publicKey); - fields.Add("PublicKeyToken", publicKeyToken); + fields.Add("PublicKey", (publicKey, false)); + fields.Add("PublicKeyToken", (publicKeyToken, false)); } - foreach (var pair in fields) + if (long.TryParse(this.GitCommitDateTicks, out long gitCommitDateTicks)) { - if (!string.IsNullOrEmpty(pair.Value)) - { - this.generator.AddThisAssemblyMember(pair.Key, pair.Value); - } + fields.Add("GitCommitDate", (gitCommitDateTicks, true)); } - foreach (var pair in boolFields) + if (this.AdditionalThisAssemblyFields != null && this.AdditionalThisAssemblyFields.Length > 0) { - this.generator.AddThisAssemblyMember(pair.Key, pair.Value); + foreach (var item in this.AdditionalThisAssemblyFields) + { + if (item == null) + continue; + + var name = item.ItemSpec.Trim(); + var metaClone = item.CloneCustomMetadata(); + var meta = new Dictionary(metaClone.Count, StringComparer.OrdinalIgnoreCase); + var iter = metaClone.GetEnumerator(); + + while ( iter.MoveNext() ) + { + meta.Add((string)iter.Key, (string)iter.Value); + } + + object value = null; + bool emitIfEmpty = false; + + if (meta.TryGetValue("String", out var stringValue)) + { + value = stringValue; + if (meta.TryGetValue("EmitIfEmpty", out var emitIfEmptyString)) + { + if (!bool.TryParse(emitIfEmptyString, out emitIfEmpty)) + { + this.Log.LogError("The value '{0}' for EmitIfEmpty metadata for item '{1}' in AdditionalThisAssemblyFields is not valid.", emitIfEmptyString, name); + continue; + } + } + } + + if (meta.TryGetValue("Boolean", out var boolText)) + { + if (value != null) + { + this.Log.LogError("The metadata for item '{0}' in AdditionalThisAssemblyFields specifies more than one kind of value.", name); + continue; + } + + if (bool.TryParse(boolText, out var boolValue)) + { + value = boolValue; + } + else + { + this.Log.LogError("The Boolean value '{0}' for item '{1}' in AdditionalThisAssemblyFields is not valid.", boolText, name); + continue; + } + } + + if (meta.TryGetValue("Ticks", out var ticksText)) + { + if (value != null) + { + this.Log.LogError("The metadata for item '{0}' in AdditionalThisAssemblyFields specifies more than one kind of value.", name); + continue; + } + + if (long.TryParse(ticksText, out var ticksValue)) + { + value = ticksValue; + } + else + { + this.Log.LogError("The Ticks value '{0}' for item '{1}' in AdditionalThisAssemblyFields is not valid.", ticksText, name); + continue; + } + } + + if ( value == null ) + { + this.Log.LogWarning("Field '{0}' in AdditionalThisAssemblyFields has no value and will be ignored.", name); + continue; + } + + if (fields.ContainsKey(name)) + { + this.Log.LogError("Field name '{0}' in AdditionalThisAssemblyFields has already been defined.", name); + continue; + } + + fields.Add(name, (value, emitIfEmpty)); + } } - if (long.TryParse(this.GitCommitDateTicks, out long gitCommitDateTicks)) + return fields.OrderBy(f => f.Key).ToList(); + } + + private void GenerateThisAssemblyClass() + { + this.generator.StartThisAssemblyClass(); + + var fields = this.GetFieldsForThisAssembly(); + + foreach (var pair in fields) { - this.generator.AddCommitDateProperty(gitCommitDateTicks); - } + switch (pair.Value.Value) + { + case null: + if (pair.Value.EmitIfEmpty) + { + this.generator.AddThisAssemblyMember(pair.Key, string.Empty); + } + break; - // These properties should be defined even if they are empty. - this.generator.AddThisAssemblyMember("RootNamespace", this.RootNamespace); + case string stringValue: + if (pair.Value.EmitIfEmpty || !string.IsNullOrEmpty(stringValue)) + { + this.generator.AddThisAssemblyMember(pair.Key, stringValue); + } + break; + + case bool boolValue: + this.generator.AddThisAssemblyMember(pair.Key, boolValue); + break; + + case long ticksValue: + this.generator.AddThisAssemblyMember(pair.Key, ticksValue); + break; + + default: + throw new NotImplementedException(); + } + } this.generator.EndThisAssemblyClass(); } @@ -464,6 +580,8 @@ internal CodeGenerator() internal abstract void AddThisAssemblyMember(string name, bool value); + internal abstract void AddThisAssemblyMember(string name, long ticks); + internal abstract void EndThisAssemblyClass(); /// @@ -479,8 +597,6 @@ internal void AddBlankLine() this.codeBuilder.AppendLine(); } - internal abstract void AddCommitDateProperty(long ticks); - protected void AddCodeComment(string comment, string token) { var sr = new StringReader(comment); @@ -510,6 +626,11 @@ internal override void AddThisAssemblyMember(string name, bool value) this.codeBuilder.AppendLine($" static member internal {name} = {(value ? "true" : "false")}"); } + internal override void AddThisAssemblyMember(string name, long ticks) + { + this.codeBuilder.AppendLine($" static member internal {name} = new System.DateTime({ticks}L, System.DateTimeKind.Utc)"); + } + internal override void EmitNamespaceIfRequired(string ns) { this.codeBuilder.AppendLine($"namespace {ns}"); @@ -520,11 +641,6 @@ internal override void DeclareAttribute(Type type, string arg) this.codeBuilder.AppendLine($"[]"); } - internal override void AddCommitDateProperty(long ticks) - { - this.codeBuilder.AppendLine($" static member internal GitCommitDate = new System.DateTime({ticks}L, System.DateTimeKind.Utc)"); - } - internal override void EndThisAssemblyClass() { this.codeBuilder.AppendLine("do()"); @@ -576,9 +692,9 @@ internal override void AddThisAssemblyMember(string name, bool value) this.codeBuilder.AppendLine($" internal const bool {name} = {(value ? "true" : "false")};"); } - internal override void AddCommitDateProperty(long ticks) + internal override void AddThisAssemblyMember(string name, long ticks) { - this.codeBuilder.AppendLine($" internal static readonly System.DateTime GitCommitDate = new System.DateTime({ticks}L, System.DateTimeKind.Utc);"); + this.codeBuilder.AppendLine($" internal static readonly System.DateTime {name} = new System.DateTime({ticks}L, System.DateTimeKind.Utc);"); } internal override void EndThisAssemblyClass() @@ -623,9 +739,9 @@ internal override void AddThisAssemblyMember(string name, bool value) this.codeBuilder.AppendLine($" Friend Const {name} As Boolean = {(value ? "True" : "False")}"); } - internal override void AddCommitDateProperty(long ticks) + internal override void AddThisAssemblyMember(string name, long ticks) { - this.codeBuilder.AppendLine($" Friend Shared ReadOnly GitCommitDate As System.DateTime = New System.DateTime({ticks}L, System.DateTimeKind.Utc)"); + this.codeBuilder.AppendLine($" Friend Shared ReadOnly {name} As System.DateTime = New System.DateTime({ticks}L, System.DateTimeKind.Utc)"); } internal override void EndThisAssemblyClass() diff --git a/src/Nerdbank.GitVersioning.Tasks/build/Nerdbank.GitVersioning.targets b/src/Nerdbank.GitVersioning.Tasks/build/Nerdbank.GitVersioning.targets index c4a60e56..9e39ea91 100644 --- a/src/Nerdbank.GitVersioning.Tasks/build/Nerdbank.GitVersioning.targets +++ b/src/Nerdbank.GitVersioning.Tasks/build/Nerdbank.GitVersioning.targets @@ -163,6 +163,7 @@ GitCommitDateTicks="$(GitCommitDateTicks)" EmitNonVersionCustomAttributes="$(NBGV_EmitNonVersionCustomAttributes)" EmitThisAssemblyClass="$(NBGV_EmitThisAssemblyClass)" + AdditionalThisAssemblyFields="@(AdditionalThisAssemblyFields)" /> From aaf7fe646534c43ea2b69a102be41e1441f6a9f1 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 18 Mar 2021 09:26:57 -0600 Subject: [PATCH 2/2] Touch-up on AssemblyVersionInfo changes --- .../AssemblyVersionInfo.cs | 143 ++++++++---------- 1 file changed, 59 insertions(+), 84 deletions(-) diff --git a/src/Nerdbank.GitVersioning.Tasks/AssemblyVersionInfo.cs b/src/Nerdbank.GitVersioning.Tasks/AssemblyVersionInfo.cs index 8d869850..fa9aa863 100644 --- a/src/Nerdbank.GitVersioning.Tasks/AssemblyVersionInfo.cs +++ b/src/Nerdbank.GitVersioning.Tasks/AssemblyVersionInfo.cs @@ -10,6 +10,7 @@ using System.Text; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; + using Validation; public class AssemblyVersionInfo : Task { @@ -112,7 +113,7 @@ public override bool Execute() { // attempt to use local codegen string fileContent = this.BuildCode(); - if (fileContent != null) + if (fileContent is object) { Directory.CreateDirectory(Path.GetDirectoryName(this.OutputFile)); Utilities.FileOperationWithRetry(() => File.WriteAllText(this.OutputFile, fileContent)); @@ -179,6 +180,13 @@ private CodeTypeDeclaration CreateThisAssemblyClass() { switch (pair.Value.Value) { + case null: + if (pair.Value.EmitIfEmpty) + { + thisAssembly.Members.Add(CreateField(pair.Key, (string)null)); + } + + break; case string stringValue: if (pair.Value.EmitIfEmpty || !string.IsNullOrEmpty(stringValue)) { @@ -190,12 +198,12 @@ private CodeTypeDeclaration CreateThisAssemblyClass() thisAssembly.Members.Add(CreateField(pair.Key, boolValue)); break; - case long ticksValue: - thisAssembly.Members.AddRange(CreateField(pair.Key, ticksValue).ToArray()); + case DateTime dateValue: + thisAssembly.Members.AddRange(CreateDateTimeField(pair.Key, dateValue).ToArray()); break; default: - throw new NotImplementedException(); + throw new NotSupportedException($"Value type {pair.Value.Value.GetType().Name} as found for the \"{pair.Key}\" property is not supported."); } } @@ -240,45 +248,15 @@ private static CodeMemberField CreateField(string name, T value) }; } - private static IEnumerable CreateField(string name, long ticks) + private static IEnumerable CreateDateTimeField(string name, DateTime value) { - if ( string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentNullException(nameof(name)); - } + Requires.NotNullOrEmpty(name, nameof(name)); - // internal static System.DateTime GitCommitDate {{ get; }} = new System.DateTime({ticks}, System.DateTimeKind.Utc);"); - - // For backing field name, try to use name with first char converted to lower case, or otherwise suffix with underscore. - string fieldName = null; - var char0 = name[0]; - - if ( char.IsUpper( char0) ) - { - fieldName = - name.Length == 1 - ? new string(char.ToLowerInvariant(char0), 1) - : new string(char.ToLowerInvariant(char0), 1) + name.Substring(1); - } - else - { - fieldName = name + "_"; - } - - yield return new CodeMemberField(typeof(DateTime), fieldName) - { - Attributes = MemberAttributes.Private, - InitExpression = new CodeObjectCreateExpression( - typeof(DateTime), - new CodePrimitiveExpression(ticks), - new CodePropertyReferenceExpression( - new CodeTypeReferenceExpression(typeof(DateTimeKind)), - nameof(DateTimeKind.Utc))) - }; + // internal static System.DateTime GitCommitDate => new System.DateTime({ticks}, System.DateTimeKind.Utc);"); var property = new CodeMemberProperty() { - Attributes = MemberAttributes.Assembly, + Attributes = MemberAttributes.Assembly | MemberAttributes.Static | MemberAttributes.Final, Type = new CodeTypeReference(typeof(DateTime)), Name = name, HasGet = true, @@ -287,9 +265,12 @@ private static IEnumerable CreateField(string name, long ticks) property.GetStatements.Add( new CodeMethodReturnStatement( - new CodeFieldReferenceExpression( - null, - fieldName))); + new CodeObjectCreateExpression( + typeof(DateTime), + new CodePrimitiveExpression(value.Ticks), + new CodePropertyReferenceExpression( + new CodeTypeReferenceExpression(typeof(DateTimeKind)), + nameof(DateTimeKind.Utc))))); yield return property; } @@ -312,7 +293,7 @@ private static CodeAttributeDeclaration DeclareAttribute(Type attributeType, par public override bool Execute() { string fileContent = this.BuildCode(); - if (fileContent != null) + if (fileContent is object) { Directory.CreateDirectory(Path.GetDirectoryName(this.OutputFile)); Utilities.FileOperationWithRetry(() => File.WriteAllText(this.OutputFile, fileContent)); @@ -330,7 +311,7 @@ public override bool Execute() public string BuildCode() { this.generator = this.CreateGenerator(); - if (this.generator != null) + if (this.generator is object) { this.generator.AddComment(FileHeaderComment); this.generator.AddBlankLine(); @@ -411,89 +392,83 @@ private void GenerateAssemblyAttributes() if (long.TryParse(this.GitCommitDateTicks, out long gitCommitDateTicks)) { - fields.Add("GitCommitDate", (gitCommitDateTicks, true)); + fields.Add("GitCommitDate", (new DateTime(gitCommitDateTicks, DateTimeKind.Utc), true)); } - if (this.AdditionalThisAssemblyFields != null && this.AdditionalThisAssemblyFields.Length > 0) + if (this.AdditionalThisAssemblyFields is object) { foreach (var item in this.AdditionalThisAssemblyFields) { - if (item == null) - continue; - var name = item.ItemSpec.Trim(); - var metaClone = item.CloneCustomMetadata(); - var meta = new Dictionary(metaClone.Count, StringComparer.OrdinalIgnoreCase); - var iter = metaClone.GetEnumerator(); - - while ( iter.MoveNext() ) + var meta = new Dictionary(item.MetadataCount, StringComparer.OrdinalIgnoreCase); + foreach (string metadataName in item.MetadataNames) { - meta.Add((string)iter.Key, (string)iter.Value); + meta.Add(metadataName, item.GetMetadata(metadataName)); } object value = null; bool emitIfEmpty = false; - if (meta.TryGetValue("String", out var stringValue)) + if (meta.TryGetValue("String", out string stringValue)) { value = stringValue; - if (meta.TryGetValue("EmitIfEmpty", out var emitIfEmptyString)) + if (meta.TryGetValue("EmitIfEmpty", out string emitIfEmptyString)) { if (!bool.TryParse(emitIfEmptyString, out emitIfEmpty)) { - this.Log.LogError("The value '{0}' for EmitIfEmpty metadata for item '{1}' in AdditionalThisAssemblyFields is not valid.", emitIfEmptyString, name); + this.Log.LogError($"The value '{emitIfEmptyString}' for EmitIfEmpty metadata for item '{name}' in {nameof(this.AdditionalThisAssemblyFields)} is not valid."); continue; } } } - if (meta.TryGetValue("Boolean", out var boolText)) + if (meta.TryGetValue("Boolean", out string boolText)) { - if (value != null) + if (value is object) { - this.Log.LogError("The metadata for item '{0}' in AdditionalThisAssemblyFields specifies more than one kind of value.", name); + this.Log.LogError($"The metadata for item '{name}' in {nameof(this.AdditionalThisAssemblyFields)} specifies more than one kind of value."); continue; } - if (bool.TryParse(boolText, out var boolValue)) + if (bool.TryParse(boolText, out bool boolValue)) { value = boolValue; } else { - this.Log.LogError("The Boolean value '{0}' for item '{1}' in AdditionalThisAssemblyFields is not valid.", boolText, name); + this.Log.LogError($"The Boolean value '{boolText}' for item '{name}' in AdditionalThisAssemblyFields is not valid."); continue; } } - if (meta.TryGetValue("Ticks", out var ticksText)) + if (meta.TryGetValue("Ticks", out string ticksText)) { - if (value != null) + if (value is object) { - this.Log.LogError("The metadata for item '{0}' in AdditionalThisAssemblyFields specifies more than one kind of value.", name); + this.Log.LogError($"The metadata for item '{name}' in {nameof(this.AdditionalThisAssemblyFields)} specifies more than one kind of value."); continue; } - if (long.TryParse(ticksText, out var ticksValue)) + if (long.TryParse(ticksText, out long ticksValue)) { - value = ticksValue; + value = new DateTime(ticksValue, DateTimeKind.Utc); } else { - this.Log.LogError("The Ticks value '{0}' for item '{1}' in AdditionalThisAssemblyFields is not valid.", ticksText, name); + this.Log.LogError($"The Ticks value '{ticksText}' for item '{name}' in {nameof(this.AdditionalThisAssemblyFields)} is not valid."); continue; } } - if ( value == null ) + if (value is null) { - this.Log.LogWarning("Field '{0}' in AdditionalThisAssemblyFields has no value and will be ignored.", name); + this.Log.LogWarning($"Field '{name}' in {nameof(this.AdditionalThisAssemblyFields)} has no value and will be ignored."); continue; } if (fields.ContainsKey(name)) { - this.Log.LogError("Field name '{0}' in AdditionalThisAssemblyFields has already been defined.", name); + this.Log.LogError($"Field name '{name}' in {nameof(this.AdditionalThisAssemblyFields)} is defined multiple times."); continue; } @@ -532,12 +507,12 @@ private void GenerateThisAssemblyClass() this.generator.AddThisAssemblyMember(pair.Key, boolValue); break; - case long ticksValue: - this.generator.AddThisAssemblyMember(pair.Key, ticksValue); + case DateTime datetimeValue: + this.generator.AddThisAssemblyMember(pair.Key, datetimeValue); break; default: - throw new NotImplementedException(); + throw new NotSupportedException($"Value type {pair.Value.Value.GetType().Name} as found for the \"{pair.Key}\" property is not supported."); } } @@ -580,7 +555,7 @@ internal CodeGenerator() internal abstract void AddThisAssemblyMember(string name, bool value); - internal abstract void AddThisAssemblyMember(string name, long ticks); + internal abstract void AddThisAssemblyMember(string name, DateTime value); internal abstract void EndThisAssemblyClass(); @@ -601,7 +576,7 @@ protected void AddCodeComment(string comment, string token) { var sr = new StringReader(comment); string line; - while ((line = sr.ReadLine()) != null) + while ((line = sr.ReadLine()) is object) { this.codeBuilder.Append(token); this.codeBuilder.AppendLine(line); @@ -626,9 +601,9 @@ internal override void AddThisAssemblyMember(string name, bool value) this.codeBuilder.AppendLine($" static member internal {name} = {(value ? "true" : "false")}"); } - internal override void AddThisAssemblyMember(string name, long ticks) + internal override void AddThisAssemblyMember(string name, DateTime value) { - this.codeBuilder.AppendLine($" static member internal {name} = new System.DateTime({ticks}L, System.DateTimeKind.Utc)"); + this.codeBuilder.AppendLine($" static member internal {name} = new System.DateTime({value.Ticks}L, System.DateTimeKind.Utc)"); } internal override void EmitNamespaceIfRequired(string ns) @@ -692,9 +667,9 @@ internal override void AddThisAssemblyMember(string name, bool value) this.codeBuilder.AppendLine($" internal const bool {name} = {(value ? "true" : "false")};"); } - internal override void AddThisAssemblyMember(string name, long ticks) + internal override void AddThisAssemblyMember(string name, DateTime value) { - this.codeBuilder.AppendLine($" internal static readonly System.DateTime {name} = new System.DateTime({ticks}L, System.DateTimeKind.Utc);"); + this.codeBuilder.AppendLine($" internal static readonly System.DateTime {name} = new System.DateTime({value.Ticks}L, System.DateTimeKind.Utc);"); } internal override void EndThisAssemblyClass() @@ -739,9 +714,9 @@ internal override void AddThisAssemblyMember(string name, bool value) this.codeBuilder.AppendLine($" Friend Const {name} As Boolean = {(value ? "True" : "False")}"); } - internal override void AddThisAssemblyMember(string name, long ticks) + internal override void AddThisAssemblyMember(string name, DateTime value) { - this.codeBuilder.AppendLine($" Friend Shared ReadOnly {name} As System.DateTime = New System.DateTime({ticks}L, System.DateTimeKind.Utc)"); + this.codeBuilder.AppendLine($" Friend Shared ReadOnly {name} As System.DateTime = New System.DateTime({value.Ticks}L, System.DateTimeKind.Utc)"); } internal override void EndThisAssemblyClass() @@ -797,14 +772,14 @@ private bool TryReadKeyInfo(out string publicKey, out string publicKeyToken) publicKeyBytes = GetPublicKeyFromKeyContainer(this.AssemblyKeyContainerName); } - if (publicKeyBytes != null && publicKeyBytes.Length > 0) // If .NET 2.0 isn't installed, we get byte[0] back. + if (publicKeyBytes is object && publicKeyBytes.Length > 0) // If .NET 2.0 isn't installed, we get byte[0] back. { publicKey = ToHex(publicKeyBytes); publicKeyToken = ToHex(CryptoBlobParser.GetStrongNameTokenFromPublicKey(publicKeyBytes)); } else { - if (publicKeyBytes != null) + if (publicKeyBytes is object) { this.Log.LogWarning("Unable to emit public key fields in ThisAssembly class because .NET 2.0 isn't installed."); }