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}");
+ }
+ }
}
}
}