From a6c207db764509147c66b9ebaa57b7cd72dacd77 Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Mon, 16 Sep 2024 18:56:23 -0300 Subject: [PATCH] Unify and future-proof Assembly*Attributes with Constants One more unification and this is likely the last one. Rather than a custom generator for assembly info attributes, we leverage MSBuild (similar to how the Metadata package does it) to transform the same set of attributes and turn them into @(Constant) items. Now that custom and dynamic roots are possible, this is really straightforward and should be 100% backwards compatible. In the future, if additional aseembly-level attributes matching the `Assembly[Name]Attribute` convention are added to the SDK, they will be surfaced automatically as well. --- .../AssemblyInfoGenerator.cs | 111 ------------------ src/ThisAssembly.AssemblyInfo/CSharp.sbntxt | 56 --------- src/ThisAssembly.AssemblyInfo/Model.cs | 21 ---- .../ThisAssembly.AssemblyInfo.csproj | 23 +--- .../ThisAssembly.AssemblyInfo.targets | 17 ++- .../Visual Basic.sbntxt | 29 ----- 6 files changed, 20 insertions(+), 237 deletions(-) delete mode 100644 src/ThisAssembly.AssemblyInfo/AssemblyInfoGenerator.cs delete mode 100644 src/ThisAssembly.AssemblyInfo/CSharp.sbntxt delete mode 100644 src/ThisAssembly.AssemblyInfo/Model.cs delete mode 100644 src/ThisAssembly.AssemblyInfo/Visual Basic.sbntxt diff --git a/src/ThisAssembly.AssemblyInfo/AssemblyInfoGenerator.cs b/src/ThisAssembly.AssemblyInfo/AssemblyInfoGenerator.cs deleted file mode 100644 index d5e3ec6a..00000000 --- a/src/ThisAssembly.AssemblyInfo/AssemblyInfoGenerator.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading; -using Devlooped.Sponsors; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Text; -using Scriban; -using static Devlooped.Sponsors.SponsorLink; -using Resources = Devlooped.Sponsors.Resources; - -namespace ThisAssembly; - -[Generator(LanguageNames.CSharp)] -public class AssemblyInfoGenerator : IIncrementalGenerator -{ - static readonly HashSet attributes = - [ - nameof(AssemblyConfigurationAttribute), - nameof(AssemblyCompanyAttribute), - nameof(AssemblyCopyrightAttribute), - nameof(AssemblyTitleAttribute), - nameof(AssemblyDescriptionAttribute), - nameof(AssemblyProductAttribute), - nameof(AssemblyVersionAttribute), - nameof(AssemblyInformationalVersionAttribute), - nameof(AssemblyFileVersionAttribute), - ]; - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var metadata = context.SyntaxProvider - .CreateSyntaxProvider( - predicate: static (s, _) => s is AttributeSyntax, - transform: static (ctx, token) => GetAttributes(ctx, token)) - .Where(static m => m is not null) - .Select(static (m, _) => m!.Value) - .Collect(); - - // Read the ThisAssemblyNamespace property or default to null - var right = context.AnalyzerConfigOptionsProvider - .Select((c, t) => c.GlobalOptions.TryGetValue("build_property.ThisAssemblyNamespace", out var ns) && !string.IsNullOrEmpty(ns) ? ns : null) - .Combine(context.ParseOptionsProvider.Combine(context.GetStatusOptions())); - - context.RegisterSourceOutput( - metadata.Combine(right), - GenerateSource); - } - - static KeyValuePair? GetAttributes(GeneratorSyntaxContext ctx, CancellationToken token) - { - var attributeNode = (AttributeSyntax)ctx.Node; - - if (attributeNode.ArgumentList?.Arguments.Count != 1) - return null; - - if (ctx.SemanticModel.GetSymbolInfo(attributeNode, token).Symbol is not IMethodSymbol ctor) - return null; - - var attributeType = ctor.ContainingType; - if (attributeType == null) - return null; - - if (!attributes.Contains(attributeType.Name)) - return null; - - // Remove the "Assembly" prefix and "Attribute" suffix. - var key = attributeType.Name[8..^9]; - var expr = attributeNode.ArgumentList!.Arguments[0].Expression; - var value = ctx.SemanticModel.GetConstantValue(expr, token).ToString(); - // KeyValuePair is a struct and properly equatable for optimal caching in the generator. - return new KeyValuePair(key, value); - } - - static void GenerateSource(SourceProductionContext spc, - (ImmutableArray> attributes, (string? ns, (ParseOptions parse, StatusOptions options))) arg) - { - var (attributes, (ns, (parse, options))) = arg; - var model = new Model([.. attributes], ns); - if (IsEditor) - { - var status = Diagnostics.GetOrSetStatus(options); - if (status == SponsorStatus.Unknown || status == SponsorStatus.Expired) - { - model.Warn = string.Format(CultureInfo.CurrentCulture, Resources.Editor_Disabled, Funding.Product, Funding.HelpUrl); - model.Remarks = Resources.Editor_DisabledRemarks; - } - else if (status == SponsorStatus.Grace && Diagnostics.TryGet() is { } grace && grace.Properties.TryGetValue(nameof(SponsorStatus.Grace), out var days)) - { - model.Remarks = string.Format(CultureInfo.CurrentCulture, Resources.Editor_GraceRemarks, days); - } - } - - if (parse is CSharpParseOptions cs && (int)cs.LanguageVersion >= 1100) - model.RawStrings = true; - - var file = parse.Language.Replace("#", "Sharp") + ".sbntxt"; - var template = Template.Parse(EmbeddedResource.GetContent(file), file); - var output = template.Render(model, member => member.Name); - - spc.AddSource( - "ThisAssembly.AssemblyInfo.g.cs", - SourceText.From(output, Encoding.UTF8)); - } -} diff --git a/src/ThisAssembly.AssemblyInfo/CSharp.sbntxt b/src/ThisAssembly.AssemblyInfo/CSharp.sbntxt deleted file mode 100644 index f2d3d736..00000000 --- a/src/ThisAssembly.AssemblyInfo/CSharp.sbntxt +++ /dev/null @@ -1,56 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.CodeDom.Compiler; -using System.Runtime.CompilerServices; -{{ if Namespace }} -namespace {{ Namespace }}; -{{~ end ~}} - -/// -/// Provides access to the current assembly information. -/// -partial class ThisAssembly -{ - /// - /// Gets the AssemblyInfo attributes. - /// - {{~ if Remarks ~}} - {{ Remarks }} - /// - {{~ end ~}} - [GeneratedCode("ThisAssembly.AssemblyInfo", "{{ Version }}")] - [CompilerGenerated] - public static partial class Info - { - {{- for prop in Properties ~}} - - {{~ if Remarks ~}} - {{ Remarks }} - /// - {{~ end ~}} - {{~ if Warn ~}} - [Obsolete("{{ Warn }}", false - #if NET6_0_OR_GREATER - , UrlFormat = "{{ Url }}" - #endif - )] - {{~ end ~}} - {{~ if RawStrings ~}} - public const string {{ prop.Key }} = - """ - {{ prop.Value }} - """; - {{~ else ~}} - public const string {{ prop.Key }} = @"{{ prop.Value }}"; - {{~ end ~}} - {{~ end ~}} - } -} diff --git a/src/ThisAssembly.AssemblyInfo/Model.cs b/src/ThisAssembly.AssemblyInfo/Model.cs deleted file mode 100644 index 1a988760..00000000 --- a/src/ThisAssembly.AssemblyInfo/Model.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace ThisAssembly; - -public class Model -{ - public Model(IEnumerable> properties, string? ns) - => (Properties, Namespace) - = (properties.ToList(), ns); - - public string? Namespace { get; } - public bool RawStrings { get; set; } = false; - public string Version => Assembly.GetExecutingAssembly().GetName().Version.ToString(3); - public string Url => Devlooped.Sponsors.SponsorLink.Funding.HelpUrl; - public string? Warn { get; set; } - public string? Remarks { get; set; } - - public List> Properties { get; } -} diff --git a/src/ThisAssembly.AssemblyInfo/ThisAssembly.AssemblyInfo.csproj b/src/ThisAssembly.AssemblyInfo/ThisAssembly.AssemblyInfo.csproj index 51518b83..74dd64e5 100644 --- a/src/ThisAssembly.AssemblyInfo/ThisAssembly.AssemblyInfo.csproj +++ b/src/ThisAssembly.AssemblyInfo/ThisAssembly.AssemblyInfo.csproj @@ -4,8 +4,7 @@ netstandard2.0 latest true - - analyzers/dotnet/roslyn$(ThisAssemblyMinimumRoslynVersion) + false @@ -28,30 +27,18 @@ on the `ThisAssembly.Info` class. - - $(MSBuildThisFileDirectory)..\SponsorLink\SponsorLink.Analyzer.targets - ThisAssembly;$(PackageId) - + + + - - - - - - - - + - - - - diff --git a/src/ThisAssembly.AssemblyInfo/ThisAssembly.AssemblyInfo.targets b/src/ThisAssembly.AssemblyInfo/ThisAssembly.AssemblyInfo.targets index 2b93a728..838f4890 100644 --- a/src/ThisAssembly.AssemblyInfo/ThisAssembly.AssemblyInfo.targets +++ b/src/ThisAssembly.AssemblyInfo/ThisAssembly.AssemblyInfo.targets @@ -1,6 +1,4 @@ - - @@ -12,4 +10,19 @@ + + + + $([MSBuild]::ValueOrDefault('%(Identity)', '').Substring(26).Replace('Attribute', '')) + + + + + + + \ No newline at end of file diff --git a/src/ThisAssembly.AssemblyInfo/Visual Basic.sbntxt b/src/ThisAssembly.AssemblyInfo/Visual Basic.sbntxt deleted file mode 100644 index 510363f9..00000000 --- a/src/ThisAssembly.AssemblyInfo/Visual Basic.sbntxt +++ /dev/null @@ -1,29 +0,0 @@ -'''------------------------------------------------------------------------------ -''' -''' This code was generated by a tool. -''' -''' Changes to this file may cause incorrect behavior And will be lost if -''' the code Is regenerated. -''' -'''------------------------------------------------------------------------------ - -{{ if Namespace }} -Namespace {{ Namespace }} -{{ else }} -Namespace Global -{{ end }} - ''' - ''' Provides access to the current assembly information as pure constants, - ''' without requiring reflection. - ''' - Partial Class ThisAssembly - ''' - ''' Gets the AssemblyInfo attributes. - ''' - Partial Class Info - {{~ for prop in Properties ~}} - Public Const {{ prop.Key }} As String = @"{{ prop.Value }}" - - End Class - End Class -End Namespace \ No newline at end of file