Skip to content

Commit

Permalink
Fixed metadata generator not producing metadata when the entry assemb…
Browse files Browse the repository at this point in the history
…ly lacked functions but dependent assemblies had them (#2301)

* Fixed metadata generator not producing metadata when the entry assembly lacked functions but dependent assemblies had them

* Fixed metadata generator not producing metadata when the entry assembly lacked functions but dependent assemblies had them. Fixes #2294

* Including release notes entry for #2208

* Removed extension method and changed "GetDependentAssemblyFunctions" method return type to List.
  • Loading branch information
kshyju authored Feb 28, 2024
1 parent 914e392 commit 084df40
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,28 @@ 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;
}

// attempt to parse user compilation
var p = new Parser(context);

var entryAssemblyFunctionSymbols = GetEntryAssemblyFunctions(receiver.CandidateMethods, context);
var entryAssemblyFunctionSymbols = GetEntryAssemblyFunctions(receiver.CandidateMethods, context).ToList();
var dependentAssemblyFunctionSymbols = GetDependentAssemblyFunctions(context);

if (entryAssemblyFunctionSymbols.Count == 0 && dependentAssemblyFunctionSymbols.Count == 0)
{
return;
}

var parser = new Parser(context);
var entryAssemblyParsingContext = new FunctionsMetadataParsingContext
{
ScriptFileExtension = GetScriptFileExtensionForEntryPointAssemblyFunctions(context)
};
var entryAssemblyFunctions = p.GetFunctionMetadataInfo(entryAssemblyFunctionSymbols.ToList(), entryAssemblyParsingContext);
var dependentAssemblyFunctions = p.GetFunctionMetadataInfo(dependentAssemblyFunctionSymbols.ToList());

var entryAssemblyFunctions = parser.GetFunctionMetadataInfo(entryAssemblyFunctionSymbols, entryAssemblyParsingContext);
var dependentAssemblyFunctions = parser.GetFunctionMetadataInfo(dependentAssemblyFunctionSymbols);

IReadOnlyList<GeneratorFunctionMetadata> functionMetadataInfo = entryAssemblyFunctions.Concat(dependentAssemblyFunctions).ToList();

Expand Down Expand Up @@ -94,7 +98,7 @@ private static bool ShouldIncludeAutoGeneratedAttributes(GeneratorExecutionConte
bool.TryParse(autoRegisterSwitch, out bool enableRegistration);
return enableRegistration;
}

