Adding support for executing functions from referenced assemblies, to the source generated executor implementation. (#2089)
* Adding support for executing functions from referenced assemblies, to the source generated executor implementaion. * Release note and version bump * Picking only public functions as valid functions for metadata generation and executor. * Minor cleanup * Get full type name including assembly identity for all types. * Minor cleanup * Switching initializing the defaultExecutor to a Lazy approach. * A bit more cleanup. * PR feedback fixes * PR feedback fixes
This commit is contained in:
Родитель
0733bbdcb1
Коммит
207c19533a
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
|
||||
{
|
||||
internal static class IMethodSymbolExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines the visibility of an azure function method.
|
||||
/// The visibility is determined by the following rules:
|
||||
/// 1. If the method is public, and all containing types are public, return Public
|
||||
/// 2. If the method is public, but one or more containing types are not public, return PublicButContainingTypeNotVisible
|
||||
/// 3. If the method is not public, return NotPublic
|
||||
/// </summary>
|
||||
/// <param name="methodSymbol">The <see cref="IMethodSymbol"/> instance representing an azure function method.</param>
|
||||
/// <returns><see cref="FunctionMethodVisibility"/></returns>
|
||||
internal static FunctionMethodVisibility GetVisibility(this IMethodSymbol methodSymbol)
|
||||
{
|
||||
// Check if the symbol itself is public
|
||||
if (methodSymbol.DeclaredAccessibility == Accessibility.Public)
|
||||
{
|
||||
// Check if any containing type is not public
|
||||
INamedTypeSymbol containingType = methodSymbol.ContainingType;
|
||||
while (containingType != null)
|
||||
{
|
||||
if (containingType.DeclaredAccessibility != Accessibility.Public)
|
||||
{
|
||||
return FunctionMethodVisibility.PublicButContainingTypeNotVisible;
|
||||
}
|
||||
containingType = containingType.ContainingType;
|
||||
}
|
||||
|
||||
// If both the symbol and all containing types are public, return PublicAndVisible
|
||||
return FunctionMethodVisibility.Public;
|
||||
}
|
||||
|
||||
// If the symbol itself is not public, return NotPublic
|
||||
return FunctionMethodVisibility.NotPublic;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,8 +13,12 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
|
|||
{
|
||||
internal static class Emitter
|
||||
{
|
||||
internal static string Emit(GeneratorExecutionContext context, IEnumerable<ExecutableFunction> functions, bool includeAutoRegistrationCode)
|
||||
private const string WorkerCoreAssemblyName = "Microsoft.Azure.Functions.Worker.Core";
|
||||
|
||||
internal static string Emit(GeneratorExecutionContext context, IEnumerable<ExecutableFunction> executableFunctions, bool includeAutoRegistrationCode)
|
||||
{
|
||||
var functions = executableFunctions.ToList();
|
||||
var defaultExecutorNeeded = functions.Any(f => f.Visibility == FunctionMethodVisibility.PublicButContainingTypeNotVisible);
|
||||
|
||||
string result = $$"""
|
||||
// <auto-generated/>
|
||||
|
@ -31,7 +35,7 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
|
|||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
internal class DirectFunctionExecutor : IFunctionExecutor
|
||||
{
|
||||
private readonly IFunctionActivator _functionActivator;
|
||||
private readonly IFunctionActivator _functionActivator;{{(defaultExecutorNeeded ? $"{Environment.NewLine} private Lazy<IFunctionExecutor> _defaultExecutor;" : string.Empty)}}
|
||||
{{GetTypesDictionary(functions)}}
|
||||
public DirectFunctionExecutor(IFunctionActivator functionActivator)
|
||||
{
|
||||
|
@ -41,8 +45,8 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
|
|||
/// <inheritdoc/>
|
||||
public async ValueTask ExecuteAsync(FunctionContext context)
|
||||
{
|
||||
{{GetMethodBody(functions)}}
|
||||
}
|
||||
{{GetMethodBody(functions, defaultExecutorNeeded)}}
|
||||
}{{(defaultExecutorNeeded ? $"{Environment.NewLine}{EmitCreateDefaultExecutorMethod(context)}" : string.Empty)}}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -67,20 +71,40 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
|
|||
return result;
|
||||
}
|
||||
|
||||
private static string EmitCreateDefaultExecutorMethod(GeneratorExecutionContext context)
|
||||
{
|
||||
var workerCoreAssembly = context.Compilation.SourceModule.ReferencedAssemblySymbols.Single(a => a.Name == WorkerCoreAssemblyName);
|
||||
var assemblyIdentity = workerCoreAssembly.Identity;
|
||||
|
||||
return $$"""
|
||||
|
||||
private IFunctionExecutor CreateDefaultExecutorInstance(FunctionContext context)
|
||||
{
|
||||
var defaultExecutorFullName = "Microsoft.Azure.Functions.Worker.Invocation.DefaultFunctionExecutor, {{assemblyIdentity}}";
|
||||
var defaultExecutorType = Type.GetType(defaultExecutorFullName);
|
||||
|
||||
return ActivatorUtilities.CreateInstance(context.InstanceServices, defaultExecutorType) as IFunctionExecutor;
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
private static string GetTypesDictionary(IEnumerable<ExecutableFunction> functions)
|
||||
{
|
||||
var classNames = functions.Where(f => !f.IsStatic).Select(f => f.ParentFunctionClassName).Distinct();
|
||||
if (!classNames.Any())
|
||||
{
|
||||
return """
|
||||
// Build a dictionary of type names and its full qualified names (including assembly identity)
|
||||
var typesDict = functions
|
||||
.Where(f => !f.IsStatic)
|
||||
.GroupBy(f => f.ParentFunctionClassName)
|
||||
.ToDictionary(k => k.First().ParentFunctionClassName, v => v.First().AssemblyIdentity);
|
||||
|
||||
""";
|
||||
if (typesDict.Count == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return $$"""
|
||||
private readonly Dictionary<string, Type> types = new()
|
||||
{
|
||||
{{string.Join($",{Environment.NewLine} ", classNames.Select(c => $$""" { "{{c}}", Type.GetType("{{c}}")! }"""))}}
|
||||
{{string.Join($",{Environment.NewLine} ", typesDict.Select(c => $$""" { "{{c.Key}}", Type.GetType("{{c.Key}}, {{c.Value}}")! }"""))}}
|
||||
};
|
||||
|
||||
""";
|
||||
|
@ -114,64 +138,84 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
|
|||
return "";
|
||||
}
|
||||
|
||||
private static string GetMethodBody(IEnumerable<ExecutableFunction> functions)
|
||||
private static string GetMethodBody(IEnumerable<ExecutableFunction> functions, bool anyDefaultExecutor)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(
|
||||
"""
|
||||
$$"""
|
||||
var inputBindingFeature = context.Features.Get<IFunctionInputBindingFeature>()!;
|
||||
var inputBindingResult = await inputBindingFeature.BindFunctionInputAsync(context)!;
|
||||
var inputArguments = inputBindingResult.Values;
|
||||
|
||||
{{(anyDefaultExecutor ? $" _defaultExecutor = new Lazy<IFunctionExecutor>(() => CreateDefaultExecutorInstance(context));{Environment.NewLine}" : string.Empty)}}
|
||||
""");
|
||||
|
||||
bool first = true;
|
||||
|
||||
foreach (ExecutableFunction function in functions)
|
||||
{
|
||||
var fast = function.Visibility == FunctionMethodVisibility.Public;
|
||||
sb.Append($$"""
|
||||
|
||||
{{(first ? string.Empty : "else ")}}if (string.Equals(context.FunctionDefinition.EntryPoint, "{{function.EntryPoint}}", StringComparison.Ordinal))
|
||||
{
|
||||
{{(fast ? EmitFastPath(function) : EmitSlowPath())}}
|
||||
}
|
||||
""");
|
||||
|
||||
first = false;
|
||||
int functionParamCounter = 0;
|
||||
var functionParamList = new List<string>();
|
||||
foreach (var argumentTypeName in function.ParameterTypeNames)
|
||||
{
|
||||
functionParamList.Add($"({argumentTypeName})inputArguments[{functionParamCounter++}]");
|
||||
}
|
||||
var methodParamsStr = string.Join(", ", functionParamList);
|
||||
|
||||
if (!function.IsStatic)
|
||||
{
|
||||
sb.Append($$"""
|
||||
|
||||
var instanceType = types["{{function.ParentFunctionClassName}}"];
|
||||
var i = _functionActivator.CreateInstance(instanceType, context) as {{function.ParentFunctionFullyQualifiedClassName}};
|
||||
""");
|
||||
}
|
||||
|
||||
sb.Append(@"
|
||||
");
|
||||
|
||||
if (function.IsReturnValueAssignable)
|
||||
{
|
||||
sb.Append(@$"context.GetInvocationResult().Value = ");
|
||||
}
|
||||
if (function.ShouldAwait)
|
||||
{
|
||||
sb.Append("await ");
|
||||
}
|
||||
|
||||
sb.Append(function.IsStatic
|
||||
? @$"{function.ParentFunctionFullyQualifiedClassName}.{function.MethodName}({methodParamsStr});
|
||||
}}"
|
||||
: $@"i.{function.MethodName}({methodParamsStr});
|
||||
}}");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string EmitFastPath(ExecutableFunction function)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
int functionParamCounter = 0;
|
||||
var functionParamList = new List<string>();
|
||||
foreach (var argumentTypeName in function.ParameterTypeNames)
|
||||
{
|
||||
functionParamList.Add($"({argumentTypeName})inputArguments[{functionParamCounter++}]");
|
||||
}
|
||||
var methodParamsStr = string.Join(", ", functionParamList);
|
||||
|
||||
if (!function.IsStatic)
|
||||
{
|
||||
sb.Append($$"""
|
||||
var instanceType = types["{{function.ParentFunctionClassName}}"];
|
||||
var i = _functionActivator.CreateInstance(instanceType, context) as {{function.ParentFunctionFullyQualifiedClassName}};
|
||||
""");
|
||||
}
|
||||
|
||||
if (!function.IsStatic)
|
||||
{
|
||||
sb.Append(@"
|
||||
");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(" ");
|
||||
}
|
||||
|
||||
if (function.IsReturnValueAssignable)
|
||||
{
|
||||
sb.Append("context.GetInvocationResult().Value = ");
|
||||
}
|
||||
if (function.ShouldAwait)
|
||||
{
|
||||
sb.Append("await ");
|
||||
}
|
||||
|
||||
sb.Append(function.IsStatic
|
||||
? $"{function.ParentFunctionFullyQualifiedClassName}.{function.MethodName}({methodParamsStr});"
|
||||
: $"i.{function.MethodName}({methodParamsStr});");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string EmitSlowPath()
|
||||
{
|
||||
return
|
||||
" await _defaultExecutor.Value.ExecuteAsync(context);";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,5 +52,16 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
|
|||
/// A collection of fully qualified type names of the parameters of the function.
|
||||
/// </summary>
|
||||
internal IEnumerable<string> ParameterTypeNames { set; get; } = Enumerable.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Get a value indicating the visibility of the executable function.
|
||||
/// </summary>
|
||||
internal FunctionMethodVisibility Visibility { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assembly identity of the function.
|
||||
/// ex: FooAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=9475d07f10cb09df
|
||||
/// </summary>
|
||||
internal string AssemblyIdentity { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
|
||||
{
|
||||
|
@ -23,48 +22,38 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
|
|||
|
||||
private Compilation Compilation => _context.Compilation;
|
||||
|
||||
internal ICollection<ExecutableFunction> GetFunctions(List<MethodDeclarationSyntax> methods)
|
||||
internal ICollection<ExecutableFunction> GetFunctions(IEnumerable<IMethodSymbol> methods)
|
||||
{
|
||||
var functionList = new List<ExecutableFunction>();
|
||||
|
||||
foreach (MethodDeclarationSyntax method in methods)
|
||||
foreach (IMethodSymbol method in methods.Where(m=>m.DeclaredAccessibility == Accessibility.Public))
|
||||
{
|
||||
_context.CancellationToken.ThrowIfCancellationRequested();
|
||||
var model = Compilation.GetSemanticModel(method.SyntaxTree);
|
||||
|
||||
if (!FunctionsUtil.IsValidFunctionMethod(_context, Compilation, model, method))
|
||||
var methodName = method.Name;
|
||||
var methodParameterList = new List<string>();
|
||||
|
||||
foreach (IParameterSymbol parameterSymbol in method.Parameters)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var methodName = method.Identifier.Text;
|
||||
var methodParameterList = new List<string>(method.ParameterList.Parameters.Count);
|
||||
|
||||
foreach (var methodParam in method.ParameterList.Parameters)
|
||||
{
|
||||
if (model.GetDeclaredSymbol(methodParam) is not IParameterSymbol parameterSymbol)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var fullyQualifiedTypeName = parameterSymbol.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
methodParameterList.Add(fullyQualifiedTypeName);
|
||||
}
|
||||
|
||||
var methodSymbol = model.GetDeclaredSymbol(method)!;
|
||||
var defaultFormatClassName = methodSymbol.ContainingSymbol.ToDisplayString();
|
||||
var fullyQualifiedClassName = methodSymbol.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
var defaultFormatClassName = method.ContainingSymbol.ToDisplayString();
|
||||
var fullyQualifiedClassName = method.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
|
||||
var function = new ExecutableFunction
|
||||
{
|
||||
EntryPoint = $"{defaultFormatClassName}.{method.Identifier.ValueText}",
|
||||
EntryPoint = $"{defaultFormatClassName}.{method.Name}",
|
||||
ParameterTypeNames = methodParameterList,
|
||||
MethodName = methodName,
|
||||
ShouldAwait = IsTaskType(methodSymbol.ReturnType),
|
||||
IsReturnValueAssignable = IsReturnValueAssignable(methodSymbol),
|
||||
IsStatic = method.Modifiers.Any(SyntaxKind.StaticKeyword),
|
||||
ShouldAwait = IsTaskType(method.ReturnType),
|
||||
IsReturnValueAssignable = IsReturnValueAssignable(method),
|
||||
IsStatic = method.IsStatic,
|
||||
ParentFunctionClassName = defaultFormatClassName,
|
||||
ParentFunctionFullyQualifiedClassName = fullyQualifiedClassName
|
||||
ParentFunctionFullyQualifiedClassName = fullyQualifiedClassName,
|
||||
Visibility = method.GetVisibility(),
|
||||
AssemblyIdentity = method.ContainingAssembly.Identity.GetDisplayName(),
|
||||
};
|
||||
|
||||
functionList.Add(function);
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using FunctionMethodSyntaxReceiver = Microsoft.Azure.Functions.Worker.Sdk.Generators.FunctionMetadataProviderGenerator.FunctionMethodSyntaxReceiver;
|
||||
|
||||
|
@ -23,25 +26,53 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
|
|||
return;
|
||||
}
|
||||
|
||||
if (context.SyntaxReceiver is not FunctionMethodSyntaxReceiver receiver || receiver.CandidateMethods.Count == 0)
|
||||
if (context.SyntaxReceiver is not FunctionMethodSyntaxReceiver receiver)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var entryAssemblyFuncs = GetSymbolsMethodSyntaxes(receiver.CandidateMethods, context);
|
||||
var dependentFuncs = GetDependentAssemblyFunctionsSymbols(context);
|
||||
var allMethods = entryAssemblyFuncs.Concat(dependentFuncs);
|
||||
|
||||
if (!allMethods.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var parser = new Parser(context);
|
||||
var functions = parser.GetFunctions(receiver.CandidateMethods);
|
||||
|
||||
if (functions.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var functions = parser.GetFunctions(allMethods);
|
||||
var shouldIncludeAutoGeneratedAttributes = ShouldIncludeAutoGeneratedAttributes(context);
|
||||
|
||||
var text = Emitter.Emit(context, functions, shouldIncludeAutoGeneratedAttributes);
|
||||
context.AddSource(Constants.FileNames.GeneratedFunctionExecutor, SourceText.From(text, Encoding.UTF8));
|
||||
}
|
||||
|
||||
private IEnumerable<IMethodSymbol> GetSymbolsMethodSyntaxes(List<MethodDeclarationSyntax> methods, GeneratorExecutionContext context)
|
||||
{
|
||||
foreach (MethodDeclarationSyntax method in methods)
|
||||
{
|
||||
var model = context.Compilation.GetSemanticModel(method.SyntaxTree);
|
||||
|
||||
if (FunctionsUtil.IsValidFunctionMethod(context, context.Compilation, model, method))
|
||||
{
|
||||
IMethodSymbol? methodSymbol = (IMethodSymbol)model.GetDeclaredSymbol(method)!;
|
||||
yield return methodSymbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collect methods with Function attributes on them from dependent/referenced assemblies.
|
||||
/// </summary>
|
||||
private static IEnumerable<IMethodSymbol> GetDependentAssemblyFunctionsSymbols(GeneratorExecutionContext context)
|
||||
{
|
||||
var visitor = new ReferencedAssemblyMethodVisitor(context.Compilation);
|
||||
visitor.Visit(context.Compilation.SourceModule);
|
||||
|
||||
return visitor.FunctionMethods;
|
||||
}
|
||||
|
||||
private static bool ShouldIncludeAutoGeneratedAttributes(GeneratorExecutionContext context)
|
||||
{
|
||||
if (!context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(
|
||||
|
|
|
@ -45,8 +45,8 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
|
|||
{
|
||||
var result = ImmutableArray.CreateBuilder<GeneratorFunctionMetadata>();
|
||||
|
||||
// Loop through the candidate methods (methods with any attribute associated with them)
|
||||
foreach (IMethodSymbol method in methods)
|
||||
// Loop through the candidate methods (methods with any attribute associated with them) which are public.
|
||||
foreach (IMethodSymbol method in methods.Where(m => m.DeclaredAccessibility == Accessibility.Public))
|
||||
{
|
||||
CancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
|
@ -129,7 +129,7 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
|
|||
/// <summary>
|
||||
/// Checks for and returns any OutputBinding attributes associated with the method.
|
||||
/// </summary>
|
||||
private bool TryGetMethodOutputBinding(IMethodSymbol method,out bool hasMethodOutputBinding, out GeneratorRetryOptions? retryOptions, out IList<IDictionary<string, object>>? bindingsList)
|
||||
private bool TryGetMethodOutputBinding(IMethodSymbol method, out bool hasMethodOutputBinding, out GeneratorRetryOptions? retryOptions, out IList<IDictionary<string, object>>? bindingsList)
|
||||
{
|
||||
var attributes = method!.GetAttributes(); // methodSymbol is not null here because it's checked in IsValidAzureFunction which is called before bindings are collected/created
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the visibility of an "azure function" method and its parent classes.
|
||||
/// </summary>
|
||||
internal enum FunctionMethodVisibility
|
||||
{
|
||||
/// <summary>
|
||||
/// The method and it's parent classes are public & visible.
|
||||
/// </summary>
|
||||
Public,
|
||||
|
||||
/// <summary>
|
||||
/// The method is public, but one or more of its parent classes are not public.
|
||||
/// </summary>
|
||||
PublicButContainingTypeNotVisible,
|
||||
|
||||
/// <summary>
|
||||
/// The method is not public.
|
||||
/// </summary>
|
||||
NotPublic
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@
|
|||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<MinorProductVersion>1</MinorProductVersion>
|
||||
<PatchProductVersion>4</PatchProductVersion>
|
||||
<PatchProductVersion>5</PatchProductVersion>
|
||||
<VersionSuffix></VersionSuffix>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -38,8 +38,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and
|
|||
<FunctionsEnableWorkerIndexing Condition="$(FunctionsEnableWorkerIndexing) == '' Or $(FunctionsEnableWorkerIndexing)">true</FunctionsEnableWorkerIndexing>
|
||||
<FunctionsEnableMetadataSourceGen>$(FunctionsEnableWorkerIndexing)</FunctionsEnableMetadataSourceGen>
|
||||
<FunctionsAutoRegisterGeneratedMetadataProvider>$(FunctionsEnableWorkerIndexing)</FunctionsAutoRegisterGeneratedMetadataProvider>
|
||||
|
||||
<FunctionsEnableExecutorSourceGen Condition="$(FunctionsEnableExecutorSourceGen) == ''">false</FunctionsEnableExecutorSourceGen>
|
||||
<FunctionsEnableExecutorSourceGen Condition="$(FunctionsEnableExecutorSourceGen) == '' Or $(FunctionsEnableExecutorSourceGen)">true</FunctionsEnableExecutorSourceGen>
|
||||
<FunctionsAutoRegisterGeneratedFunctionsExecutor Condition="$(FunctionsAutoRegisterGeneratedFunctionsExecutor) == ''">true</FunctionsAutoRegisterGeneratedFunctionsExecutor>
|
||||
<FunctionsAutoRegisterGeneratedFunctionsExecutor Condition="$(FunctionsAutoRegisterGeneratedFunctionsExecutor)">true</FunctionsAutoRegisterGeneratedFunctionsExecutor>
|
||||
<FunctionsGeneratedCodeNamespace Condition="$(FunctionsGeneratedCodeNamespace) == ''">$(RootNamespace.Replace("-", "_"))</FunctionsGeneratedCodeNamespace>
|
||||
|
|
|
@ -4,12 +4,15 @@
|
|||
- My change description (#PR/#issue)
|
||||
-->
|
||||
|
||||
### Microsoft.Azure.Functions.Worker.Sdk 1.16.4 (meta package)
|
||||
|
||||
### Microsoft.Azure.Functions.Worker.Sdk 1.16.3 (meta package)
|
||||
|
||||
- Update worker.config generation to accurate worker executable name (#1053)
|
||||
- Default to optimized function executor.
|
||||
- Update Microsoft.Azure.Functions.Worker.Sdk.Generators dependency to 1.1.5
|
||||
|
||||
### Microsoft.Azure.Functions.Worker.Sdk.Generators 1.1.5
|
||||
|
||||
- Adding support for executing functions from referenced assemblies in the optimized function executor (#2089)
|
||||
- Update worker.config generation to accurate worker executable name (#1053)
|
||||
- Fix incorrect value of `ScriptFile` property in function metadata for .Net Framework function apps (#2103)
|
||||
- Generate valid namespace when root namespace contains `-` (#2097)
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
using Microsoft.Azure.Functions.Worker;
|
||||
// (c).NET Foundation.All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.Azure.Functions.Worker;
|
||||
using Microsoft.Azure.Functions.Worker.Http;
|
||||
|
||||
namespace DependentAssemblyWithFunctions
|
||||
|
@ -11,5 +14,11 @@ namespace DependentAssemblyWithFunctions
|
|||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[Function("ThisShouldBeSkippedBecauseMethodNotPublic")]
|
||||
internal static HttpResponseData Run2([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Functions.Worker.Sdk.Generators;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.Functions.SdkGeneratorTests
|
||||
{
|
||||
public partial class FunctionExecutorGeneratorTests
|
||||
{
|
||||
public class DependentAssemblyTest
|
||||
{
|
||||
private readonly Assembly[] _referencedAssemblies;
|
||||
|
||||
public DependentAssemblyTest()
|
||||
{
|
||||
var abstractionsExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Abstractions.dll");
|
||||
var httpExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Http.dll");
|
||||
var hostingExtension = Assembly.LoadFrom("Microsoft.Extensions.Hosting.dll");
|
||||
var diExtension = Assembly.LoadFrom("Microsoft.Extensions.DependencyInjection.dll");
|
||||
var hostingAbExtension = Assembly.LoadFrom("Microsoft.Extensions.Hosting.Abstractions.dll");
|
||||
var diAbExtension = Assembly.LoadFrom("Microsoft.Extensions.DependencyInjection.Abstractions.dll");
|
||||
var dependentAssembly = Assembly.LoadFrom("DependentAssemblyWithFunctions.dll");
|
||||
|
||||
_referencedAssemblies = new[]
|
||||
{
|
||||
abstractionsExtension,
|
||||
httpExtension,
|
||||
hostingExtension,
|
||||
hostingAbExtension,
|
||||
diExtension,
|
||||
diAbExtension,
|
||||
dependentAssembly
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FunctionsFromDependentAssembly()
|
||||
{
|
||||
const string inputSourceCode = """
|
||||
using System;
|
||||
using Microsoft.Azure.Functions.Worker;
|
||||
using Microsoft.Azure.Functions.Worker.Http;
|
||||
namespace MyCompany
|
||||
{
|
||||
public class MyHttpTriggers
|
||||
{
|
||||
[Function("FunctionA")]
|
||||
public HttpResponseData Foo([HttpTrigger(AuthorizationLevel.User, "get")] HttpRequestData r, FunctionContext c)
|
||||
{
|
||||
return r.CreateResponse(System.Net.HttpStatusCode.OK);
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
var expected = $$"""
|
||||
// <auto-generated/>
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
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 TestProject
|
||||
{
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
internal class DirectFunctionExecutor : IFunctionExecutor
|
||||
{
|
||||
private readonly IFunctionActivator _functionActivator;
|
||||
private Lazy<IFunctionExecutor> _defaultExecutor;
|
||||
private readonly Dictionary<string, Type> types = new()
|
||||
{
|
||||
{ "MyCompany.MyHttpTriggers", Type.GetType("MyCompany.MyHttpTriggers, TestProject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null")! },
|
||||
{ "DependentAssemblyWithFunctions.DependencyFunction", Type.GetType("DependentAssemblyWithFunctions.DependencyFunction, DependentAssemblyWithFunctions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")! },
|
||||
{ "MyCompany.MyProduct.MyApp.HttpFunctions", Type.GetType("MyCompany.MyProduct.MyApp.HttpFunctions, DependentAssemblyWithFunctions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")! },
|
||||
{ "MyCompany.MyProduct.MyApp.Foo.Bar", Type.GetType("MyCompany.MyProduct.MyApp.Foo.Bar, DependentAssemblyWithFunctions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")! }
|
||||
};
|
||||
|
||||
public DirectFunctionExecutor(IFunctionActivator functionActivator)
|
||||
{
|
||||
_functionActivator = functionActivator ?? throw new ArgumentNullException(nameof(functionActivator));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ExecuteAsync(FunctionContext context)
|
||||
{
|
||||
var inputBindingFeature = context.Features.Get<IFunctionInputBindingFeature>()!;
|
||||
var inputBindingResult = await inputBindingFeature.BindFunctionInputAsync(context)!;
|
||||
var inputArguments = inputBindingResult.Values;
|
||||
_defaultExecutor = new Lazy<IFunctionExecutor>(() => CreateDefaultExecutorInstance(context));
|
||||
|
||||
if (string.Equals(context.FunctionDefinition.EntryPoint, "MyCompany.MyHttpTriggers.Foo", StringComparison.Ordinal))
|
||||
{
|
||||
var instanceType = types["MyCompany.MyHttpTriggers"];
|
||||
var i = _functionActivator.CreateInstance(instanceType, context) as global::MyCompany.MyHttpTriggers;
|
||||
context.GetInvocationResult().Value = i.Foo((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0], (global::Microsoft.Azure.Functions.Worker.FunctionContext)inputArguments[1]);
|
||||
}
|
||||
else if (string.Equals(context.FunctionDefinition.EntryPoint, "DependentAssemblyWithFunctions.DependencyFunction.Run", StringComparison.Ordinal))
|
||||
{
|
||||
var instanceType = types["DependentAssemblyWithFunctions.DependencyFunction"];
|
||||
var i = _functionActivator.CreateInstance(instanceType, context) as global::DependentAssemblyWithFunctions.DependencyFunction;
|
||||
context.GetInvocationResult().Value = i.Run((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0]);
|
||||
}
|
||||
else if (string.Equals(context.FunctionDefinition.EntryPoint, "DependentAssemblyWithFunctions.InternalFunction.Run", StringComparison.Ordinal))
|
||||
{
|
||||
await _defaultExecutor.Value.ExecuteAsync(context);
|
||||
}
|
||||
else if (string.Equals(context.FunctionDefinition.EntryPoint, "DependentAssemblyWithFunctions.StaticFunction.Run", StringComparison.Ordinal))
|
||||
{
|
||||
context.GetInvocationResult().Value = global::DependentAssemblyWithFunctions.StaticFunction.Run((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0], (global::Microsoft.Azure.Functions.Worker.FunctionContext)inputArguments[1]);
|
||||
}
|
||||
else if (string.Equals(context.FunctionDefinition.EntryPoint, "MyCompany.MyProduct.MyApp.HttpFunctions.Run", StringComparison.Ordinal))
|
||||
{
|
||||
var instanceType = types["MyCompany.MyProduct.MyApp.HttpFunctions"];
|
||||
var i = _functionActivator.CreateInstance(instanceType, context) as global::MyCompany.MyProduct.MyApp.HttpFunctions;
|
||||
context.GetInvocationResult().Value = i.Run((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0]);
|
||||
}
|
||||
else if (string.Equals(context.FunctionDefinition.EntryPoint, "MyCompany.MyProduct.MyApp.Foo.Bar.Run", StringComparison.Ordinal))
|
||||
{
|
||||
var instanceType = types["MyCompany.MyProduct.MyApp.Foo.Bar"];
|
||||
var i = _functionActivator.CreateInstance(instanceType, context) as global::MyCompany.MyProduct.MyApp.Foo.Bar;
|
||||
context.GetInvocationResult().Value = i.Run((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private IFunctionExecutor CreateDefaultExecutorInstance(FunctionContext context)
|
||||
{
|
||||
var defaultExecutorFullName = "Microsoft.Azure.Functions.Worker.Invocation.DefaultFunctionExecutor, Microsoft.Azure.Functions.Worker.Core, Version=1.16.0.0, Culture=neutral, PublicKeyToken=551316b6919f366c";
|
||||
var defaultExecutorType = Type.GetType(defaultExecutorFullName);
|
||||
|
||||
return ActivatorUtilities.CreateInstance(context.InstanceServices, defaultExecutorType) as IFunctionExecutor;
|
||||
}
|
||||
}
|
||||
{{GetExpectedExtensionMethodCode()}}
|
||||
}
|
||||
""".Replace("'", "\"");
|
||||
|
||||
await TestHelpers.RunTestAsync<FunctionExecutorGenerator>(
|
||||
_referencedAssemblies,
|
||||
inputSourceCode,
|
||||
Constants.FileNames.GeneratedFunctionExecutor,
|
||||
expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.Azure.Functions.SdkGeneratorTests
|
||||
{
|
||||
public class FunctionExecutorGeneratorTests
|
||||
public partial class FunctionExecutorGeneratorTests
|
||||
{
|
||||
// A super set of assemblies we need for all tests in the file.
|
||||
private readonly Assembly[] _referencedAssemblies = new[]
|
||||
|
@ -116,9 +116,9 @@ namespace TestProject
|
|||
private readonly IFunctionActivator _functionActivator;
|
||||
private readonly Dictionary<string, Type> types = new()
|
||||
{{
|
||||
{{ ""MyCompany.MyHttpTriggers"", Type.GetType(""MyCompany.MyHttpTriggers"")! }},
|
||||
{{ ""MyCompany.MyHttpTriggers2"", Type.GetType(""MyCompany.MyHttpTriggers2"")! }},
|
||||
{{ ""MyCompany.QueueTriggers"", Type.GetType(""MyCompany.QueueTriggers"")! }}
|
||||
{{ ""MyCompany.MyHttpTriggers"", Type.GetType(""MyCompany.MyHttpTriggers, TestProject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"")! }},
|
||||
{{ ""MyCompany.MyHttpTriggers2"", Type.GetType(""MyCompany.MyHttpTriggers2, TestProject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"")! }},
|
||||
{{ ""MyCompany.QueueTriggers"", Type.GetType(""MyCompany.QueueTriggers, TestProject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"")! }}
|
||||
}};
|
||||
|
||||
public DirectFunctionExecutor(IFunctionActivator functionActivator)
|
||||
|
@ -225,7 +225,7 @@ namespace MyCompany.MyProject.MyApp
|
|||
private readonly IFunctionActivator _functionActivator;
|
||||
private readonly Dictionary<string, Type> types = new()
|
||||
{{
|
||||
{{ ""MyCompany.MyHttpTriggers"", Type.GetType(""MyCompany.MyHttpTriggers"")! }}
|
||||
{{ ""MyCompany.MyHttpTriggers"", Type.GetType(""MyCompany.MyHttpTriggers, TestProject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"")! }}
|
||||
}};
|
||||
|
||||
public DirectFunctionExecutor(IFunctionActivator functionActivator)
|
||||
|
@ -471,7 +471,7 @@ namespace TestProject
|
|||
private readonly IFunctionActivator _functionActivator;
|
||||
private readonly Dictionary<string, Type> types = new()
|
||||
{{
|
||||
{{ ""MyCompany.MyHttpTriggers"", Type.GetType(""MyCompany.MyHttpTriggers"")! }}
|
||||
{{ ""MyCompany.MyHttpTriggers"", Type.GetType(""MyCompany.MyHttpTriggers, TestProject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"")! }}
|
||||
}};
|
||||
|
||||
public DirectFunctionExecutor(IFunctionActivator functionActivator)
|
||||
|
@ -556,7 +556,7 @@ namespace TestProject
|
|||
private readonly IFunctionActivator _functionActivator;
|
||||
private readonly Dictionary<string, Type> types = new()
|
||||
{{
|
||||
{{ ""TestProject.TestProject"", Type.GetType(""TestProject.TestProject"")! }}
|
||||
{{ ""TestProject.TestProject"", Type.GetType(""TestProject.TestProject, TestProject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"")! }}
|
||||
}};
|
||||
|
||||
public DirectFunctionExecutor(IFunctionActivator functionActivator)
|
||||
|
@ -639,7 +639,7 @@ namespace TestProject
|
|||
private readonly IFunctionActivator _functionActivator;
|
||||
private readonly Dictionary<string, Type> types = new()
|
||||
{{
|
||||
{{ ""MyCompany.MyHttpTriggers"", Type.GetType(""MyCompany.MyHttpTriggers"")! }}
|
||||
{{ ""MyCompany.MyHttpTriggers"", Type.GetType(""MyCompany.MyHttpTriggers, TestProject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"")! }}
|
||||
}};
|
||||
|
||||
public DirectFunctionExecutor(IFunctionActivator functionActivator)
|
||||
|
|
|
@ -15,6 +15,8 @@ using Microsoft.CodeAnalysis.Testing.Verifiers;
|
|||
using Microsoft.Azure.Functions.Worker.Core;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Microsoft.Azure.Functions.SdkGeneratorTests
|
||||
{
|
||||
|
@ -87,11 +89,16 @@ namespace Microsoft.Azure.Functions.SdkGeneratorTests
|
|||
{
|
||||
public Test()
|
||||
{
|
||||
// See https://www.nuget.org/packages/Microsoft.NETCore.App.Ref/6.0.0
|
||||
var targetFrameworkAttribute = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(TargetFrameworkAttribute), false)
|
||||
.SingleOrDefault() as TargetFrameworkAttribute;
|
||||
|
||||
string targetFramework = targetFrameworkAttribute!.FrameworkName;
|
||||
var tfm = ConvertFrameworkMonikerToTfm(targetFramework);
|
||||
|
||||
this.ReferenceAssemblies = new ReferenceAssemblies(
|
||||
targetFramework: "net6.0",
|
||||
referenceAssemblyPackage: new PackageIdentity("Microsoft.NETCore.App.Ref", "6.0.0"),
|
||||
referenceAssemblyPath: Path.Combine("ref", "net6.0"));
|
||||
targetFramework: tfm,
|
||||
referenceAssemblyPackage: new PackageIdentity("Microsoft.NETCore.App.Ref", Environment.Version.ToString()),
|
||||
referenceAssemblyPath: Path.Combine("ref", tfm));
|
||||
}
|
||||
|
||||
public LanguageVersion LanguageVersion { get; set; } = LanguageVersion.CSharp9;
|
||||
|
@ -119,6 +126,29 @@ namespace Microsoft.Azure.Functions.SdkGeneratorTests
|
|||
{
|
||||
return ((CSharpParseOptions)base.CreateParseOptions()).WithLanguageVersion(this.LanguageVersion);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Example input: .NETCoreApp,Version=v7.0
|
||||
/// Example output: net7.0
|
||||
/// </summary>
|
||||
private static string ConvertFrameworkMonikerToTfm(string frameworkMoniker)
|
||||
{
|
||||
var parts = frameworkMoniker.Split(',');
|
||||
var identifier = parts[0];
|
||||
var version = parts[1].Split('=')[1];
|
||||
|
||||
switch (identifier)
|
||||
{
|
||||
case ".NETCoreApp":
|
||||
return $"net{version.Substring(1)}";
|
||||
case ".NETFramework":
|
||||
return $"net{version.Replace(".", "")}";
|
||||
case ".NETStandard":
|
||||
return $"netstandard{version.Substring(1)}";
|
||||
default:
|
||||
throw new NotSupportedException($"Unknown framework identifier: {identifier}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче