Skip to content

Commit

Permalink
Rework to use AnalyzerConfigOptions.GlobalOptions instead of the dotn…
Browse files Browse the repository at this point in the history
…et process
  • Loading branch information
ashovlin committed Feb 13, 2024
1 parent 1646e3a commit e6ec280
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 316 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
Expand Down Expand Up @@ -49,10 +50,10 @@ public class Generator : ISourceGenerator
public Generator()
{
#if DEBUG
//if (!Debugger.IsAttached)
//{
// Debugger.Launch();
//}
// if (!Debugger.IsAttached)
// {
// Debugger.Launch();
// }
#endif
}

Expand All @@ -71,7 +72,7 @@ public void Execute(GeneratorExecutionContext context)
// Check to see if any of the current syntax trees has any error diagnostics. If so
// Skip generation. We only want to sync the CloudFormation template if the project
// can compile.
foreach(var syntaxTree in context.Compilation.SyntaxTrees)
foreach (var syntaxTree in context.Compilation.SyntaxTrees)
{
if(syntaxTree.GetDiagnostics().Any(x => x.Severity == DiagnosticSeverity.Error))
{
Expand Down Expand Up @@ -110,16 +111,16 @@ public void Execute(GeneratorExecutionContext context)

var defaultRuntime = "dotnet6";

// Try to determine the TFM -> defaultRuntime from the project file, in case it's newer than our current default
if (ProjectFileHandler.TryDetermineTargetFramework(receiver.ProjectPath, out var parsedRuntime))
// Try to determine the target framework from the source generator context
if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.TargetFramework", out var targetFramework))
{
if (_targetFrameworksToRuntimes.ContainsKey(parsedRuntime))
if (_targetFrameworksToRuntimes.ContainsKey(targetFramework))
{
defaultRuntime = _targetFrameworksToRuntimes[parsedRuntime];
defaultRuntime = _targetFrameworksToRuntimes[targetFramework];
}
}

// The runtime specified in the global property has precedence over the one we parsed from the project file
// The runtime specified in the global property has precedence over the one we determined from the TFM (if we did)
if (globalPropertiesAttribute != null)
{
var generateMain = globalPropertiesAttribute.NamedArguments.FirstOrDefault(kvp => kvp.Key == "GenerateMain").Value;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using Amazon.Lambda.Annotations.SourceGenerator.FileIO;
using Newtonsoft.Json.Linq;
using System;
using System.Diagnostics;
using System.Xml;

namespace Amazon.Lambda.Annotations.SourceGenerator
Expand All @@ -11,14 +9,6 @@ namespace Amazon.Lambda.Annotations.SourceGenerator
/// </summary>
public class ProjectFileHandler
{
/// <summary>
/// Timeout for the `dotnet msbuild -getProperty` command we use to determine target framework
/// </summary>
private const int DotnetMsbuildTimeoutMs = 5000;

/// <summary>
/// MSBuild property to determine if the project has opted out of the CFN template description
/// </summary>
private const string OptOutNodeXpath = "//PropertyGroup/AWSSuppressLambdaAnnotationsTelemetry";

/// <summary>
Expand Down Expand Up @@ -52,91 +42,5 @@ public static bool IsTelemetrySuppressed(string projectFilePath, IFileManager fi

return false;
}

/// <summary>
/// Attempts to determine a single target framework moniker from a .csproj file
/// </summary>
/// <param name="projectFilePath">Path to a .csproj file</param>
/// <param name="outTargetFramework">Output variable for the target framework moniker</param>
/// <returns>True if a single TFM was determined, false otherwise</returns>
public static bool TryDetermineTargetFramework(string projectFilePath, out string outTargetFramework)
{
outTargetFramework = null;
JObject parsedJson;
try
{
var process = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = "dotnet",
Arguments = $"msbuild {projectFilePath} -getProperty:TargetFramework,TargetFrameworks",
RedirectStandardOutput = true,
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden
}
};

process.Start();
var outputJson = process.StandardOutput.ReadToEnd();
var hasExited = process.WaitForExit(DotnetMsbuildTimeoutMs);

// If it hasn't completed in the specified timeout, stop the process and give up
if (!hasExited)
{
process.Kill();
return false;
}

// If it has completed but unsuccessfully, give up
if (process.ExitCode != 0)
{
return false;
}

if (string.IsNullOrEmpty(outputJson))
{
return false;
}

parsedJson = JObject.Parse(outputJson);
}
catch (Exception)
{
// swallow any exceptions related to `dotnet msbuild`, Generator
// will fall back to allowing the user to specify the target framework
// via the global property
return false;
}

// If there isn't the Properties key in the JSON, we failed to read values for either
if (!parsedJson.ContainsKey("Properties"))
{
return false;
}

// If <TargetFramework> (singular) is specified, that takes precedence over <TargetFrameworks> (plural)
var targetFramework = parsedJson["Properties"]?["TargetFramework"]?.ToString();

if (!string.IsNullOrEmpty(targetFramework))
{
outTargetFramework = targetFramework;
return true;
}


// Otherwise fallback to <TargetFrameworks> (plural)
var possibleList = parsedJson["Properties"]?["TargetFrameworks"]?.ToString();

// But only use it if it contains a single entry,
// otherwise we don't know at this point which entry is being built
if (!string.IsNullOrEmpty(possibleList) && !possibleList.Contains(";"))
{
outTargetFramework = possibleList;
return true;
}

return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@
<Content Remove="..\TestServerlessApp\bin\**" />
<Content Remove="..\TestServerlessApp\serverless.template" />

<Content Include="..\TestServerlessApp.NET8\**">
<Link>TestServerlessApp.NET8\%(RecursiveDir)/%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Remove="..\TestServerlessApp.NET8\obj\**" />
<Content Remove="..\TestServerlessApp.NET8\bin\**" />
<Content Remove="..\TestServerlessApp.NET8\serverless.template" />
<Content Include="..\TestServerlessApp.NET8\**">
<Link>TestServerlessApp.NET8\%(RecursiveDir)/%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Remove="..\TestServerlessApp.NET8\obj\**" />
<Content Remove="..\TestServerlessApp.NET8\bin\**" />
<Content Remove="..\TestServerlessApp.NET8\serverless.template" />

<Content Include="..\TestExecutableServerlessApp\**">
<Link>TestExecutableServerlessApp\%(RecursiveDir)/%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
using System;
using System.Collections.Immutable;
using Amazon.Lambda.Annotations.APIGateway;
using Amazon.Lambda.Annotations.APIGateway;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.Core;
using Amazon.Lambda.RuntimeSupport;
using Amazon.Lambda.Serialization.SystemTextJson;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Immutable;
using System.IO;

namespace Amazon.Lambda.Annotations.SourceGenerators.Tests
{
using Amazon.Lambda.RuntimeSupport;

/// <summary>
/// Source: https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md
/// </summary>
Expand All @@ -23,7 +25,10 @@ public static class CSharpSourceGeneratorVerifier<TSourceGenerator>
public class Test : CSharpSourceGeneratorTest<TSourceGenerator, XUnitVerifier>
{
public enum ReferencesMode {All, NoApiGatewayEvents}
public Test(ReferencesMode referencesMode = ReferencesMode.All)

public enum TargetFramework { Net60, Net80 }

public Test(ReferencesMode referencesMode = ReferencesMode.All, TargetFramework targetFramework = TargetFramework.Net60)
{
if(referencesMode == ReferencesMode.NoApiGatewayEvents)
{
Expand Down Expand Up @@ -51,6 +56,39 @@ public Test(ReferencesMode referencesMode = ReferencesMode.All)
.AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(LambdaBootstrapBuilder).Assembly.Location));
});
}

// Set up the target framework moniker and reference assemblies
if (targetFramework == TargetFramework.Net60)
{
SolutionTransforms.Add((solution, projectId) =>
{
return solution.AddAnalyzerConfigDocument(
DocumentId.CreateNewId(projectId),
"TargetFrameworkConfig.editorconfig",
SourceText.From("""
is_global = true
build_property.TargetFramework = net6.0
"""),
filePath: "/TargetFrameworkConfig.editorconfig");
});
ReferenceAssemblies = ReferenceAssemblies.Net.Net60;
}
else if (targetFramework == TargetFramework.Net80)
{
SolutionTransforms.Add((solution, projectId) =>
{
return solution.AddAnalyzerConfigDocument(
DocumentId.CreateNewId(projectId),
"TargetFrameworkConfig.editorconfig",
SourceText.From("""
is_global = true
build_property.TargetFramework = net8.0
"""),
filePath: "/TargetFrameworkConfig.editorconfig");
});
// There isn't a static .NET 8 yet
ReferenceAssemblies = new ReferenceAssemblies("net8.0", new PackageIdentity("Microsoft.NETCore.App.Ref", "8.0.0"), Path.Combine("ref", "net8.0"));
}
}

protected override CompilationOptions CreateCompilationOptions()
Expand Down
Loading

0 comments on commit e6ec280

Please sign in to comment.