Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions sdk/Sdk.Generators/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ internal static class Languages

internal static class BuildProperties
{
internal const string MSBuildRootNamespace = "build_property.RootNamespace";
internal const string GeneratedCodeNamespace = "build_property.FunctionsGeneratedCodeNamespace";
internal const string EnableSourceGen = "build_property.FunctionsEnableMetadataSourceGen";
internal const string EnablePlaceholder = "build_property.FunctionsEnableExecutorSourceGen";
internal const string AutoRegisterGeneratedFunctionsExecutor = "build_property.FunctionsAutoRegisterGeneratedFunctionsExecutor";
Expand Down
10 changes: 6 additions & 4 deletions sdk/Sdk.Generators/ExtensionStartupRunnerGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public void Execute(GeneratorExecutionContext context)
return;
}

var source = GenerateExtensionStartupRunner(extensionStartupTypeNames);
var source = GenerateExtensionStartupRunner(context, extensionStartupTypeNames);
var sourceText = SourceText.From(source, encoding: Encoding.UTF8);

// Add the source code to the compilation
Expand All @@ -81,18 +81,20 @@ public void Execute(GeneratorExecutionContext context)
/// </summary>
/// <param name="extensionStartupTypeNames">The types to add to the configuration/bootstrapping process.</param>
/// <returns>The generated source code.</returns>
internal string GenerateExtensionStartupRunner(IList<string> extensionStartupTypeNames)
internal string GenerateExtensionStartupRunner(GeneratorExecutionContext context, IList<string> extensionStartupTypeNames)
{
string startupCodeExecutor = GenerateStartupCodeExecutorClass(extensionStartupTypeNames);
var namespaceValue = FunctionsUtil.GetNamespaceForGeneratedCode(context);

return $$"""
// <auto-generated/>
using System;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Core;

[assembly: WorkerExtensionStartupCodeExecutorInfo(typeof(Microsoft.Azure.Functions.Worker.WorkerExtensionStartupCodeExecutor))]
[assembly: WorkerExtensionStartupCodeExecutorInfo(typeof({{namespaceValue}}.WorkerExtensionStartupCodeExecutor))]

namespace Microsoft.Azure.Functions.Worker
namespace {{namespaceValue}}
{
internal class WorkerExtensionStartupCodeExecutor : WorkerExtensionStartup
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;

namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
{
public partial class FunctionExecutorGenerator
{
internal static class Emitter
{
internal static string Emit(IEnumerable<ExecutableFunction> functions, bool includeAutoRegistrationCode, CancellationToken cancellationToken)
internal static string Emit(GeneratorExecutionContext context, IEnumerable<ExecutableFunction> functions, bool includeAutoRegistrationCode)
{

string result = $$"""
Expand All @@ -23,9 +23,10 @@ internal static string Emit(IEnumerable<ExecutableFunction> functions, bool incl
using System.Collections.Generic;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Context.Features;
using Microsoft.Azure.Functions.Worker.Invocation;
namespace Microsoft.Azure.Functions.Worker
namespace {{FunctionsUtil.GetNamespaceForGeneratedCode(context)}}
{
internal class DirectFunctionExecutor : IFunctionExecutor
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public void Execute(GeneratorExecutionContext context)

var shouldIncludeAutoGeneratedAttributes = ShouldIncludeAutoGeneratedAttributes(context);

var text = Emitter.Emit(functions, shouldIncludeAutoGeneratedAttributes, context.CancellationToken);
var text = Emitter.Emit(context, functions, shouldIncludeAutoGeneratedAttributes);
context.AddSource(Constants.FileNames.GeneratedFunctionExecutor, SourceText.From(text, Encoding.UTF8));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Text;
using System.Text.Json;
using System.Threading;
using Microsoft.CodeAnalysis;

namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
{
Expand All @@ -18,9 +19,9 @@ internal sealed class Emitter
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase
};

public string Emit(IReadOnlyList<GeneratorFunctionMetadata> funcMetadata, bool includeAutoRegistrationCode, CancellationToken cancellationToken)
public string Emit(GeneratorExecutionContext context, IReadOnlyList<GeneratorFunctionMetadata> funcMetadata, bool includeAutoRegistrationCode)
{
string functionMetadataInfo = AddFunctionMetadataInfo(funcMetadata, cancellationToken);
string functionMetadataInfo = AddFunctionMetadataInfo(funcMetadata, context.CancellationToken);

return $$"""
// <auto-generated/>
Expand All @@ -29,11 +30,12 @@ public string Emit(IReadOnlyList<GeneratorFunctionMetadata> funcMetadata, bool i
using System.Collections.Immutable;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Microsoft.Azure.Functions.Worker
namespace {{FunctionsUtil.GetNamespaceForGeneratedCode(context)}}
{
public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
Expand Down Expand Up @@ -49,7 +48,7 @@ public void Execute(GeneratorExecutionContext context)
Emitter e = new();
var shouldIncludeAutoGeneratedAttributes = ShouldIncludeAutoGeneratedAttributes(context);

string result = e.Emit(functionMetadataInfo, shouldIncludeAutoGeneratedAttributes, context.CancellationToken);
string result = e.Emit(context, functionMetadataInfo, shouldIncludeAutoGeneratedAttributes);

context.AddSource(Constants.FileNames.GeneratedFunctionMetadata, SourceText.From(result, Encoding.UTF8));
}
Expand All @@ -63,7 +62,7 @@ public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new FunctionMethodSyntaxReceiver());
}

private static bool ShouldIncludeAutoGeneratedAttributes(GeneratorExecutionContext context)
{
if (!context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(
Expand All @@ -77,15 +76,13 @@ private static bool ShouldIncludeAutoGeneratedAttributes(GeneratorExecutionConte

private IEnumerable<IMethodSymbol> GetEntryAssemblyFunctions(List<MethodDeclarationSyntax> candidateMethods, GeneratorExecutionContext context)
{
IList<IMethodSymbol>? entryAssemblyFuncs = new List<IMethodSymbol>();

foreach (MethodDeclarationSyntax method in candidateMethods)
{
var model = context.Compilation.GetSemanticModel(method.SyntaxTree);

if (FunctionsUtil.IsValidFunctionMethod(context, context.Compilation, model, method))
if (FunctionsUtil.IsValidFunctionMethod(context, context.Compilation, model, method))
{
IMethodSymbol? methodSymbol = (IMethodSymbol) model.GetDeclaredSymbol(method)!;
IMethodSymbol? methodSymbol = (IMethodSymbol)model.GetDeclaredSymbol(method)!;
yield return methodSymbol;
}
}
Expand Down
27 changes: 21 additions & 6 deletions sdk/Sdk.Generators/FunctionsUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Data;
using System;

namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
{
Expand All @@ -18,7 +15,7 @@ internal static class FunctionsUtil
internal static bool IsValidFunctionMethod(
GeneratorExecutionContext context,
Compilation compilation,
SemanticModel model,
SemanticModel model,
MethodDeclarationSyntax method)
{
var methodSymbol = model.GetDeclaredSymbol(method);
Expand Down Expand Up @@ -51,7 +48,7 @@ internal static bool IsFunctionSymbol(ISymbol symbol, Compilation compilation)
return false;
}

internal static bool TryGetFunctionName(ISymbol symbol, Compilation compilation, out string? functionName)
internal static bool TryGetFunctionName(ISymbol symbol, Compilation compilation, out string? functionName)
{
functionName = null;

Expand All @@ -60,7 +57,7 @@ internal static bool TryGetFunctionName(ISymbol symbol, Compilation compilation,

if (functionAttribute is not null)
{
functionName = (string) functionAttribute.ConstructorArguments.First().Value!;
functionName = (string)functionAttribute.ConstructorArguments.First().Value!;
return true;
}

Expand All @@ -77,5 +74,23 @@ internal static string GetFullyQualifiedMethodName(IMethodSymbol method)
var fullyQualifiedClassName = method.ContainingSymbol.ToDisplayString();
return $"{fullyQualifiedClassName}.{method.Name}";
}

/// <summary>
/// Gets the namespace value to be used for the auto generated types.
/// </summary>
internal static string GetNamespaceForGeneratedCode(GeneratorExecutionContext context)
{
// If csproj has the msbuild property specified, use it's value.
if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(Constants.BuildProperties.GeneratedCodeNamespace, out var namespaceValue)
&& !string.IsNullOrWhiteSpace(namespaceValue))
{
return namespaceValue;
}

// Get the "RootNamespace" msbuild property value.(This gets populated in Microsoft.NET.Sdk.props and can be overridden by user in their function app)
context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(Constants.BuildProperties.MSBuildRootNamespace, out var rootNamespaceValue);

return rootNamespaceValue!;
}
}
}
1 change: 1 addition & 0 deletions sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and
<CompilerVisibleProperty Include="FunctionsEnableExecutorSourceGen" />
<CompilerVisibleProperty Include="FunctionsAutoRegisterGeneratedFunctionsExecutor" />
<CompilerVisibleProperty Include="FunctionsAutoRegisterGeneratedMetadataProvider" />
<CompilerVisibleProperty Include="FunctionsGeneratedCodeNamespace" />
</ItemGroup>
<!--
***********************************************************************************************
Expand Down
2 changes: 2 additions & 0 deletions sdk/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@
- Parse named arguments by type (#1877)
- Refactor source gen to walk dependent assemblies (#1896)
- Add diagnostic descriptor logs for parsing binding arguments in source gen (#1882)
- Use project root namespace for generated types (#1158)

68 changes: 14 additions & 54 deletions test/Sdk.Generator.Tests/ExtensionStartupRunnerGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ public async Task StartupExecutorCodeGetsGenerated()
string expectedOutput = """
// <auto-generated/>
using System;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Core;

[assembly: WorkerExtensionStartupCodeExecutorInfo(typeof(Microsoft.Azure.Functions.Worker.WorkerExtensionStartupCodeExecutor))]
[assembly: WorkerExtensionStartupCodeExecutorInfo(typeof(TestProject.WorkerExtensionStartupCodeExecutor))]

namespace Microsoft.Azure.Functions.Worker
namespace TestProject
{
internal class WorkerExtensionStartupCodeExecutor : WorkerExtensionStartup
{
Expand All @@ -62,55 +63,6 @@ await TestHelpers.RunTestAsync<ExtensionStartupRunnerGenerator>(
expectedOutput);
}

[Fact]
public void StartupExecutorCodeWithMultipleStartupsGetsGenerated()
{
// Source generation is based on referenced assembly.
var typeNames = new[]
{
"Microsoft.Azure.Functions.Tests.WorkerExtensionsSample.SampleExtensionStartup",
"Microsoft.Azure.Functions.Tests.WorkerExtensionsSample.SampleExtensionStartup2",
};

string expectedOutput = """
// <auto-generated/>
using System;
using Microsoft.Azure.Functions.Worker.Core;

[assembly: WorkerExtensionStartupCodeExecutorInfo(typeof(Microsoft.Azure.Functions.Worker.WorkerExtensionStartupCodeExecutor))]

namespace Microsoft.Azure.Functions.Worker
{
internal class WorkerExtensionStartupCodeExecutor : WorkerExtensionStartup
{
public override void Configure(IFunctionsWorkerApplicationBuilder applicationBuilder)
{
try
{
new Microsoft.Azure.Functions.Tests.WorkerExtensionsSample.SampleExtensionStartup().Configure(applicationBuilder);
}
catch (Exception ex)
{
Console.Error.WriteLine("Error calling Configure on Microsoft.Azure.Functions.Tests.WorkerExtensionsSample.SampleExtensionStartup instance."+ex.ToString());
}
try
{
new Microsoft.Azure.Functions.Tests.WorkerExtensionsSample.SampleExtensionStartup2().Configure(applicationBuilder);
}
catch (Exception ex)
{
Console.Error.WriteLine("Error calling Configure on Microsoft.Azure.Functions.Tests.WorkerExtensionsSample.SampleExtensionStartup2 instance."+ex.ToString());
}
}
}
}
""";

var source = new ExtensionStartupRunnerGenerator().GenerateExtensionStartupRunner(typeNames);

Assert.Equal(expectedOutput, source);
}

[Fact]
public async Task StartupExecutorCodeDoesNotGetsGeneratedWheNoExtensionAssembliesAreReferenced()
{
Expand Down Expand Up @@ -144,11 +96,12 @@ public async Task DiagnosticErrorsAreReportedWhenStartupTypeIsInvalid()
string expectedOutput = """
// <auto-generated/>
using System;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Core;

[assembly: WorkerExtensionStartupCodeExecutorInfo(typeof(Microsoft.Azure.Functions.Worker.WorkerExtensionStartupCodeExecutor))]
[assembly: WorkerExtensionStartupCodeExecutorInfo(typeof(MyCompany.MyProject.MyApp.WorkerExtensionStartupCodeExecutor))]

namespace Microsoft.Azure.Functions.Worker
namespace MyCompany.MyProject.MyApp
{
internal class WorkerExtensionStartupCodeExecutor : WorkerExtensionStartup
{
Expand Down Expand Up @@ -177,12 +130,19 @@ public override void Configure(IFunctionsWorkerApplicationBuilder applicationBui
.WithArguments("Worker.Extensions.Sample_IncorrectImplementation.SampleIncorrectExtensionStartup")
};

// override the namespace value for generated types using msbuild property.
var buildPropertiesDict = new Dictionary<string, string>()
{
{ Constants.BuildProperties.GeneratedCodeNamespace, "MyCompany.MyProject.MyApp"}
};

await TestHelpers.RunTestAsync<ExtensionStartupRunnerGenerator>(
referencedExtensionAssemblies,
InputCode,
expectedGeneratedFileName,
expectedOutput,
expectedDiagnosticResults);
expectedDiagnosticResults,
buildPropertiesDict);
}
}
}
Loading