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

Add support for .NET 8 to Lambda Annotations #1658

Merged
merged 8 commits into from
Feb 14, 2024
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
7 changes: 4 additions & 3 deletions Libraries/Amazon.Lambda.Annotations.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
"solution": {
"path": "Libraries.sln",
"projects": [
"src\\Amazon.Lambda.APIGatewayEvents\\Amazon.Lambda.APIGatewayEvents.csproj",
"src\\Amazon.Lambda.Annotations.SourceGenerator\\Amazon.Lambda.Annotations.SourceGenerator.csproj",
"src\\Amazon.Lambda.Annotations\\Amazon.Lambda.Annotations.csproj",
"src\\Amazon.Lambda.APIGatewayEvents\\Amazon.Lambda.APIGatewayEvents.csproj",
"src\\Amazon.Lambda.RuntimeSupport\\Amazon.Lambda.RuntimeSupport.csproj",
"src\\Amazon.Lambda.Core\\Amazon.Lambda.Core.csproj",
"src\\Amazon.Lambda.RuntimeSupport\\Amazon.Lambda.RuntimeSupport.csproj",
"src\\Amazon.Lambda.Serialization.SystemTextJson\\Amazon.Lambda.Serialization.SystemTextJson.csproj",
"test\\Amazon.Lambda.Annotations.SourceGenerators.Tests\\Amazon.Lambda.Annotations.SourceGenerators.Tests.csproj",
"test\\TestServerlessApp.IntegrationTests\\TestServerlessApp.IntegrationTests.csproj",
"test\\TestExecutableServerlessApp\\TestExecutableServerlessApp.csproj",
"test\\TestServerlessApp.IntegrationTests\\TestServerlessApp.IntegrationTests.csproj",
"test\\TestServerlessApp.NET8\\TestServerlessApp.NET8.csproj",
Copy link
Member Author

@ashovlin ashovlin Jan 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fyi: I was only adding line 14, my VS sorted the list.

"test\\TestServerlessApp\\TestServerlessApp.csproj"
]
}
Expand Down
19 changes: 13 additions & 6 deletions Libraries/Libraries.sln
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amazon.Lambda.LexV2Events",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest", "test\Amazon.Lambda.RuntimeSupport.Tests\CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest\CustomRuntimeAspNetCoreMinimalApiCustomSerializerTest.csproj", "{0BD83939-458C-4EF5-8663-7098AD1200F2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestExecutableServerlessApp", "test\TestExecutableServerlessApp\TestExecutableServerlessApp.csproj", "{DD378063-C54A-44C7-9A6F-32A6A1AE94B3}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestExecutableServerlessApp", "test\TestExecutableServerlessApp\TestExecutableServerlessApp.csproj", "{DD378063-C54A-44C7-9A6F-32A6A1AE94B3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestServerlessApp.NET8", "test\TestServerlessApp.NET8\TestServerlessApp.NET8.csproj", "{7300983D-8FCE-42EA-9B9E-B1C5347D15D8}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
test\EventsTests.Shared\EventsTests.Shared.projitems*{44e9d925-b61d-4234-97b7-61424c963ba6}*SharedItemsImports = 5
test\EventsTests.Shared\EventsTests.Shared.projitems*{a2cb78bb-e54f-48ca-bbfb-9553d27ef23d}*SharedItemsImports = 13
test\EventsTests.Shared\EventsTests.Shared.projitems*{c1bb30d2-3237-4cfc-ba93-627471148ec2}*SharedItemsImports = 5
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
Expand Down Expand Up @@ -359,6 +356,10 @@ Global
{DD378063-C54A-44C7-9A6F-32A6A1AE94B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD378063-C54A-44C7-9A6F-32A6A1AE94B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD378063-C54A-44C7-9A6F-32A6A1AE94B3}.Release|Any CPU.Build.0 = Release|Any CPU
{7300983D-8FCE-42EA-9B9E-B1C5347D15D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7300983D-8FCE-42EA-9B9E-B1C5347D15D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7300983D-8FCE-42EA-9B9E-B1C5347D15D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7300983D-8FCE-42EA-9B9E-B1C5347D15D8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -422,8 +423,14 @@ Global
{3C6AABF5-0372-41E0-874F-DF18ECCC7FB6} = {AAB54E74-20B1-42ED-BC3D-CE9F7BC7FD12}
{0BD83939-458C-4EF5-8663-7098AD1200F2} = {B5BD0336-7D08-492C-8489-42C987E29B39}
{DD378063-C54A-44C7-9A6F-32A6A1AE94B3} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69}
{7300983D-8FCE-42EA-9B9E-B1C5347D15D8} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {503678A4-B8D1-4486-8915-405A3E9CF0EB}
EndGlobalSection
GlobalSection(SharedMSBuildProjectFiles) = preSolution
test\EventsTests.Shared\EventsTests.Shared.projitems*{44e9d925-b61d-4234-97b7-61424c963ba6}*SharedItemsImports = 5
test\EventsTests.Shared\EventsTests.Shared.projitems*{a2cb78bb-e54f-48ca-bbfb-9553d27ef23d}*SharedItemsImports = 13
test\EventsTests.Shared\EventsTests.Shared.projitems*{c1bb30d2-3237-4cfc-ba93-627471148ec2}*SharedItemsImports = 5
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
; Shipped analyzer releases
; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