private static bool ShouldExecuteGeneration(GeneratorExecutionContext context)
{
if (!context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(
Expand Down Expand Up @@ -124,7 +128,7 @@ private IEnumerable<IMethodSymbol> GetEntryAssemblyFunctions(List<MethodDeclarat
/// <summary>
/// Collect methods with Function attributes on them from dependent/referenced assemblies.
/// </summary>
private IEnumerable<IMethodSymbol> GetDependentAssemblyFunctions(GeneratorExecutionContext context)
private List<IMethodSymbol> GetDependentAssemblyFunctions(GeneratorExecutionContext context)
{
var visitor = new ReferencedAssemblyMethodVisitor(context.Compilation);
visitor.Visit(context.Compilation.SourceModule);
Expand Down
2 changes: 1 addition & 1 deletion sdk/Sdk.Generators/Sdk.Generators.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<IncludeBuildOutput>false</IncludeBuildOutput>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<MinorProductVersion>2</MinorProductVersion>
<PatchProductVersion>0</PatchProductVersion>
<PatchProductVersion>1</PatchProductVersion>
<IsRoslynComponent>true</IsRoslynComponent>
</PropertyGroup>

Expand Down
1 change: 1 addition & 0 deletions sdk/Sdk/Sdk.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<MinorProductVersion>17</MinorProductVersion>
<PatchProductVersion>1</PatchProductVersion>
<TargetFrameworks>netstandard2.0;net472</TargetFrameworks>
<PackageId>Microsoft.Azure.Functions.Worker.Sdk</PackageId>
<Description>This package provides development time support for the Azure Functions .NET Worker.</Description>
Expand Down
10 changes: 7 additions & 3 deletions sdk/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
- My change description (#PR/#issue)
-->

### Microsoft.Azure.Functions.Worker.Sdk 1.17.0 (meta package)
### Microsoft.Azure.Functions.Worker.Sdk 1.17.1 (meta package)

- Updating extension project generator to use `Microsoft.NET.Sdk.Functions` 4.3.0 (#2247)
- Fix build when `local.settings.json` is not present (#2251)
- Update Microsoft.Azure.Functions.Worker.Sdk.Generators dependency to 1.2.1

### Microsoft.Azure.Functions.Worker.Sdk.Generators 1.2.1

- Minor refactoring and cleanup (#2208)
- Fixed metadata generator not producing metadata when the entry assembly lacked functions but dependent assemblies had them (#2300)
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public DependentAssemblyTest()
}

[Fact]
public async Task FunctionInDependentAssemblyTest()
public async Task FunctionsFromFunctionAppAndDependentAssembly()
{
string inputCode = """
using System;
Expand Down Expand Up @@ -195,6 +195,147 @@ await TestHelpers.RunTestAsync<FunctionMetadataProviderGenerator>(
expectedGeneratedFileName,
expectedOutput);
}

[Fact]
public async Task FunctionsFromDependentAssemblyOnly()
{
string inputCode = """
using System;
namespace FunctionApp
{
public class Program
{
public static void Main()
{
Console.WriteLine("App main starting");
}
}
}
""";

string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs";
string expectedOutput = """
// <auto-generated/>
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace TestProject
{
/// <summary>
/// Custom <see cref="IFunctionMetadataProvider"/> implementation that returns function metadata definitions for the current worker."/>
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider
{
/// <inheritdoc/>
public Task<ImmutableArray<IFunctionMetadata>> GetFunctionMetadataAsync(string directory)
{
var metadataList = new List<IFunctionMetadata>();
var Function0RawBindings = new List<string>();
Function0RawBindings.Add(@"{""name"":""req"",""type"":""httpTrigger"",""direction"":""In"",""authLevel"":""Anonymous"",""methods"":[""get"",""post""]}");
Function0RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}");
var Function0 = new DefaultFunctionMetadata
{
Language = "dotnet-isolated",
Name = "DependencyFunc",
EntryPoint = "DependentAssemblyWithFunctions.DependencyFunction.Run",
RawBindings = Function0RawBindings,
ScriptFile = "DependentAssemblyWithFunctions.dll"
};
metadataList.Add(Function0);
var Function1RawBindings = new List<string>();
Function1RawBindings.Add(@"{""name"":""req"",""type"":""httpTrigger"",""direction"":""In"",""authLevel"":""Anonymous"",""methods"":[""get"",""post""]}");
Function1RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}");
var Function1 = new DefaultFunctionMetadata
{
Language = "dotnet-isolated",
Name = "InternalFunction",
EntryPoint = "DependentAssemblyWithFunctions.InternalFunction.Run",
RawBindings = Function1RawBindings,
ScriptFile = "DependentAssemblyWithFunctions.dll"
};
metadataList.Add(Function1);
var Function2RawBindings = new List<string>();
Function2RawBindings.Add(@"{""name"":""req"",""type"":""httpTrigger"",""direction"":""In"",""authLevel"":""Anonymous"",""methods"":[""get"",""post""]}");
Function2RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}");
var Function2 = new DefaultFunctionMetadata
{
Language = "dotnet-isolated",
Name = "StaticFunction",
EntryPoint = "DependentAssemblyWithFunctions.StaticFunction.Run",
RawBindings = Function2RawBindings,
ScriptFile = "DependentAssemblyWithFunctions.dll"
};
metadataList.Add(Function2);
var Function3RawBindings = new List<string>();
Function3RawBindings.Add(@"{""name"":""req"",""type"":""httpTrigger"",""direction"":""In"",""authLevel"":""Anonymous"",""methods"":[""get"",""post""]}");
Function3RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}");
var Function3 = new DefaultFunctionMetadata
{
Language = "dotnet-isolated",
Name = "NestedNamespaceFunc1",
EntryPoint = "MyCompany.MyProduct.MyApp.HttpFunctions.Run",
RawBindings = Function3RawBindings,
ScriptFile = "DependentAssemblyWithFunctions.dll"
};
metadataList.Add(Function3);
var Function4RawBindings = new List<string>();
Function4RawBindings.Add(@"{""name"":""req"",""type"":""httpTrigger"",""direction"":""In"",""authLevel"":""Anonymous"",""methods"":[""get"",""post""]}");
Function4RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}");
var Function4 = new DefaultFunctionMetadata
{
Language = "dotnet-isolated",
Name = "NestedTypeFunc",
EntryPoint = "MyCompany.MyProduct.MyApp.Foo.Bar.Run",
RawBindings = Function4RawBindings,
ScriptFile = "DependentAssemblyWithFunctions.dll"
};
metadataList.Add(Function4);
return Task.FromResult(metadataList.ToImmutableArray());
}
}
/// <summary>
/// Extension methods to enable registration of the custom <see cref="IFunctionMetadataProvider"/> implementation generated for the current worker.
/// </summary>
public static class WorkerHostBuilderFunctionMetadataProviderExtension
{
///<summary>
/// Adds the GeneratedFunctionMetadataProvider to the service collection.
/// During initialization, the worker will return generated function metadata instead of relying on the Azure Functions host for function indexing.
///</summary>
public static IHostBuilder ConfigureGeneratedFunctionMetadataProvider(this IHostBuilder builder)
{
builder.ConfigureServices(s =>
{
s.AddSingleton<IFunctionMetadataProvider, GeneratedFunctionMetadataProvider>();
});
return builder;
}
}
}
""";

await TestHelpers.RunTestAsync<FunctionMetadataProviderGenerator>(
_referencedExtensionAssemblies,
inputCode,
expectedGeneratedFileName,
expectedOutput);
}
}
}
}

0 comments on commit 084df40

Please sign in to comment.