Skip to content

Commit

Permalink
Switch to using the .NET SDK properties that are defined to provide t…
Browse files Browse the repository at this point in the history
…he TFM instead of using the assembly that defines System.Object (dotnet#80646)

Fixes dotnet#80621
  • Loading branch information
jkoritzinsky authored and mdh1418 committed Jan 24, 2023
1 parent ae3b8b9 commit 434d4f1
Show file tree
Hide file tree
Showing 15 changed files with 257 additions and 85 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// 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);
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,36 @@
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
{
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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;
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down
Loading

0 comments on commit 434d4f1

Please sign in to comment.