## Release 1.1.0
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this change, just moving the errors we added in 1.1.0 to the shipped file.

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
AWSLambda0111 | AWSLambdaCSharpGenerator | Error | If the GenerateMain global property is set to true but the project OutputType is not set to 'exe'
AWSLambda0112 | AWSLambdaCSharpGenerator | Error | An invalid runtime is selected in the LambdaGlobalProperties attribute
AWSLambda0113 | AWSLambdaCSharpGenerator | Error | The GenerateMain global property is set to true and the OutputType is set to 'exe', but no Lambda Function attributes are used
AWSLambda0114 | AWSLambdaCSharpGenerator | Error | The GenerateMain global property is set to true, but the project already contains a static Main method

## Release 1.0.0
Rule ID | Category | Severity | Notes
--------|----------|----------|-------
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,2 @@
; Unshipped analyzer release
; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
AWSLambda0111|AWSLambdaCSharpGenerator|Error|If the GenerateMain global property is set to true but the project OutputType is not set to 'exe'
AWSLambda0112|AWSLambdaCSharpGenerator|Error|An invalid runtime is selected in the LambdaGlobalProperties attribute
AWSLambda0113|AWSLambdaCSharpGenerator|Error|The GenerateMain global property is set to true and the OutputType is set to 'exe', but no Lambda Function attributes are used
AWSLambda0114|AWSLambdaCSharpGenerator|Error|The GenerateMain global property is set to true, but the project already contains a static Main method
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public static class DiagnosticDescriptors
public static readonly DiagnosticDescriptor InvalidRuntimeSelection = new DiagnosticDescriptor(id: "AWSLambda0112",
title: "Invalid runtime selection",
messageFormat: "The runtime selected in the Amazon.Lambda.Annotations.LambdaGlobalPropertiesAttribute is not a supported value. " +
$"The valid values are: {string.Join(", ", Generator._allowdRuntimeValues.ToArray())}",
$"The valid values are: {string.Join(", ", Generator._allowedRuntimeValues.ToArray())}",
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
using System;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using Amazon.Lambda.Annotations.SourceGenerator.Diagnostics;
using Amazon.Lambda.Annotations.SourceGenerator.Diagnostics;
using Amazon.Lambda.Annotations.SourceGenerator.Extensions;
using Amazon.Lambda.Annotations.SourceGenerator.FileIO;
using Amazon.Lambda.Annotations.SourceGenerator.Models;
Expand All @@ -13,23 +8,36 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Amazon.Lambda.Annotations.SourceGenerator
{
using System.Collections.Generic;

[Generator]
public class Generator : ISourceGenerator
{
private const string DEFAULT_LAMBDA_SERIALIZER = "Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer";
private readonly IFileManager _fileManager = new FileManager();
private readonly IDirectoryManager _directoryManager = new DirectoryManager();

internal static readonly List<string> _allowdRuntimeValues = new List<string>(2)
/// <summary>
/// Maps .NET TargetFramework values to the corresponding Lambda runtime CloudFormation value
/// </summary>
internal static readonly Dictionary<string, string> _targetFrameworksToRuntimes = new Dictionary<string, string>(2)
{
{ "net6.0", "dotnet6" },
{ "net8.0", "dotnet8" }
};

internal static readonly List<string> _allowedRuntimeValues = new List<string>(4)
{
"dotnet6",
"provided.al2",
"provided.al2023"
"provided.al2023",
"dotnet8"
};

// Only allow alphanumeric characters
Expand Down Expand Up @@ -102,13 +110,23 @@ 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))
{
if (_targetFrameworksToRuntimes.ContainsKey(parsedRuntime))
{
defaultRuntime = _targetFrameworksToRuntimes[parsedRuntime];
}
}

// The runtime specified in the global property has precedence over the one we parsed from the project file
if (globalPropertiesAttribute != null)
{
var generateMain = globalPropertiesAttribute.NamedArguments.FirstOrDefault(kvp => kvp.Key == "GenerateMain").Value;
var runtimeAttributeValue = globalPropertiesAttribute.NamedArguments.FirstOrDefault(kvp => kvp.Key == "Runtime").Value;
var runtime = runtimeAttributeValue.Value == null ? defaultRuntime : runtimeAttributeValue.Value.ToString();

if (!_allowdRuntimeValues.Contains(runtime))
if (!_allowedRuntimeValues.Contains(runtime))
{
diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.InvalidRuntimeSelection, Location.None));
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
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 @@ -9,6 +11,14 @@ 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;
ashovlin marked this conversation as resolved.
Show resolved Hide resolved

/// <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 @@ -42,5 +52,95 @@ 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 ex)
{
// 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 precendece over <TargetFrameworks> (plural)
ashovlin marked this conversation as resolved.
Show resolved Hide resolved
if (parsedJson["Properties"]["TargetFramework"] != null)
ashovlin marked this conversation as resolved.
Show resolved Hide resolved
{
var targetFramework = parsedJson["Properties"]["TargetFramework"].ToString();

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

// Otherwise fallback to <TargetFrameworks> (plural)
if (parsedJson["Properties"]["TargetFrameworks"] != null)
ashovlin marked this conversation as resolved.
Show resolved Hide resolved
{
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;
}
}
}
2 changes: 1 addition & 1 deletion Libraries/src/Amazon.Lambda.Annotations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ To auto-generate the `static Main` method, first ensure the `OutputType` in your
</PropertyGroup>
```

Once the output type is set to executable, add the `LambdaGlobalProperties` assembly attribute and set the `GenerateMain` property to true. You can also configure the `Runtime` in the generated CloudFormation template.
Once the output type is set to executable, add the `LambdaGlobalProperties` assembly attribute and set the `GenerateMain` property to true. If `Runtime` is not specified in the global attribute, Lambda Annotations will attempt to determine it from your project file. You can also configure the `Runtime` in the generated CloudFormation template.

To allow for multiple Lambda functions in the same executable an Environment Variable is used to determine which handler is executed. When using the `GenerateMain` attribute, ensure you also set the `ANNOTATIONS_HANDLER` environment variable on the deployed Lambda function.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@
<Content Remove="..\TestServerlessApp\obj\**" />
<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="..\TestExecutableServerlessApp\**">
<Link>TestExecutableServerlessApp\%(RecursiveDir)/%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
Expand Down Expand Up @@ -137,6 +146,7 @@
<None Remove="Snapshots\ServerlessTemplates\customizeResponse.template" />
<None Remove="Snapshots\ServerlessTemplates\dynamicexample.template" />
<None Remove="Snapshots\ServerlessTemplates\intrinsicexample.template" />
<None Remove="Snapshots\ServerlessTemplates\net8.template" />
<None Remove="Snapshots\ServerlessTemplates\nullreferenceexample.template" />
<None Remove="Snapshots\ServerlessTemplates\sourcegeneratorserializationexample.template" />
<None Remove="Snapshots\ServerlessTemplates\subnamespace.template" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Amazon.Lambda.Core;

namespace TestServerlessApp.NET8
{
public class Functions_ToUpper_Generated
{
private readonly Functions functions;
private readonly Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer serializer;

public Functions_ToUpper_Generated()
{
SetExecutionEnvironment();
functions = new Functions();
serializer = new Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer();
}

public string ToUpper(string text)
{
return functions.ToUpper(text);
}

private static void SetExecutionEnvironment()
{
const string envName = "AWS_EXECUTION_ENV";

var envValue = new StringBuilder();

// If there is an existing execution environment variable add the annotations package as a suffix.
if(!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(envName)))
{
envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_");
}

envValue.Append("amazon-lambda-annotations_1.1.0.0");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we just increment the version to the one we intend to release this PR in? (and do it for all the other snaphsot files)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the churn we've had with .NET 8, I didn't want to tie this to a version yet so would just do the separate versioning PR.


Environment.SetEnvironmentVariable(envName, envValue.ToString());
}
}
}
Loading
Loading