Skip to content

Commit

Permalink
Merge branch 'feature/annotations-executable' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
normj committed Nov 15, 2023
2 parents b90a012 + 146ce67 commit 20b137e
Show file tree
Hide file tree
Showing 109 changed files with 4,469 additions and 629 deletions.
68 changes: 68 additions & 0 deletions Docs/lambda-annotations-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,69 @@ public class LambdaFunctions
}
```

## Auto Generate Main

A `LambdaGlobalProperties` attribute is available to set global settings that the annotations framework uses when generating code at compile time. This simplifies the programming model when using custom runtimes or native ahead of time (AOT) compilation. It removes the need to manually bootstrap the Lambda runtime.

To auto-generate the `static Main` method, first ensure the `OutputType` in your `csproj` file is set to `exe`.
```xml
<PropertyGroup>
<!--Removed for brevity..-->
<OutputType>exe</OutputType>
</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.

```c#
[assembly: LambdaGlobalProperties(GenerateMain = true, Runtime = "provided.al2")]
```

### Behind The Scenes

Assuming the below Lambda function handler:

```c#
public class Greeter
{
[LambdaFunction(ResourceName = "GreeterSayHello", MemorySize = 1024, PackageType = LambdaPackageType.Image)]
[HttpApi(LambdaHttpMethod.Get, "/Greeter/SayHello", Version = HttpApiVersion.V1)]
public void SayHello([FromQuery(Name = "names")]IEnumerable<string> firstNames, APIGatewayProxyRequest request, ILambdaContext context)
{
context.Logger.LogLine($"Request {JsonSerializer.Serialize(request)}");

if (firstNames == null)
{
return;
}

foreach (var firstName in firstNames)
{
Console.WriteLine($"Hello {firstName}");
}
}
}
```

The generated `static Main` method would look like the below. 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 resource.

The auto-generated CloudFormation template will include this as a default.

```c#
public class GeneratedProgram
{
private static async Task Main(string[] args)
{
switch (Environment.GetEnvironmentVariable("ANNOTATIONS_HANDLER"))
{
case "ToUpper":
Func<string, string> toupper_handler = new TestServerlessApp.Sub1.Functions_ToUpper_Generated().ToUpper;
await Amazon.Lambda.RuntimeSupport.LambdaBootstrapBuilder.Create(toupper_handler, new Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer()).Build().RunAsync();
break;
}
}
}
```

## Lambda .NET Attributes

Expand Down Expand Up @@ -368,6 +430,12 @@ Here is a preliminary list of .NET attributes that will tell the source generato
* Map method parameter to HTTP request body. If parameter is a complex type then request body will be assumed to be JSON and deserialized into the type.
* FromServices
* Map method parameter to registered service in IServiceProvider

### Global Attributes
* GenerateMain
* Generates a `static Program` class and a `static Main` method that bootstraps the Lambda runtime. Simplifies the programming model when building on a custom runtime or using native ahead of time (AOT) compilation.
* Runtime
* Set the runtime in the generated CloudFormation template. Set to either `dotnet6` or `provided.al2`

Here is a list of features that are supported/planned in no particular priority order. The list will grow as we get deeper into implementation.
- [x] LambdaFunction attribute triggers source generator and syncs with the CloudFormation template
Expand Down
4 changes: 3 additions & 1 deletion Libraries/Amazon.Lambda.Annotations.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
"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.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\\TestServerlessApp.csproj"
]
}
Expand Down
7 changes: 7 additions & 0 deletions Libraries/Libraries.sln
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ 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}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
test\EventsTests.Shared\EventsTests.Shared.projitems*{44e9d925-b61d-4234-97b7-61424c963ba6}*SharedItemsImports = 5
Expand Down Expand Up @@ -353,6 +355,10 @@ Global
{0BD83939-458C-4EF5-8663-7098AD1200F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0BD83939-458C-4EF5-8663-7098AD1200F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0BD83939-458C-4EF5-8663-7098AD1200F2}.Release|Any CPU.Build.0 = Release|Any CPU
{DD378063-C54A-44C7-9A6F-32A6A1AE94B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -415,6 +421,7 @@ Global
{BF85932E-2DFF-41CD-8090-A672468B8FBB} = {AAB54E74-20B1-42ED-BC3D-CE9F7BC7FD12}
{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}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {503678A4-B8D1-4486-8915-405A3E9CF0EB}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@
<Generator>TextTemplatingFilePreprocessor</Generator>
<LastGenOutput>NoEventMethodBody.cs</LastGenOutput>
</None>
<None Update="Templates\ExecutableAssembly.tt">
<Generator>TextTemplatingFilePreprocessor</Generator>
<LastGenOutput>ExecutableAssembly.cs</LastGenOutput>
</None>
</ItemGroup>

<ItemGroup>
Expand Down Expand Up @@ -84,6 +88,11 @@
<AutoGen>True</AutoGen>
<DependentUpon>NoEventMethodBody.tt</DependentUpon>
</Compile>
<Compile Update="Templates\ExecutableAssembly.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ExecutableAssembly.tt</DependentUpon>
</Compile>
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@

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 @@ -87,5 +87,35 @@ public static class DiagnosticDescriptors
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor SetOutputTypeExecutable = new DiagnosticDescriptor(id: "AWSLambda0111",
title: "Output Type is not an executable",
messageFormat: "AssemblyAttribute Amazon.Lambda.Annotations.LambdaGlobalPropertiesAttribute is configured to generate a static main method " +
"but the assembly itself is not configured to output an executable. Set the 'OutputType' property in the .csproj file to be 'exe'.",
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

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())}",
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor ExecutableWithNoFunctions = new DiagnosticDescriptor(id: "AWSLambda0113",
title: "Executable output with no LambdaFunction annotations",
messageFormat: "Your project is configured to output an executable and generate a static Main method, but you have not configured any methods with the 'LambdaFunction' attribute",
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor MainMethodExists = new DiagnosticDescriptor(id: "AWSLambda0114",
title: "static Main method exists",
messageFormat: "Failed to generate Main method for LambdaGenerateMainAttribute because project already contains Main method. Existing Main methods must be removed when using LambdaGenerateMainAttribute attribute.",
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);
}
}
Loading

0 comments on commit 20b137e

Please sign in to comment.