Skip to content

Commit 5366211

Browse files
authored
Add support for externally supplied worker extensions project (#2763)
* Refactor SDK to consume output path from WorkerExtensions.csproj * Allow for extension project to be externally supplied * Fix JSON deserialization * Update unit test for order * Fix PublishTests * Fix InnerBuildTests * Suppress OutDir and OutputPath from inner build * Remove directory recreation on generate csproj * Update unit tests * Ensure directories created * Add more csproj generator tests * Update unit tests * Update assertion failure message for investigation * Update datetime assertions in tests * Add short delays for timestamp consistency * update SDK release_notes.md
1 parent 06bfbf2 commit 5366211

14 files changed

+408
-142
lines changed

.reporoot

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
File to mark repo root. Do not edit.

samples/FunctionApp/FunctionApp.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
<ItemGroup>
1111
<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" />
12-
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.18.1" />
12+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="$(SdkVersion)" />
1313
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
1414
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
1515
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Abstractions" Version="1.3.0" />

sdk/Sdk/ExtensionsCsprojGenerator.cs

+6-20
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ namespace Microsoft.Azure.Functions.Worker.Sdk
1010
{
1111
internal class ExtensionsCsprojGenerator
1212
{
13-
internal const string ExtensionsProjectName = "WorkerExtensions.csproj";
14-
1513
private readonly IDictionary<string, string> _extensions;
1614
private readonly string _outputPath;
1715
private readonly string _targetFrameworkIdentifier;
@@ -29,32 +27,20 @@ public ExtensionsCsprojGenerator(IDictionary<string, string> extensions, string
2927

3028
public void Generate()
3129
{
32-
var extensionsCsprojFilePath = Path.Combine(_outputPath, ExtensionsProjectName);
33-
3430
string csproj = GetCsProjContent();
35-
if (File.Exists(extensionsCsprojFilePath))
31+
if (File.Exists(_outputPath))
3632
{
37-
string existing = File.ReadAllText(extensionsCsprojFilePath);
33+
string existing = File.ReadAllText(_outputPath);
3834
if (string.Equals(csproj, existing, StringComparison.Ordinal))
3935
{
4036
// If contents are the same, only touch the file to update timestamp.
41-
File.SetLastWriteTimeUtc(extensionsCsprojFilePath, DateTime.UtcNow);
37+
File.SetLastWriteTimeUtc(_outputPath, DateTime.UtcNow);
4238
return;
4339
}
4440
}
4541

46-
RecreateDirectory(_outputPath);
47-
File.WriteAllText(extensionsCsprojFilePath, csproj);
48-
}
49-
50-
private void RecreateDirectory(string directoryPath)
51-
{
52-
if (Directory.Exists(directoryPath))
53-
{
54-
Directory.Delete(directoryPath, recursive: true);
55-
}
56-
57-
Directory.CreateDirectory(directoryPath);
42+
Directory.CreateDirectory(Path.GetDirectoryName(_outputPath));
43+
File.WriteAllText(_outputPath, csproj);
5844
}
5945

6046
internal string GetCsProjContent()
@@ -70,7 +56,7 @@ internal string GetCsProjContent()
7056
}
7157
}
7258

73-
string netSdkVersion = _azureFunctionsVersion.StartsWith(Constants.AzureFunctionsVersion3, StringComparison.OrdinalIgnoreCase) ? "3.1.2" : "4.3.0";
59+
string netSdkVersion = _azureFunctionsVersion.StartsWith(Constants.AzureFunctionsVersion3, StringComparison.OrdinalIgnoreCase) ? "3.1.2" : "4.6.0";
7460

7561
return $@"
7662
<Project Sdk=""Microsoft.NET.Sdk"">

sdk/Sdk/ExtensionsMetadata.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System.Collections.Generic;
@@ -9,6 +9,6 @@ namespace Microsoft.Azure.Functions.Worker.Sdk
99
public class ExtensionsMetadata
1010
{
1111
[JsonPropertyName("extensions")]
12-
public IEnumerable<ExtensionReference>? Extensions { get; set; }
12+
public List<ExtensionReference> Extensions { get; set; } = new List<ExtensionReference>();
1313
}
1414
}

sdk/Sdk/ExtensionsMetadataEnhancer.cs

+60
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System.Collections.Generic;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Reflection;
58
using System.Text.RegularExpressions;
9+
using Mono.Cecil;
610

711
namespace Microsoft.Azure.Functions.Worker.Sdk
812
{
@@ -24,6 +28,34 @@ public static void AddHintPath(IEnumerable<ExtensionReference> extensions)
2428
}
2529
}
2630

31+
public static IEnumerable<ExtensionReference> GetWebJobsExtensions(string fileName)
32+
{
33+
// NOTE: this is an incomplete approach to getting extensions and is intended only for our usages.
34+
// Running this with arbitrary assemblies (especially user supplied) can lead to exceptions.
35+
AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(fileName);
36+
IEnumerable<CustomAttribute> attributes = assembly.Modules.SelectMany(p => p.GetCustomAttributes())
37+
.Where(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.Hosting.WebJobsStartupAttribute");
38+
39+
foreach (CustomAttribute attribute in attributes)
40+
{
41+
CustomAttributeArgument typeProperty = attribute.ConstructorArguments.ElementAtOrDefault(0);
42+
CustomAttributeArgument nameProperty = attribute.ConstructorArguments.ElementAtOrDefault(1);
43+
44+
TypeDefinition typeDef = (TypeDefinition)typeProperty.Value;
45+
string assemblyQualifiedName = Assembly.CreateQualifiedName(
46+
typeDef.Module.Assembly.FullName, GetReflectionFullName(typeDef));
47+
48+
string name = GetName((string)nameProperty.Value, typeDef);
49+
50+
yield return new ExtensionReference
51+
{
52+
Name = name,
53+
TypeName = assemblyQualifiedName,
54+
HintPath = $@"{ExtensionsBinaryDirectoryPath}/{Path.GetFileName(fileName)}",
55+
};
56+
}
57+
}
58+
2759
private static string? GetAssemblyNameOrNull(string? typeName)
2860
{
2961
if (typeName == null)
@@ -40,5 +72,33 @@ public static void AddHintPath(IEnumerable<ExtensionReference> extensions)
4072

4173
return null;
4274
}
75+
76+
// Copying the WebJobsStartup constructor logic from:
77+
// https://github.com/Azure/azure-webjobs-sdk/blob/e5417775bcb8c8d3d53698932ca8e4e265eac66d/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsStartupAttribute.cs#L33-L47.
78+
private static string GetName(string name, TypeDefinition startupTypeDef)
79+
{
80+
if (string.IsNullOrEmpty(name))
81+
{
82+
// for a startup class named 'CustomConfigWebJobsStartup' or 'CustomConfigStartup',
83+
// default to a name 'CustomConfig'
84+
name = startupTypeDef.Name;
85+
int idx = name.IndexOf("WebJobsStartup");
86+
if (idx < 0)
87+
{
88+
idx = name.IndexOf("Startup");
89+
}
90+
if (idx > 0)
91+
{
92+
name = name.Substring(0, idx);
93+
}
94+
}
95+
96+
return name;
97+
}
98+
99+
private static string GetReflectionFullName(TypeReference typeRef)
100+
{
101+
return typeRef.FullName.Replace("/", "+");
102+
}
43103
}
44104
}

0 commit comments

Comments
 (0)