未验证 提交 729f22cf 编写于 作者: J Jeremy Koritzinsky 提交者: GitHub

Switch to using the .NET SDK properties that are defined to provide the TFM...

Switch to using the .NET SDK properties that are defined to provide the TFM instead of using the assembly that defines System.Object (#80646)

Fixes https://github.com/dotnet/runtime/issues/80621
上级 e6bb6761
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.Interop;
using Microsoft.Interop.UnitTests;
using SourceGenerators.Tests;
namespace SourceGenerators.Tests
{
/// <summary>
/// An implementation of <see cref="AnalyzerConfigOptionsProvider"/> that provides configuration in code
/// of global options.
/// </summary>
internal class GlobalOptionsOnlyProvider : AnalyzerConfigOptionsProvider
{
public GlobalOptionsOnlyProvider(AnalyzerConfigOptions globalOptions)
{
GlobalOptions = globalOptions;
}
public sealed override AnalyzerConfigOptions GlobalOptions { get; }
public sealed override AnalyzerConfigOptions GetOptions(SyntaxTree tree)
{
return EmptyOptions.Instance;
}
public sealed override AnalyzerConfigOptions GetOptions(AdditionalText textFile)
{
return EmptyOptions.Instance;
}
private sealed class EmptyOptions : AnalyzerConfigOptions
{
public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value)
{
value = null;
return false;
}
public static AnalyzerConfigOptions Instance = new EmptyOptions();
}
}
}
......@@ -55,11 +55,19 @@ public override void Initialize(AnalysisContext context)
if (libraryImportAttrType == null)
return;
context.RegisterSymbolAction(symbolContext => AnalyzeSymbol(symbolContext, libraryImportAttrType), SymbolKind.Method);
TargetFrameworkSettings targetFramework = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.GetTargetFrameworkSettings();
StubEnvironment env = new StubEnvironment(
context.Compilation,
targetFramework.TargetFramework,
targetFramework.Version,
context.Compilation.SourceModule.GetAttributes().Any(attr => attr.AttributeClass.ToDisplayString() == TypeNames.System_Runtime_CompilerServices_SkipLocalsInitAttribute));
context.RegisterSymbolAction(symbolContext => AnalyzeSymbol(symbolContext, libraryImportAttrType, env), SymbolKind.Method);
});
}
private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol libraryImportAttrType)
private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol libraryImportAttrType, StubEnvironment env)
{
var method = (IMethodSymbol)context.Symbol;
......@@ -86,7 +94,6 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbo
// If any diagnostics or failures to marshal are reported, then mark this diagnostic with a property signifying that it may require
// later user work.
AnyDiagnosticsSink diagnostics = new();
StubEnvironment env = context.Compilation.CreateStubEnvironment();
AttributeData dllImportAttribute = method.GetAttributes().First(attr => attr.AttributeClass.ToDisplayString() == TypeNames.DllImportAttribute);
SignatureContext targetSignatureContext = SignatureContext.Create(method, DefaultMarshallingInfoParser.Create(env, diagnostics, method, CreateInteropAttributeDataFromDllImport(dllImportData), dllImportAttribute), env, typeof(ConvertToLibraryImportAnalyzer).Assembly);
......
......@@ -15,5 +15,9 @@
of generating a stub that handles all of the marshalling.
-->
<CompilerVisibleProperty Include="LibraryImportGenerator_GenerateForwarders" />
<!-- These properies are defined by the MSBuild SDK but are used in the interop source generators' TFM calculations -->
<CompilerVisibleProperty Include="TargetFrameworkIdentifier" />
<CompilerVisibleProperty Include="TargetFrameworkVersion" />
</ItemGroup>
</Project>
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.Interop
{
// This type is a record to get the generated equality and hashing operators
// which will be faster than the reflection-based ones.
public readonly record struct TargetFrameworkSettings(TargetFramework TargetFramework, Version Version);
public static class AnalyzerConfigOptionsExtensions
{
private static readonly Version FirstNonCoreVersion = new(5, 0);
// Parse from the informational version as that is the only version that always matches the TFM version
// even in debug builds.
private static readonly Version ThisAssemblyVersion = Version.Parse(
typeof(IncrementalGeneratorInitializationContextExtensions).Assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion.Split('-')[0]);
public static TargetFrameworkSettings GetTargetFrameworkSettings(this AnalyzerConfigOptions options)
{
// Our generator only runs in the following scenarios:
// - In the dotnet/runtime repository.
// - In a .NET SDK for the same TFM that matches the version of this assembly.
// We'll try to pull the TFM information from the build, but if it is not present,
// then we'll assume we're in the ref pack as the TFM information will always be present in the dotnet/runtime build.
options.TryGetValue("build_property.TargetFrameworkIdentifier", out string? frameworkIdentifier);
options.TryGetValue("build_property.TargetFrameworkVersion", out string? versionString);
// TargetFrameworkVersion starts with a 'v'.
Version? version = versionString is not null ? Version.Parse(versionString.Substring(1)) : null;
return new TargetFrameworkSettings(
frameworkIdentifier switch
{
".NETStandard" => TargetFramework.Standard,
".NETCoreApp" when version is not null && version < FirstNonCoreVersion => TargetFramework.Core,
".NETCoreApp" => TargetFramework.Net,
// If the TFM is not specified, we'll infer it from this assembly.
// Since we only ship this assembly as part of the Microsoft.NETCore.App TFM,
// the down-level support only matters for the repo where this project is built.
// In all other cases, we will only be used from the TFM with the matching version as our assembly.
null => TargetFramework.Net,
// Assume that all unknown target framework identifiers are .NET Framework.
// All legacy target frameworks will have effectively the same feature set as we provide for .NET Framework
// for our purposes.
_ => TargetFramework.Framework
},
// If the version is not specified, we'll infer it from this assembly.
version ?? ThisAssemblyVersion);
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
namespace Microsoft.Interop
{
public static class CompilationExtensions
{
public static StubEnvironment CreateStubEnvironment(this Compilation compilation)
{
TargetFramework targetFramework = DetermineTargetFramework(compilation, out Version targetFrameworkVersion);
return new StubEnvironment(
compilation,
targetFramework,
targetFrameworkVersion,
compilation.SourceModule.GetAttributes().Any(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.System_Runtime_CompilerServices_SkipLocalsInitAttribute));
static TargetFramework DetermineTargetFramework(Compilation compilation, out Version version)
{
IAssemblySymbol systemAssembly = compilation.GetSpecialType(SpecialType.System_Object).ContainingAssembly;
version = systemAssembly.Identity.Version;
return systemAssembly.Identity.Name switch
{
// .NET Framework
"mscorlib" => TargetFramework.Framework,
// .NET Standard
"netstandard" => TargetFramework.Standard,
// .NET Core (when version < 5.0) or .NET
"System.Runtime" or "System.Private.CoreLib" =>
(version.Major < 5) ? TargetFramework.Core : TargetFramework.Net,
_ => TargetFramework.Unknown,
};
}
}
}
}
......@@ -5,8 +5,10 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.Interop
{
......@@ -14,7 +16,25 @@ public static class IncrementalGeneratorInitializationContextExtensions
{
public static IncrementalValueProvider<StubEnvironment> CreateStubEnvironmentProvider(this IncrementalGeneratorInitializationContext context)
{
return context.CompilationProvider.Select(static (comp, ct) => comp.CreateStubEnvironment());
var tfmVersion = context.AnalyzerConfigOptionsProvider
.Select((options, ct) => options.GlobalOptions.GetTargetFrameworkSettings());
var isModuleSkipLocalsInit = context.SyntaxProvider
.ForAttributeWithMetadataName(
TypeNames.System_Runtime_CompilerServices_SkipLocalsInitAttribute,
(node, ct) => node is ICompilationUnitSyntax,
// If SkipLocalsInit is applied at the top level, it is either applied to the module
// or is invalid syntax. As a result, we just need to know if there's any top-level
// SkipLocalsInit attributes. So the result we return here is meaningless.
(context, ct) => true)
.Collect()
.Select((topLevelAttrs, ct) => !topLevelAttrs.IsEmpty);
return tfmVersion
.Combine(isModuleSkipLocalsInit)
.Combine(context.CompilationProvider)
.Select((data, ct) =>
new StubEnvironment(data.Right, data.Left.Left.TargetFramework, data.Left.Left.Version, data.Left.Right));
}
public static void RegisterDiagnostics(this IncrementalGeneratorInitializationContext context, IncrementalValuesProvider<Diagnostic> diagnostics)
......
......@@ -10,9 +10,13 @@
<ItemGroup>
<Compile Include="$(CommonTestPath)SourceGenerators\LiveReferencePack.cs"
Link="Common\SourceGenerators\LiveReferencePack.cs" />
Link="Common\SourceGenerators\LiveReferencePack.cs" />
<Compile Include="$(CommonTestPath)SourceGenerators\GlobalOptionsOnlyProvider.cs"
Link="Common\SourceGenerators\GlobalOptionsOnlyProvider.cs" />
<Compile Include="..\Common\TestUtils.cs"
Link="Common\TestUtils.cs" />
<Compile Include="..\Common\TargetFrameworkOptions.cs"
Link="Common\TargetFrameworkOptions.cs" />
<Compile Include="..\Common\CustomCollectionMarshallingCodeSnippets.cs"
Link="Common\CustomCollectionMarshallingCodeSnippets.cs" />
<Compile Include="..\Common\CustomStructMarshallingCodeSnippets.cs"
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.Interop;
using Microsoft.Interop.UnitTests;
using SourceGenerators.Tests;
namespace Microsoft.Interop.UnitTests
{
/// <summary>
/// An implementation of <see cref="AnalyzerConfigOptions"/> that provides configuration in code
/// of the target framework options. Used when testing interop source generators.
/// </summary>
public class TargetFrameworkConfigOptions : AnalyzerConfigOptions
{
private static readonly string _liveTargetFrameworkVersion;
private readonly string _targetFrameworkIdentifier;
private readonly string _targetFrameworkVersion;
static TargetFrameworkConfigOptions()
{
Version liveVersion = Version.Parse(
typeof(TargetFrameworkConfigOptions)
.Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!
.InformationalVersion.Split('-')[0]);
_liveTargetFrameworkVersion = $"v{liveVersion.ToString(2)}";
}
public TargetFrameworkConfigOptions(TestTargetFramework targetFramework)
{
_targetFrameworkIdentifier = targetFramework switch
{
TestTargetFramework.Framework => ".NETFramework",
TestTargetFramework.Standard => ".NETStandard",
_ => ".NETCoreApp"
};
_targetFrameworkVersion = targetFramework switch
{
TestTargetFramework.Framework => "v4.8",
TestTargetFramework.Standard => "v2.1",
TestTargetFramework.Core => "v3.1",
TestTargetFramework.Net6 => "v6.0",
TestTargetFramework.Net => _liveTargetFrameworkVersion,
_ => throw new UnreachableException()
};
}
public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value)
{
switch (key)
{
case "build_property.TargetFrameworkIdentifier":
value = _targetFrameworkIdentifier;
return true;
case "build_property.TargetFrameworkVersion":
value = _targetFrameworkVersion;
return true;
default:
value = null;
return false;
}
}
}
}
......@@ -6,6 +6,7 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.DotNet.XUnitExtensions;
using SourceGenerators.Tests;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
......
......@@ -4,6 +4,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.Interop.UnitTests;
using SourceGenerators.Tests;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
......@@ -155,7 +156,7 @@ partial class C
}}";
Compilation comp = await TestUtils.CreateCompilation(source, targetFramework);
Compilation newComp = TestUtils.RunGenerators(comp, out _, new Microsoft.Interop.LibraryImportGenerator());
Compilation newComp = TestUtils.RunGenerators(comp, new GlobalOptionsOnlyProvider(new TargetFrameworkConfigOptions(targetFramework)), out _, new Microsoft.Interop.LibraryImportGenerator());
ITypeSymbol c = newComp.GetTypeByMetadataName("C")!;
IMethodSymbol stubMethod = c.GetMembers().OfType<IMethodSymbol>().Single(m => m.Name == "Method");
......
......@@ -5,6 +5,7 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Interop;
using Microsoft.Interop.UnitTests;
using SourceGenerators.Tests;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
......@@ -350,7 +351,11 @@ partial class C
}
" + CodeSnippets.LibraryImportAttributeDeclaration;
Compilation origComp = await TestUtils.CreateCompilation(source, TestTargetFramework.Standard);
Compilation newComp = TestUtils.RunGenerators(origComp, out _, new Microsoft.Interop.LibraryImportGenerator());
Compilation newComp = TestUtils.RunGenerators(
origComp,
new GlobalOptionsOnlyProvider(new TargetFrameworkConfigOptions(TestTargetFramework.Standard)),
out _,
new Microsoft.Interop.LibraryImportGenerator());
IMethodSymbol targetMethod = GetGeneratedPInvokeTargetFromCompilation(newComp);
......@@ -388,7 +393,11 @@ partial class C
}
" + CodeSnippets.LibraryImportAttributeDeclaration;
Compilation origComp = await TestUtils.CreateCompilation(source, TestTargetFramework.Standard);
Compilation newComp = TestUtils.RunGenerators(origComp, out _, new Microsoft.Interop.LibraryImportGenerator());
Compilation newComp = TestUtils.RunGenerators(
origComp,
new GlobalOptionsOnlyProvider(new TargetFrameworkConfigOptions(TestTargetFramework.Standard)),
out _,
new Microsoft.Interop.LibraryImportGenerator());
IMethodSymbol targetMethod = GetGeneratedPInvokeTargetFromCompilation(newComp);
......
......@@ -12,6 +12,7 @@
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Xunit;
using SourceGenerators.Tests;
namespace LibraryImportGenerator.UnitTests
{
......@@ -501,6 +502,7 @@ public async Task ValidateSnippetsFallbackForwarder(string id, string source, Te
var newComp = TestUtils.RunGenerators(
comp,
new GlobalOptionsOnlyProvider(new TargetFrameworkConfigOptions(targetFramework)),
out var generatorDiags,
new Microsoft.Interop.LibraryImportGenerator());
......@@ -611,7 +613,7 @@ public async Task ValidateSnippetsWithMarshalType(string id, string source)
var newComp = TestUtils.RunGenerators(
comp,
new LibraryImportGeneratorOptionsProvider(useMarshalType: true, generateForwarders: false),
new LibraryImportGeneratorOptionsProvider(TestTargetFramework.Net, useMarshalType: true, generateForwarders: false),
out var generatorDiags,
new Microsoft.Interop.LibraryImportGenerator());
......@@ -660,7 +662,7 @@ public async Task ValidateNoGeneratedOuptutForNoImport(string id, string source,
Compilation comp = await TestUtils.CreateCompilation(source, framework, allowUnsafe: false);
TestUtils.AssertPreSourceGeneratorCompilation(comp);
var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.LibraryImportGenerator());
var newComp = TestUtils.RunGenerators(comp, new GlobalOptionsOnlyProvider(new TargetFrameworkConfigOptions(framework)), out var generatorDiags, new Microsoft.Interop.LibraryImportGenerator());
Assert.Empty(generatorDiags);
// Assert we didn't generate any syntax trees, even empty ones
......
......@@ -11,6 +11,7 @@
using Microsoft.CodeAnalysis.Testing;
using Microsoft.Interop;
using Microsoft.Interop.UnitTests;
using SourceGenerators.Tests;
using Xunit;
using StringMarshalling = Microsoft.Interop.StringMarshalling;
......@@ -320,8 +321,11 @@ struct Native
// Compile against Standard so that we generate forwarders
Compilation comp = await TestUtils.CreateCompilation(source, TestTargetFramework.Standard);
TestUtils.AssertPreSourceGeneratorCompilation(comp);
var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.LibraryImportGenerator());
Compilation newComp = TestUtils.RunGenerators(
comp,
new GlobalOptionsOnlyProvider(new TargetFrameworkConfigOptions(TestTargetFramework.Standard)),
out var generatorDiags,
new Microsoft.Interop.LibraryImportGenerator());
DiagnosticResult[] expectedDiags = new DiagnosticResult[]
{
(new DiagnosticResult(GeneratorDiagnostics.CannotForwardToDllImport))
......
......@@ -8,9 +8,13 @@
<ItemGroup>
<Compile Include="$(CommonTestPath)SourceGenerators\LiveReferencePack.cs"
Link="Common\SourceGenerators\LiveReferencePack.cs" />
Link="Common\SourceGenerators\LiveReferencePack.cs" />
<Compile Include="$(CommonTestPath)SourceGenerators\GlobalOptionsOnlyProvider.cs"
Link="Common\SourceGenerators\GlobalOptionsOnlyProvider.cs" />
<Compile Include="..\Common\TestUtils.cs"
Link="Common\TestUtils.cs" />
<Compile Include="..\Common\TargetFrameworkOptions.cs"
Link="Common\TargetFrameworkOptions.cs" />
<Compile Include="..\Common\CustomCollectionMarshallingCodeSnippets.cs"
Link="Common\CustomCollectionMarshallingCodeSnippets.cs" />
<Compile Include="..\Common\CustomStructMarshallingCodeSnippets.cs"
......
......@@ -6,6 +6,7 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.Interop;
using Microsoft.Interop.UnitTests;
using SourceGenerators.Tests;
namespace LibraryImportGenerator.UnitTests
{
......@@ -13,30 +14,19 @@ namespace LibraryImportGenerator.UnitTests
/// An implementation of <see cref="AnalyzerConfigOptionsProvider"/> that provides configuration in code
/// of the options supported by the LibraryImportGenerator source generator. Used for testing various configurations.
/// </summary>
internal class LibraryImportGeneratorOptionsProvider : AnalyzerConfigOptionsProvider
internal sealed class LibraryImportGeneratorOptionsProvider : GlobalOptionsOnlyProvider
{
public LibraryImportGeneratorOptionsProvider(bool useMarshalType, bool generateForwarders)
public LibraryImportGeneratorOptionsProvider(TestTargetFramework targetFramework, bool useMarshalType, bool generateForwarders)
:base(new GlobalGeneratorOptions(targetFramework, useMarshalType, generateForwarders))
{
GlobalOptions = new GlobalGeneratorOptions(useMarshalType, generateForwarders);
}
public override AnalyzerConfigOptions GlobalOptions { get; }
public override AnalyzerConfigOptions GetOptions(SyntaxTree tree)
{
return EmptyOptions.Instance;
}
public override AnalyzerConfigOptions GetOptions(AdditionalText textFile)
{
return EmptyOptions.Instance;
}
private class GlobalGeneratorOptions : AnalyzerConfigOptions
private sealed class GlobalGeneratorOptions : TargetFrameworkConfigOptions
{
private readonly bool _useMarshalType = false;
private readonly bool _generateForwarders = false;
public GlobalGeneratorOptions(bool useMarshalType, bool generateForwarders)
public GlobalGeneratorOptions(TestTargetFramework targetFramework, bool useMarshalType, bool generateForwarders)
: base(targetFramework)
{
_useMarshalType = useMarshalType;
_generateForwarders = generateForwarders;
......@@ -55,21 +45,9 @@ public override bool TryGetValue(string key, [NotNullWhen(true)] out string? val
return true;
default:
value = null;
return false;
return base.TryGetValue(key, out value);
}
}
}
private class EmptyOptions : AnalyzerConfigOptions
{
public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value)
{
value = null;
return false;
}
public static AnalyzerConfigOptions Instance = new EmptyOptions();
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册