Generator scans assembly's dependencies for functions. Addresses #274.
* This functionality is off by default; to enable, set the project property FunctionsInDependencies to True. * Compilers can be too smart. To ensure that a referenced project's DLL is included in the assembly's dependencies, make sure the assembly's code explicitly references a member or type from the referenced project.
This commit is contained in:
Родитель
2d39aded66
Коммит
101e4ced4f
|
@ -1,9 +1,10 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net461</TargetFramework>
|
||||
<FunctionsInDependencies>true</FunctionsInDependencies>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.15" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.29" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FunctionsRefClassicClassLib\FunctionsRefClassicClassLib.csproj" />
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
using FunctionsRefSdkClassLib;
|
||||
|
||||
namespace FunctionAppNETFramework
|
||||
{
|
||||
// Uses references so compiler won't strip them out of the managed module.
|
||||
class RefUser
|
||||
{
|
||||
public Type FunctionsRefSdkClassLibStandard { get => typeof(HttpTriggerRefSdkNETFramework); }
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace FunctionsRefSdkClassLib
|
||||
{
|
||||
public class Class1
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,7 +1,11 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net461</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.29" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Azure.WebJobs.Host;
|
||||
|
||||
namespace FunctionsRefSdkClassLib
|
||||
{
|
||||
public class HttpTriggerRefSdkNETFramework
|
||||
{
|
||||
[FunctionName("HttpTriggerRefSdkNETFramework")]
|
||||
public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
|
||||
{
|
||||
//log.Info("C# HTTP trigger function processed a request.");
|
||||
|
||||
// parse query parameter
|
||||
string name = req.GetQueryNameValuePairs()
|
||||
.FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
|
||||
.Value;
|
||||
|
||||
return name == null
|
||||
? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
|
||||
: req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,6 +26,22 @@ namespace UnitTestProject1
|
|||
Assert.AreEqual("\"Hello test\"", responseString);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ClassicUnitTestProject_Test_NETFx_Ref()
|
||||
{
|
||||
var requestMessage = new HttpRequestMessage(HttpMethod.Get, "https://functions.azurewebsites.net?name=test")
|
||||
{
|
||||
Content = new StringContent(""),
|
||||
Properties = { { HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration() } }
|
||||
};
|
||||
|
||||
var response = await HttpTriggerNETFramework.Run(requestMessage, null);
|
||||
|
||||
string responseString = await response.Content.ReadAsStringAsync();
|
||||
Assert.IsTrue(response.IsSuccessStatusCode);
|
||||
Assert.AreEqual("\"Hello test\"", responseString);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ClassicUnitTestProject_Empty()
|
||||
{
|
||||
|
|
|
@ -26,6 +26,22 @@ namespace UnitTestProject2
|
|||
Assert.AreEqual("\"Hello test\"", responseString);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task NewProjectSystem_MsTest_NETFx_Ref()
|
||||
{
|
||||
var requestMessage = new HttpRequestMessage(HttpMethod.Get, "https://functions.azurewebsites.net?name=test")
|
||||
{
|
||||
Content = new StringContent(""),
|
||||
Properties = { { HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration() } }
|
||||
};
|
||||
|
||||
var response = await HttpTriggerNETFramework.Run(requestMessage, null);
|
||||
|
||||
string responseString = await response.Content.ReadAsStringAsync();
|
||||
Assert.IsTrue(response.IsSuccessStatusCode);
|
||||
Assert.AreEqual("\"Hello test\"", responseString);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NewProjectSystem_MsTest_Empty()
|
||||
{
|
||||
|
|
|
@ -26,6 +26,21 @@ namespace XUnitTestProject1
|
|||
Assert.Equal("\"Hello test\"", responseString);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task XUnitTest_NETFx_Ref()
|
||||
{
|
||||
var requestMessage = new HttpRequestMessage(HttpMethod.Get, "https://functions.azurewebsites.net?name=test")
|
||||
{
|
||||
Content = new StringContent(""),
|
||||
Properties = { { HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration() } }
|
||||
};
|
||||
|
||||
var response = await HttpTriggerNETFramework.Run(requestMessage, null);
|
||||
|
||||
string responseString = await response.Content.ReadAsStringAsync();
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
Assert.Equal("\"Hello test\"", responseString);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NewProjectSystem_XUnit_Empty()
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<FunctionsInDependencies>true</FunctionsInDependencies>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.14" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.29" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
|
|
|
@ -2,7 +2,6 @@ using System.Linq;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Host;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
using FunctionsRefNETStandard;
|
||||
|
||||
namespace FunctionAppNETStandard
|
||||
{
|
||||
// Uses references so compiler won't strip them out of the managed module.
|
||||
class RefUser
|
||||
{
|
||||
public Type FunctionsRefNetStandard { get => typeof(HttpTriggerRefNETStandard); }
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace FunctionsRefNETStandard
|
||||
{
|
||||
public class Class1
|
||||
{
|
||||
}
|
||||
}
|
|
@ -4,4 +4,8 @@
|
|||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.29" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Host;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace FunctionsRefNETStandard
|
||||
{
|
||||
public class HttpTriggerRefNETStandard
|
||||
{
|
||||
[FunctionName("HttpTriggerRefNETStandard")]
|
||||
public static IActionResult Run([HttpTrigger]HttpRequest req, ILogger log)
|
||||
{
|
||||
//log.Info("C# HTTP trigger function processed a request.");
|
||||
|
||||
if (req.Query.TryGetValue("name", out StringValues value))
|
||||
{
|
||||
return new OkObjectResult($"Hello, {value.First()}");
|
||||
}
|
||||
|
||||
return new BadRequestObjectResult("Please pass a name on the query string");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using FunctionAppNETStandard;
|
||||
using FunctionsRefNETStandard;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
@ -22,5 +23,17 @@ namespace UnitTestNETFramework
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NewProjectSystem_MsTest_NETFx_Ref()
|
||||
{
|
||||
HttpRequest httpRequest = new DefaultHttpRequest();
|
||||
var response = HttpTriggerRefNETStandard.Run(httpRequest, null);
|
||||
|
||||
if (response is OkObjectResult)
|
||||
{
|
||||
Assert.AreEqual("Hello, test", ((OkObjectResult)response).Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ using System.Net.Http;
|
|||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using FunctionAppNETStandard;
|
||||
using FunctionsRefNETStandard;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
@ -24,5 +25,17 @@ namespace UnitTestProject2
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NewProjectSystem_MsTest_NETFx_Ref()
|
||||
{
|
||||
HttpRequest httpRequest = new DefaultHttpRequest();
|
||||
var response = HttpTriggerRefNETStandard.Run(httpRequest, null);
|
||||
|
||||
if (response is OkObjectResult)
|
||||
{
|
||||
Assert.AreEqual("Hello, test", ((OkObjectResult)response).Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using FunctionAppNETStandard;
|
||||
using FunctionsRefNETStandard;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Xunit;
|
||||
|
@ -20,5 +21,17 @@ namespace XUnitTestProject1
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NewProjectSystem_MsTest_NETFx_Ref()
|
||||
{
|
||||
HttpRequest httpRequest = new UnitTestProject2.DefaultHttpRequest();
|
||||
var response = HttpTriggerRefNETStandard.Run(httpRequest, null);
|
||||
|
||||
if (response is OkObjectResult)
|
||||
{
|
||||
Assert.Equal("Hello, test", ((OkObjectResult)response).Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace MakeFunctionJson
|
|||
{
|
||||
private string _assemblyPath;
|
||||
private string _outputPath;
|
||||
private bool _functionsInDependencies;
|
||||
private readonly HashSet<string> _excludedFunctionNames;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IDictionary<string, MethodInfo> _functionNamesSet;
|
||||
|
@ -27,7 +28,7 @@ namespace MakeFunctionJson
|
|||
"kafkatrigger"
|
||||
};
|
||||
|
||||
internal FunctionJsonConverter(ILogger logger, string assemblyPath, string outputPath, IEnumerable<string> excludedFunctionNames = null)
|
||||
internal FunctionJsonConverter(ILogger logger, string assemblyPath, string outputPath, bool functionsInDependencies, IEnumerable<string> excludedFunctionNames = null)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
|
@ -47,6 +48,7 @@ namespace MakeFunctionJson
|
|||
_logger = logger;
|
||||
_assemblyPath = assemblyPath;
|
||||
_outputPath = outputPath.Trim('"');
|
||||
_functionsInDependencies = functionsInDependencies;
|
||||
_excludedFunctionNames = new HashSet<string>(excludedFunctionNames ?? Enumerable.Empty<string>(), StringComparer.OrdinalIgnoreCase);
|
||||
if (!Path.IsPathRooted(_outputPath))
|
||||
{
|
||||
|
@ -161,7 +163,28 @@ namespace MakeFunctionJson
|
|||
private bool TryGenerateFunctionJsons()
|
||||
{
|
||||
var assembly = Assembly.LoadFrom(_assemblyPath);
|
||||
var functions = GenerateFunctions(assembly.GetExportedTypes()).ToList();
|
||||
var assemblyRoot = Path.GetDirectoryName(_assemblyPath);
|
||||
|
||||
var exportedTypes = assembly.ExportedTypes;
|
||||
|
||||
if (_functionsInDependencies)
|
||||
{
|
||||
foreach (var referencedAssembly in assembly.GetReferencedAssemblies())
|
||||
{
|
||||
var tryPath = Path.Combine(assemblyRoot, $"{referencedAssembly.Name}.dll");
|
||||
try
|
||||
{
|
||||
var loadedAssembly = Assembly.LoadFrom(tryPath);
|
||||
exportedTypes = exportedTypes.Concat(loadedAssembly.ExportedTypes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Could not evaluate '{referencedAssembly.Name}' for function types. Exception message: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var functions = GenerateFunctions(exportedTypes).ToList();
|
||||
foreach (var function in functions.Where(f => f.HasValue && !f.Value.outputFile.Exists).Select(f => f.Value))
|
||||
{
|
||||
function.schema.Serialize(function.outputFile.FullName);
|
||||
|
|
|
@ -10,14 +10,16 @@ namespace Microsoft.NET.Sdk.Functions.Console
|
|||
private static void Main(string[] args)
|
||||
{
|
||||
var logger = new ConsoleLogger();
|
||||
if (args.Length < 2 || args.Length > 3)
|
||||
if (args.Length < 3 || args.Length > 4)
|
||||
{
|
||||
logger.LogError("USAGE: <assemblyPath> <outputPath> <excludedFunctionName1;excludedFunctionName2;...>");
|
||||
logger.LogError("USAGE: <assemblyPath> <outputPath> <functionsInDependencies> <excludedFunctionName1;excludedFunctionName2;...>");
|
||||
}
|
||||
else
|
||||
{
|
||||
var assemblyPath = args[0].Trim();
|
||||
var outputPath = args[1].Trim();
|
||||
var functionsInDependencies = bool.Parse(args[2].Trim());
|
||||
|
||||
IEnumerable<string> excludedFunctionNames = Enumerable.Empty<string>();
|
||||
|
||||
if (args.Length > 2)
|
||||
|
@ -26,7 +28,7 @@ namespace Microsoft.NET.Sdk.Functions.Console
|
|||
excludedFunctionNames = excludedFunctionNamesArg.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
var converter = new FunctionJsonConverter(logger, assemblyPath, outputPath, excludedFunctionNames);
|
||||
var converter = new FunctionJsonConverter(logger, assemblyPath, outputPath, functionsInDependencies, excludedFunctionNames);
|
||||
if (!converter.TryRun())
|
||||
{
|
||||
logger.LogError("Error generating functions metadata");
|
||||
|
|
|
@ -43,7 +43,8 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and
|
|||
OutputPath="$(TargetDir)"
|
||||
UseNETCoreGenerator="$(UseNETCoreGenerator)"
|
||||
UseNETFrameworkGenerator="$(UseNETFrameworkGenerator)"
|
||||
UserProvidedFunctionJsonFiles="@(UserProvidedFunctionJsonFiles)" />
|
||||
UserProvidedFunctionJsonFiles="@(UserProvidedFunctionJsonFiles)"
|
||||
FunctionsInDependencies="$(FunctionsInDependencies)"/>
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
|
|
|
@ -148,7 +148,8 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and
|
|||
OutputPath="$(FunctionsOutputPath)"
|
||||
UseNETCoreGenerator="$(UseNETCoreGenerator)"
|
||||
UseNETFrameworkGenerator="$(UseNETFrameworkGenerator)"
|
||||
UserProvidedFunctionJsonFiles="@(UserProvidedFunctionJsonFiles)" />
|
||||
UserProvidedFunctionJsonFiles="@(UserProvidedFunctionJsonFiles)"
|
||||
FunctionsInDependencies="$(FunctionsInDependencies)" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -31,6 +31,8 @@ namespace Microsoft.NET.Sdk.Functions.Tasks
|
|||
public bool GenerateHostJson { get; set; }
|
||||
|
||||
public ITaskItem[] UserProvidedFunctionJsonFiles { get; set; }
|
||||
|
||||
public bool FunctionsInDependencies { get; set; }
|
||||
|
||||
public override bool Execute()
|
||||
{
|
||||
|
@ -89,7 +91,7 @@ namespace Microsoft.NET.Sdk.Functions.Tasks
|
|||
{
|
||||
string workingDirectory = isCore ? Path.Combine(baseLocation, NETStandardFolder) : Path.Combine(baseLocation, NETFrameworkFolder);
|
||||
string exePath = isCore ? DotNetMuxer.MuxerPathOrDefault() : Path.Combine(workingDirectory, "Microsoft.NET.Sdk.Functions.Generator.exe");
|
||||
string arguments = isCore ? $"Microsoft.NET.Sdk.Functions.Generator.dll \"{TargetPath} \" \"{OutputPath} \"" : $"\"{TargetPath} \" \"{OutputPath} \"";
|
||||
string arguments = isCore ? $"Microsoft.NET.Sdk.Functions.Generator.dll \"{TargetPath} \" \"{OutputPath} \" \"{FunctionsInDependencies} \"" : $"\"{TargetPath} \" \"{OutputPath} \" \"{FunctionsInDependencies} \"";
|
||||
|
||||
string excludedFunctionNamesArg = UserProvidedFunctionJsonFiles?
|
||||
.Select(f => f.ItemSpec.Replace("/", @"\").Replace(@"\function.json", string.Empty))
|
||||
|
|
Загрузка…
Ссылка в новой задаче