Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
jaredpar marked this conversation as resolved.
Show resolved Hide resolved
{
/// <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 @@ -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
Expand Up @@ -5,16 +5,81 @@
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
{
// This type is a record to get the generated equality and hashing operators
// which will be faster than the reflection-based ones.
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
private record struct TargetFrameworkSettings
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
{
public TargetFrameworkSettings(AnalyzerConfigOptions options)
{
options.TryGetValue("build_property.TargetFrameworkIdentifier", out string? frameworkIdentifier);
Identifier = frameworkIdentifier;
options.TryGetValue("build_property.TargetFrameworkVersion", out string? version);
// TargetFrameworkVersion starts with a 'v'.
Version = version is not null ? Version.Parse(version.Substring(1)) : null;
}

public string? Identifier { get; init; }

public Version? Version { get; init; }
}

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]);
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved

public static IncrementalValueProvider<StubEnvironment> CreateStubEnvironmentProvider(this IncrementalGeneratorInitializationContext context)
{
return context.CompilationProvider.Select(static (comp, ct) => comp.CreateStubEnvironment());
// PERF: With the .NET SDK, this path is equivalent to calling CompilationExtensions.CreateStubEnvironment.
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
// However, using the compilation here has been known to cause perf issues, so instead we'll use MSBuild properties
// from the .NET SDK, the version of the source generator assembly, and directly inferring from syntax
// the information we need to calculate for the StubEnvironment object
// and only include the compilation after all of that is already done.
var tfmVersion = context.AnalyzerConfigOptionsProvider
.Select((options, ct) => new TargetFrameworkSettings(options.GlobalOptions))
.Select((tfm, ct) => (TargetFramework: tfm.Identifier switch
{
".NETStandard" => TargetFramework.Standard,
".NETCoreApp" when tfm.Version is not null && tfm.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 downlevel 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.
// This matches the behavior in CompilationExtensions.CreateStubEnvironment
// as all legacy target framework identifiers also use mscorlib.dll as the core library.
_ => TargetFramework.Framework
}, Version: tfm.Version ?? ThisAssemblyVersion));

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)
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
.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
jaredpar marked this conversation as resolved.
Show resolved Hide resolved
{
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Xunit;
using SourceGenerators.Tests;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;

namespace LibraryImportGenerator.UnitTests
{
Expand Down Expand Up @@ -501,6 +503,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());

Expand Down Expand Up @@ -611,7 +614,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());

Expand Down Expand Up @@ -660,7 +663,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -320,8 +321,11 @@ public Native(string s) { }
// 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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading