diff --git a/sdk/Sdk.Generators/Extensions/IMethodSymbolExtensions.cs b/sdk/Sdk.Generators/Extensions/IMethodSymbolExtensions.cs new file mode 100644 index 000000000..f85aae1a7 --- /dev/null +++ b/sdk/Sdk.Generators/Extensions/IMethodSymbolExtensions.cs @@ -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 + { + /// + /// 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 + /// + /// The instance representing an azure function method. + /// + 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; + } + } +} diff --git a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Emitter.cs b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Emitter.cs index 2d4c3e5fc..0fcdff7bd 100644 --- a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Emitter.cs +++ b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Emitter.cs @@ -13,8 +13,12 @@ public partial class FunctionExecutorGenerator { internal static class Emitter { - internal static string Emit(GeneratorExecutionContext context, IEnumerable functions, bool includeAutoRegistrationCode) + private const string WorkerCoreAssemblyName = "Microsoft.Azure.Functions.Worker.Core"; + + internal static string Emit(GeneratorExecutionContext context, IEnumerable executableFunctions, bool includeAutoRegistrationCode) { + var functions = executableFunctions.ToList(); + var defaultExecutorNeeded = functions.Any(f => f.Visibility == FunctionMethodVisibility.PublicButContainingTypeNotVisible); string result = $$""" // @@ -31,7 +35,7 @@ namespace {{FunctionsUtil.GetNamespaceForGeneratedCode(context)}} [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 _defaultExecutor;" : string.Empty)}} {{GetTypesDictionary(functions)}} public DirectFunctionExecutor(IFunctionActivator functionActivator) { @@ -41,8 +45,8 @@ public DirectFunctionExecutor(IFunctionActivator functionActivator) /// public async ValueTask ExecuteAsync(FunctionContext context) { - {{GetMethodBody(functions)}} - } + {{GetMethodBody(functions, defaultExecutorNeeded)}} + }{{(defaultExecutorNeeded ? $"{Environment.NewLine}{EmitCreateDefaultExecutorMethod(context)}" : string.Empty)}} } /// @@ -67,20 +71,40 @@ public static IHostBuilder ConfigureGeneratedFunctionExecutor(this IHostBuilder 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 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 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 @@ public void Configure(IHostBuilder hostBuilder) return ""; } - private static string GetMethodBody(IEnumerable functions) + private static string GetMethodBody(IEnumerable functions, bool anyDefaultExecutor) { var sb = new StringBuilder(); sb.Append( - """ + $$""" var inputBindingFeature = context.Features.Get()!; var inputBindingResult = await inputBindingFeature.BindFunctionInputAsync(context)!; var inputArguments = inputBindingResult.Values; - + {{(anyDefaultExecutor ? $" _defaultExecutor = new Lazy(() => 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(); - 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}}"]; + } + + return sb.ToString(); + } + + private static string EmitFastPath(ExecutableFunction function) + { + var sb = new StringBuilder(); + int functionParamCounter = 0; + var functionParamList = new List(); + 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}); - }}"); + 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);"; + } } } } diff --git a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.ExecutableFunction.cs b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.ExecutableFunction.cs index e9f1f16f0..2dbcfe7b6 100644 --- a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.ExecutableFunction.cs +++ b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.ExecutableFunction.cs @@ -52,5 +52,16 @@ internal class ExecutableFunction /// A collection of fully qualified type names of the parameters of the function. /// internal IEnumerable ParameterTypeNames { set; get; } = Enumerable.Empty(); + + /// + /// Get a value indicating the visibility of the executable function. + /// + internal FunctionMethodVisibility Visibility { get; set; } + + /// + /// Gets the assembly identity of the function. + /// ex: FooAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=9475d07f10cb09df + /// + internal string AssemblyIdentity { get; set; } = null!; } } diff --git a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Parser.cs b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Parser.cs index fc7ccffa1..fd6c570fc 100644 --- a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Parser.cs +++ b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Parser.cs @@ -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 @@ internal Parser(GeneratorExecutionContext context) private Compilation Compilation => _context.Compilation; - internal ICollection GetFunctions(List methods) + internal ICollection GetFunctions(IEnumerable methods) { var functionList = new List(); - 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)) - { - continue; - } + var methodName = method.Name; + var methodParameterList = new List(); - var methodName = method.Identifier.Text; - var methodParameterList = new List(method.ParameterList.Parameters.Count); - - foreach (var methodParam in method.ParameterList.Parameters) + foreach (IParameterSymbol parameterSymbol in method.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); diff --git a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.cs b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.cs index 4bbea665f..1f239e3b5 100644 --- a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.cs +++ b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.cs @@ -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 @@ public void Execute(GeneratorExecutionContext context) return; } - if (context.SyntaxReceiver is not FunctionMethodSyntaxReceiver receiver || receiver.CandidateMethods.Count == 0) + if (context.SyntaxReceiver is not FunctionMethodSyntaxReceiver receiver) { return; } - var parser = new Parser(context); - var functions = parser.GetFunctions(receiver.CandidateMethods); + var entryAssemblyFuncs = GetSymbolsMethodSyntaxes(receiver.CandidateMethods, context); + var dependentFuncs = GetDependentAssemblyFunctionsSymbols(context); + var allMethods = entryAssemblyFuncs.Concat(dependentFuncs); - if (functions.Count == 0) + if (!allMethods.Any()) { return; } + var parser = new Parser(context); + 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 GetSymbolsMethodSyntaxes(List 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; + } + } + } + + /// + /// Collect methods with Function attributes on them from dependent/referenced assemblies. + /// + private static IEnumerable 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( diff --git a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs index d939f8616..f8c01a50a 100644 --- a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs +++ b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs @@ -45,8 +45,8 @@ public IReadOnlyList GetFunctionMetadataInfo(List(); - // 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 @@ private bool TryGetBindings(IMethodSymbol method, out IList /// Checks for and returns any OutputBinding attributes associated with the method. /// - private bool TryGetMethodOutputBinding(IMethodSymbol method,out bool hasMethodOutputBinding, out GeneratorRetryOptions? retryOptions, out IList>? bindingsList) + private bool TryGetMethodOutputBinding(IMethodSymbol method, out bool hasMethodOutputBinding, out GeneratorRetryOptions? retryOptions, out IList>? bindingsList) { var attributes = method!.GetAttributes(); // methodSymbol is not null here because it's checked in IsValidAzureFunction which is called before bindings are collected/created diff --git a/sdk/Sdk.Generators/FunctionMethodVisibility.cs b/sdk/Sdk.Generators/FunctionMethodVisibility.cs new file mode 100644 index 000000000..558ce5054 --- /dev/null +++ b/sdk/Sdk.Generators/FunctionMethodVisibility.cs @@ -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 +{ + /// + /// Represents the visibility of an "azure function" method and its parent classes. + /// + internal enum FunctionMethodVisibility + { + /// + /// The method and it's parent classes are public & visible. + /// + Public, + + /// + /// The method is public, but one or more of its parent classes are not public. + /// + PublicButContainingTypeNotVisible, + + /// + /// The method is not public. + /// + NotPublic + } +} diff --git a/sdk/Sdk.Generators/Sdk.Generators.csproj b/sdk/Sdk.Generators/Sdk.Generators.csproj index 6d312db62..cbe6ac94e 100644 --- a/sdk/Sdk.Generators/Sdk.Generators.csproj +++ b/sdk/Sdk.Generators/Sdk.Generators.csproj @@ -10,7 +10,7 @@ false true 1 - 4 + 5 true diff --git a/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.targets b/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.targets index a6fa6dd62..7ad914b23 100644 --- a/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.targets +++ b/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.targets @@ -38,8 +38,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and true $(FunctionsEnableWorkerIndexing) $(FunctionsEnableWorkerIndexing) - - false + true true true $(RootNamespace.Replace("-", "_")) diff --git a/sdk/release_notes.md b/sdk/release_notes.md index 541bb6809..f49464aa3 100644 --- a/sdk/release_notes.md +++ b/sdk/release_notes.md @@ -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) diff --git a/test/DependentAssemblyWithFunctions/InternalFunction.cs b/test/DependentAssemblyWithFunctions/InternalFunction.cs index 6a34b7388..5cf10a5c2 100644 --- a/test/DependentAssemblyWithFunctions/InternalFunction.cs +++ b/test/DependentAssemblyWithFunctions/InternalFunction.cs @@ -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 @@ public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "g { throw new NotImplementedException(); } + + [Function("ThisShouldBeSkippedBecauseMethodNotPublic")] + internal static HttpResponseData Run2([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req) + { + throw new NotImplementedException(); + } } } diff --git a/test/Sdk.Generator.Tests/FunctionExecutor/DependentAssemblyTest.cs b/test/Sdk.Generator.Tests/FunctionExecutor/DependentAssemblyTest.cs new file mode 100644 index 000000000..e9094d064 --- /dev/null +++ b/test/Sdk.Generator.Tests/FunctionExecutor/DependentAssemblyTest.cs @@ -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 = $$""" + // + 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 _defaultExecutor; + private readonly Dictionary 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)); + } + + /// + public async ValueTask ExecuteAsync(FunctionContext context) + { + var inputBindingFeature = context.Features.Get()!; + var inputBindingResult = await inputBindingFeature.BindFunctionInputAsync(context)!; + var inputArguments = inputBindingResult.Values; + _defaultExecutor = new Lazy(() => 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( + _referencedAssemblies, + inputSourceCode, + Constants.FileNames.GeneratedFunctionExecutor, + expected); + } + } + } +} diff --git a/test/Sdk.Generator.Tests/FunctionExecutor/FunctionExecutorGeneratorTests.cs b/test/Sdk.Generator.Tests/FunctionExecutor/FunctionExecutorGeneratorTests.cs index 46b4a461b..fcda1c27b 100644 --- a/test/Sdk.Generator.Tests/FunctionExecutor/FunctionExecutorGeneratorTests.cs +++ b/test/Sdk.Generator.Tests/FunctionExecutor/FunctionExecutorGeneratorTests.cs @@ -16,7 +16,7 @@ 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 @@ internal class DirectFunctionExecutor : IFunctionExecutor private readonly IFunctionActivator _functionActivator; private readonly Dictionary 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 @@ internal class DirectFunctionExecutor : IFunctionExecutor private readonly IFunctionActivator _functionActivator; private readonly Dictionary 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 @@ internal class DirectFunctionExecutor : IFunctionExecutor private readonly IFunctionActivator _functionActivator; private readonly Dictionary 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 @@ internal class DirectFunctionExecutor : IFunctionExecutor private readonly IFunctionActivator _functionActivator; private readonly Dictionary 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 @@ internal class DirectFunctionExecutor : IFunctionExecutor private readonly IFunctionActivator _functionActivator; private readonly Dictionary 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) diff --git a/test/Sdk.Generator.Tests/TestHelpers.cs b/test/Sdk.Generator.Tests/TestHelpers.cs index 71605e43e..5224b8eaa 100644 --- a/test/Sdk.Generator.Tests/TestHelpers.cs +++ b/test/Sdk.Generator.Tests/TestHelpers.cs @@ -15,6 +15,8 @@ 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 @@ public class Test : CSharpSourceGeneratorTest { 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 @@ protected override ParseOptions CreateParseOptions() { return ((CSharpParseOptions)base.CreateParseOptions()).WithLanguageVersion(this.LanguageVersion); } + + /// + /// Example input: .NETCoreApp,Version=v7.0 + /// Example output: net7.0 + /// + 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}"); + } + } } } }