refactoring build; using Mono.Cecil for attribute discovery

This commit is contained in:
Brett Samblanet 2019-11-14 14:34:07 -08:00 коммит произвёл Yogesh Jagadeesan
Родитель d2e97355c3
Коммит 1922f7d495
23 изменённых файлов: 295 добавлений и 221 удалений

Просмотреть файл

@ -64,6 +64,7 @@ Target "GenerateZipToSign" (fun _ ->
|> CreateZip "." (version + "net46.zip") "" 7 true
!! (generatorOutputPath @@ "net461\\Newtonsoft.Json.dll")
++ (generatorOutputPath @@ "net461\\Mono.Cecil.dll")
|> CreateZip "." (version + "net46thirdparty.zip") "" 7 true
!! (packOutputPath @@ "netstandard2.0\\Microsoft.NET.Sdk.Functions.dll")
@ -72,6 +73,7 @@ Target "GenerateZipToSign" (fun _ ->
|> CreateZip "." (version + "netstandard2.zip") "" 7 true
!! (generatorOutputPath @@ "netcoreapp2.1\\Newtonsoft.Json.dll")
++ (generatorOutputPath @@ "netcoreapp2.1\\Mono.Cecil.dll")
|> CreateZip "." (version + "netstandard2thidparty.zip") "" 7 true
)
@ -146,6 +148,7 @@ Target "WaitForSigning" (fun _ ->
| Success file ->
Unzip "tmpBuild" file
MoveFileTo ("tmpBuild" @@ "Newtonsoft.Json.dll", generatorOutputPath @@ "net461\\Newtonsoft.Json.dll")
MoveFileTo ("tmpBuild" @@ "Mono.Cecil.dll", generatorOutputPath @@ "net461\\Mono.Cecil.dll")
| Failure e -> targetError e null |> ignore
CleanDir "tmpBuild"
@ -155,6 +158,7 @@ Target "WaitForSigning" (fun _ ->
| Success file ->
Unzip "tmpBuild" file
MoveFileTo ("tmpBuild" @@ "Newtonsoft.Json.dll", generatorOutputPath @@ "netcoreapp2.1\\Newtonsoft.Json.dll")
MoveFileTo ("tmpBuild" @@ "Mono.Cecil.dll", generatorOutputPath @@ "netcoreapp2.1\\Mono.Cecil.dll")
| Failure e -> targetError e null |> ignore
)

Просмотреть файл

@ -63,6 +63,10 @@
<None Include="$(FunctionsGeneratorOutputPath)\net461\System.ValueTuple.dll">
<Pack>true</Pack>
<PackagePath>tools\net46\</PackagePath>
</None>
<None Include="$(FunctionsGeneratorOutputPath)\net461\Mono.Cecil.dll">
<Pack>true</Pack>
<PackagePath>tools\net46\</PackagePath>
</None>
<None Include="$(FunctionsGeneratorOutputPath)\net461\Microsoft.NET.Sdk.Functions.Generator.exe">
<Pack>true</Pack>
@ -80,6 +84,10 @@
<None Include="$(FunctionsGeneratorOutputPath)\netcoreapp2.1\Newtonsoft.Json.dll">
<Pack>true</Pack>
<PackagePath>tools\netcoreapp2.1\</PackagePath>
</None>
<None Include="$(FunctionsGeneratorOutputPath)\netcoreapp2.1\Mono.Cecil.dll">
<Pack>true</Pack>
<PackagePath>tools\netcoreapp2.1\</PackagePath>
</None>
<None Include="$(FunctionsGeneratorOutputPath)\netcoreapp2.1\Microsoft.NET.Sdk.Functions.Generator.dll">
<Pack>true</Pack>

Просмотреть файл

@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.NET.Sdk.Functions.MakeFunction;
using Mono.Cecil;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
namespace MakeFunctionJson
{
@ -15,9 +16,18 @@ namespace MakeFunctionJson
/// <param name="attribute"></param>
/// <returns></returns>
public static string ToAttributeFriendlyName(this Attribute attribute)
{
return ToAttributeFriendlyName(attribute.GetType().Name);
}
public static string ToAttributeFriendlyName(this CustomAttribute attribute)
{
return ToAttributeFriendlyName(attribute.AttributeType.Name);
}
private static string ToAttributeFriendlyName(string name)
{
const string suffix = nameof(Attribute);
var name = attribute.GetType().Name;
name = name.Substring(0, name.Length - suffix.Length);
return name.ToLowerFirstCharacter();
}
@ -37,15 +47,10 @@ namespace MakeFunctionJson
/// </summary>
/// <param name="attribute"></param>
/// <returns></returns>
public static bool IsWebJobsAttribute(this Attribute attribute)
public static bool IsWebJobsAttribute(this CustomAttribute attribute)
{
#if NET46
return attribute.GetType().GetCustomAttributes().Any(a => a.GetType().FullName == "Microsoft.Azure.WebJobs.Description.BindingAttribute")
|| _supportedAttributes.Contains(attribute.GetType().Name);
#else
return attribute.GetType().GetTypeInfo().GetCustomAttributesData().Any(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.Description.BindingAttribute")
|| _supportedAttributes.Contains(attribute.GetType().Name);
#endif
return attribute.AttributeType.Resolve().CustomAttributes.Any(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.Description.BindingAttribute")
|| _supportedAttributes.Contains(attribute.AttributeType.FullName);
}
/// <summary>
@ -211,14 +216,6 @@ namespace MakeFunctionJson
return "schedule";
}
}
else if (attributeName == "EventHubTriggerAttribute" &&
attribute.GetType().Assembly.GetName().Version.Major == 2)
{
if (propertyName == "EventHubName")
{
return "path";
}
}
else if (attributeName == "ApiHubFileTrigger")
{
if (propertyName == "ConnectionStringSetting")

Просмотреть файл

@ -1,22 +0,0 @@
using System;
using System.Linq;
using System.Reflection;
namespace Microsoft.NET.Sdk.Functions.Generator
{
public static class CustomAttributeDataExtensions
{
public static Attribute ConvertToAttribute(this CustomAttributeData data)
{
var attribute = data.Constructor.Invoke(data.ConstructorArguments.Select(arg => arg.Value).ToArray()) as Attribute;
foreach (var namedArgument in data.NamedArguments)
{
(namedArgument.MemberInfo as PropertyInfo)?.SetValue(attribute, namedArgument.TypedValue.Value, null);
(namedArgument.MemberInfo as FieldInfo)?.SetValue(attribute, namedArgument.TypedValue.Value);
}
return attribute;
}
}
}

Просмотреть файл

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Mono.Cecil;
using Newtonsoft.Json;
namespace MakeFunctionJson
@ -14,7 +15,7 @@ namespace MakeFunctionJson
private bool _functionsInDependencies;
private readonly HashSet<string> _excludedFunctionNames;
private readonly ILogger _logger;
private readonly IDictionary<string, MethodInfo> _functionNamesSet;
private readonly IDictionary<string, MethodDefinition> _functionNamesSet;
private static readonly IEnumerable<string> _functionsArtifacts = new[]
{
@ -34,7 +35,7 @@ namespace MakeFunctionJson
{
throw new ArgumentNullException(nameof(logger));
}
if (string.IsNullOrEmpty(assemblyPath))
{
throw new ArgumentNullException(nameof(assemblyPath));
@ -54,7 +55,7 @@ namespace MakeFunctionJson
{
_outputPath = Path.Combine(Directory.GetCurrentDirectory(), _outputPath);
}
_functionNamesSet = new Dictionary<string, MethodInfo>(StringComparer.OrdinalIgnoreCase);
_functionNamesSet = new Dictionary<string, MethodDefinition>(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
@ -111,11 +112,11 @@ namespace MakeFunctionJson
}
}
public IEnumerable<(FunctionJsonSchema schema, FileInfo outputFile)?> GenerateFunctions(IEnumerable<Type> types)
public IEnumerable<(FunctionJsonSchema schema, FileInfo outputFile)?> GenerateFunctions(IEnumerable<TypeDefinition> types)
{
foreach (var type in types)
{
foreach (var method in type.GetMethods())
foreach (var method in type.Methods)
{
if (method.HasFunctionNameAttribute())
{
@ -129,7 +130,7 @@ namespace MakeFunctionJson
var functionName = method.GetSdkFunctionName();
var artifactName = Path.Combine(functionName, "function.json");
var path = Path.Combine(_outputPath, artifactName);
var relativeAssemblyPath = PathUtility.MakeRelativePath(Path.Combine(_outputPath, "dummyFunctionName"), type.Assembly.Location);
var relativeAssemblyPath = PathUtility.MakeRelativePath(Path.Combine(_outputPath, "dummyFunctionName"), type.Module.FileName);
var functionJson = method.ToFunctionJson(relativeAssemblyPath);
if (CheckAppSettingsAndFunctionName(functionJson, method))
{
@ -144,42 +145,54 @@ namespace MakeFunctionJson
{
if (method.HasNoAutomaticTriggerAttribute() && method.HasTriggerAttribute())
{
_logger.LogWarning($"Method {method.ReflectedType?.FullName}.{method.Name} has both a 'NoAutomaticTrigger' attribute and a trigger attribute. Both can't be used together for an Azure function definition.");
_logger.LogWarning($"Method {method.DeclaringType.GetReflectionFullName()}.{method.Name} has both a 'NoAutomaticTrigger' attribute and a trigger attribute. Both can't be used together for an Azure function definition.");
}
else
{
_logger.LogWarning($"Method {method.ReflectedType?.FullName}.{method.Name} is missing a trigger attribute. Both a trigger attribute and FunctionName attribute are required for an Azure function definition.");
_logger.LogWarning($"Method {method.DeclaringType.GetReflectionFullName()}.{method.Name} is missing a trigger attribute. Both a trigger attribute and FunctionName attribute are required for an Azure function definition.");
}
}
else if (method.HasValidWebJobSdkTriggerAttribute())
{
_logger.LogWarning($"Method {method.ReflectedType?.FullName}.{method.Name} is missing the 'FunctionName' attribute. Both a trigger attribute and 'FunctionName' are required for an Azure function definition.");
_logger.LogWarning($"Method {method.DeclaringType.GetReflectionFullName()}.{method.Name} is missing the 'FunctionName' attribute. Both a trigger attribute and 'FunctionName' are required for an Azure function definition.");
}
}
}
}
}
private bool TryGenerateFunctionJsons()
{
var assembly = Assembly.LoadFrom(_assemblyPath);
var assemblyRoot = Path.GetDirectoryName(_assemblyPath);
var exportedTypes = assembly.ExportedTypes;
var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(assemblyRoot);
var readerParams = new ReaderParameters
{
AssemblyResolver = resolver
};
var module = ModuleDefinition.ReadModule(_assemblyPath, readerParams);
IEnumerable<TypeDefinition> exportedTypes = module.Types;
if (_functionsInDependencies)
{
foreach (var referencedAssembly in assembly.GetReferencedAssemblies())
foreach (var referencedAssembly in module.AssemblyReferences)
{
var tryPath = Path.Combine(assemblyRoot, $"{referencedAssembly.Name}.dll");
try
if (File.Exists(tryPath))
{
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}");
try
{
var loadedModule = ModuleDefinition.ReadModule(tryPath, readerParams);
exportedTypes = exportedTypes.Concat(loadedModule.Types);
}
catch (Exception ex)
{
_logger.LogWarning($"Could not evaluate '{referencedAssembly.Name}' for function types. Exception message: {ex.Message}");
}
}
}
}
@ -192,7 +205,7 @@ namespace MakeFunctionJson
return functions.All(f => f.HasValue);
}
private bool CheckAppSettingsAndFunctionName(FunctionJsonSchema functionJson, MethodInfo method)
private bool CheckAppSettingsAndFunctionName(FunctionJsonSchema functionJson, MethodDefinition method)
{
try
{

Просмотреть файл

@ -1,7 +1,7 @@
using System;
using System.Linq;
using System.Reflection;
using Microsoft.NET.Sdk.Functions.Generator;
using Mono.Cecil;
using Newtonsoft.Json.Linq;
namespace MakeFunctionJson
@ -13,37 +13,37 @@ namespace MakeFunctionJson
/// </summary>
/// <param name="method">method to check if an SDK method or not.</param>
/// <returns>true if <paramref name="method"/> is a WebJobs SDK method. False otherwise.</returns>
public static bool IsWebJobsSdkMethod(this MethodInfo method)
public static bool IsWebJobsSdkMethod(this MethodDefinition method)
{
return method.HasFunctionNameAttribute() && method.HasValidWebJobSdkTriggerAttribute();
}
public static bool HasValidWebJobSdkTriggerAttribute(this MethodInfo method)
public static bool HasValidWebJobSdkTriggerAttribute(this MethodDefinition method)
{
var hasNoAutomaticTrigger = method.HasNoAutomaticTriggerAttribute();
var hasTrigger = method.HasTriggerAttribute();
return (hasNoAutomaticTrigger || hasTrigger) && !(hasNoAutomaticTrigger && hasTrigger);
}
public static bool HasFunctionNameAttribute(this MethodInfo method)
public static bool HasFunctionNameAttribute(this MethodDefinition method)
{
return method.GetCustomAttributesData().FirstOrDefault(d => d.AttributeType.FullName == "Microsoft.Azure.WebJobs.FunctionNameAttribute") != null;
return method.CustomAttributes.FirstOrDefault(d => d.AttributeType.FullName == "Microsoft.Azure.WebJobs.FunctionNameAttribute") != null;
}
public static bool HasNoAutomaticTriggerAttribute(this MethodInfo method)
public static bool HasNoAutomaticTriggerAttribute(this MethodDefinition method)
{
return method.GetCustomAttributesData().FirstOrDefault(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.NoAutomaticTriggerAttribute") != null;
return method.CustomAttributes.FirstOrDefault(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.NoAutomaticTriggerAttribute") != null;
}
public static bool HasTriggerAttribute(this MethodInfo method)
public static bool HasTriggerAttribute(this MethodDefinition method)
{
return method.GetParameters().Any(p => p.IsWebJobSdkTriggerParameter());
return method.Parameters.Any(p => p.IsWebJobSdkTriggerParameter());
}
public static JObject ManualTriggerBinding(this MethodInfo method)
public static JObject ManualTriggerBinding(this MethodDefinition method)
{
var binding = new JObject { ["type"] = "manualTrigger", ["direction"] = "in" };
var stringParameter = method.GetParameters().FirstOrDefault(p => p.ParameterType == typeof(string));
var stringParameter = method.Parameters.FirstOrDefault(p => p.ParameterType.FullName == typeof(string).FullName);
if (stringParameter != null)
{
binding["name"] = stringParameter.Name;
@ -57,13 +57,13 @@ namespace MakeFunctionJson
/// <param name="method">method to convert to a <see cref="FunctionJsonSchema"/> object. The method has to be <see cref="IsWebJobsSdkMethod(MethodInfo)"/> </param>
/// <param name="assemblyPath">This will be the value of <see cref="FunctionJsonSchema.ScriptFile"/> on the returned value.</param>
/// <returns><see cref="FunctionJsonSchema"/> object that represents the passed in <paramref name="method"/>.</returns>
public static FunctionJsonSchema ToFunctionJson(this MethodInfo method, string assemblyPath)
public static FunctionJsonSchema ToFunctionJson(this MethodDefinition method, string assemblyPath)
{
return new FunctionJsonSchema
{
// For every SDK parameter, convert it to a FunctionJson bindings.
// Every parameter can potentially contain more than 1 attribute that will be converted into a binding object.
Bindings = method.HasNoAutomaticTriggerAttribute() ? new[] { method.ManualTriggerBinding() } : method.GetParameters()
Bindings = method.HasNoAutomaticTriggerAttribute() ? new[] { method.ManualTriggerBinding() } : method.Parameters
.Where(p => p.IsWebJobSdkTriggerParameter())
.Select(p => p.ToFunctionJsonBindings())
.SelectMany(i => i)
@ -82,17 +82,17 @@ namespace MakeFunctionJson
/// </summary>
/// <param name="method">method has to be a WebJobs SDK method. <see cref="IsWebJobsSdkMethod(MethodInfo)"/></param>
/// <returns>Function name.</returns>
public static string GetSdkFunctionName(this MethodInfo method)
public static string GetSdkFunctionName(this MethodDefinition method)
{
if (!method.IsWebJobsSdkMethod())
{
throw new ArgumentException($"{nameof(method)} has to be a WebJob SDK function");
}
var functionNameAttribute = method.GetCustomAttributesData().FirstOrDefault(a => a.AttributeType.Name == "FunctionNameAttribute")?.ConvertToAttribute();
if (functionNameAttribute != null)
string functionName = method.CustomAttributes.FirstOrDefault(a => a.AttributeType.Name == "FunctionNameAttribute")?.ConstructorArguments[0].Value.ToString();
if (functionName != null)
{
return functionNameAttribute.GetType().GetProperty("Name").GetValue(functionNameAttribute).ToString();
return functionName;
}
else
{
@ -107,13 +107,16 @@ namespace MakeFunctionJson
/// </summary>
/// <param name="method"></param>
/// <returns>a boolean true or false if the outcome is fixed, a string if the ScriptHost should interpret it</returns>
public static object GetDisabled(this MethodInfo method)
public static object GetDisabled(this MethodDefinition method)
{
var attribute = method.GetParameters().Select(p => p.GetDisabledAttribute()).Where(a => a != null).FirstOrDefault() ??
var customAttribute = method.Parameters.Select(p => p.GetDisabledAttribute()).Where(a => a != null).FirstOrDefault() ??
method.GetDisabledAttribute() ??
method.DeclaringType.GetTypeInfo().GetDisabledAttribute();
if (attribute != null)
method.DeclaringType.GetDisabledAttribute();
if (customAttribute != null)
{
var attribute = customAttribute.ToReflection();
// With a SettingName defined, just put that as string. The ScriptHost will evaluate it.
var settingName = attribute.GetValue<string>("SettingName");
if (!string.IsNullOrEmpty(settingName))
@ -141,9 +144,9 @@ namespace MakeFunctionJson
/// </summary>
/// <param name="method"></param>
/// <returns></returns>
public static Attribute GetDisabledAttribute(this MethodInfo method)
public static CustomAttribute GetDisabledAttribute(this MethodDefinition method)
{
return method.GetCustomAttributesData().FirstOrDefault(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.DisableAttribute")?.ConvertToAttribute();
return method.CustomAttributes.FirstOrDefault(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.DisableAttribute");
}
/// <summary>
@ -155,7 +158,7 @@ namespace MakeFunctionJson
/// <param name="method"></param>
/// <param name="error"></param>
/// <returns></returns>
public static bool HasUnsuportedAttributes(this MethodInfo method, out string error)
public static bool HasUnsuportedAttributes(this MethodDefinition method, out string error)
{
error = string.Empty;
var disabled = method.GetDisabled();

Просмотреть файл

@ -18,11 +18,13 @@
<PackageReference Include="Newtonsoft.Json" Version="[11.0.2]" />
<PackageReference Include="System.Runtime.Handles" Version="4.3.0" />
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
<PackageReference Include="Mono.Cecil" Version="0.11.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net461'">
<PackageReference Include="Newtonsoft.Json" Version="[9.0.1]" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
<PackageReference Include="Mono.Cecil" Version="0.11.1" />
</ItemGroup>

Просмотреть файл

@ -1,8 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Mono.Cecil;
using Newtonsoft.Json.Linq;
using System;
namespace MakeFunctionJson
{
@ -13,10 +12,10 @@ namespace MakeFunctionJson
/// </summary>
/// <param name="parameterInfo"></param>
/// <returns></returns>
public static bool IsWebJobSdkTriggerParameter(this ParameterInfo parameterInfo)
public static bool IsWebJobSdkTriggerParameter(this ParameterDefinition parameterInfo)
{
return parameterInfo
.GetCustomAttributes()
.CustomAttributes
.Any(a => a.IsWebJobsAttribute() && a.ToAttributeFriendlyName().IndexOf("Trigger") > -1);
}
@ -25,11 +24,10 @@ namespace MakeFunctionJson
/// </summary>
/// <param name="parameterInfo">Has to be a WebJobSdkParameter <see cref="IsWebJobsSdkParameter(ParameterInfo)"/></param>
/// <returns></returns>
public static IEnumerable<JObject> ToFunctionJsonBindings(this ParameterInfo parameterInfo)
public static IEnumerable<JObject> ToFunctionJsonBindings(this ParameterDefinition parameterInfo)
{
return parameterInfo
.GetCustomAttributes()
.CustomAttributes
.Where(a => a.IsWebJobsAttribute()) // this has to return at least 1.
.Select(a => TypeUtility.GetResolvedAttribute(parameterInfo, a)) // For IConnectionProvider logic.
.Select(a => a.ToJObject()) // Convert the Attribute into a JObject.
@ -47,9 +45,9 @@ namespace MakeFunctionJson
/// </summary>
/// <param name="parameterInfo"></param>
/// <returns></returns>
public static Attribute GetDisabledAttribute(this ParameterInfo parameterInfo)
public static CustomAttribute GetDisabledAttribute(this ParameterDefinition parameterInfo)
{
return parameterInfo.GetCustomAttributes().FirstOrDefault(a => a.GetType().FullName == "Microsoft.Azure.WebJobs.DisableAttribute");
return parameterInfo.CustomAttributes.FirstOrDefault(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.DisableAttribute");
}
}
}

Просмотреть файл

@ -1,6 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
#if NETCOREAPP2_1
using System.Runtime.Loader;
#endif
using MakeFunctionJson;
namespace Microsoft.NET.Sdk.Functions.Console
@ -19,7 +24,19 @@ namespace Microsoft.NET.Sdk.Functions.Console
var assemblyPath = args[0].Trim();
var outputPath = args[1].Trim();
var functionsInDependencies = bool.Parse(args[2].Trim());
var assemblyDir = Path.GetDirectoryName(assemblyPath);
#if NETCOREAPP2_1
AssemblyLoadContext.Default.Resolving += (context, assemblyName) =>
{
return context.LoadFromAssemblyPath(Path.Combine(assemblyDir, assemblyName.Name + ".dll"));
};
#else
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) =>
{
return Assembly.LoadFrom(Path.Combine(assemblyDir, e.Name + ".dll"));
};
#endif
IEnumerable<string> excludedFunctionNames = Enumerable.Empty<string>();
if (args.Length > 2)

Просмотреть файл

@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Reflection;
using Mono.Cecil;
namespace MakeFunctionJson
{
@ -11,9 +12,9 @@ namespace MakeFunctionJson
return typeInfo.ImplementedInterfaces.Any(i => i.Name.Equals(interfaceName, StringComparison.OrdinalIgnoreCase));
}
public static Attribute GetDisabledAttribute(this TypeInfo type)
public static CustomAttribute GetDisabledAttribute(this TypeDefinition type)
{
return type.GetCustomAttributes().FirstOrDefault(a => a.GetType().FullName == "Microsoft.Azure.WebJobs.DisableAttribute");
return type.CustomAttributes.FirstOrDefault(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.DisableAttribute");
}
}
}

Просмотреть файл

@ -1,6 +1,11 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
#if NETCOREAPP2_1
using System.Runtime.Loader;
#endif
using Mono.Cecil;
namespace MakeFunctionJson
{
@ -15,7 +20,7 @@ namespace MakeFunctionJson
/// </summary>
/// <param name="parameter">The parameter to check.</param>
/// <param name="attributeType">The attribute type to look for.</param>
private static Attribute GetHierarchicalAttributeOrNull(ParameterInfo parameter, Type attributeType)
private static CustomAttribute GetHierarchicalAttributeOrNull(ParameterDefinition parameter, Type attributeType)
{
if (parameter == null)
{
@ -28,7 +33,7 @@ namespace MakeFunctionJson
return attribute;
}
var method = parameter.Member as MethodInfo;
var method = parameter.Method as MethodDefinition;
if (method == null)
{
return null;
@ -42,7 +47,7 @@ namespace MakeFunctionJson
/// </summary>
/// <param name="method">The method to check.</param>
/// <param name="type">The attribute type to look for.</param>
private static Attribute GetHierarchicalAttributeOrNull(MethodInfo method, Type type)
private static CustomAttribute GetHierarchicalAttributeOrNull(Mono.Cecil.MethodDefinition method, Type type)
{
var attribute = method.GetCustomAttribute(type);
if (attribute != null)
@ -50,7 +55,7 @@ namespace MakeFunctionJson
return attribute;
}
attribute = method.DeclaringType.GetTypeInfo().GetCustomAttribute(type);
attribute = method.DeclaringType.GetCustomAttribute(type);
if (attribute != null)
{
return attribute;
@ -59,8 +64,10 @@ namespace MakeFunctionJson
return null;
}
internal static Attribute GetResolvedAttribute(ParameterInfo parameter, Attribute attribute)
internal static Attribute GetResolvedAttribute(ParameterDefinition parameter, CustomAttribute customAttribute)
{
Attribute attribute = customAttribute.ToReflection();
if (attribute != null &&
attribute.GetType().GetTypeInfo().IsImplementing("IConnectionProvider") &&
string.IsNullOrEmpty(attribute.GetValue<string>("Connection")))
@ -75,14 +82,14 @@ namespace MakeFunctionJson
if (connectionProviderAttribute?.GetValue<Type>("ProviderType") != null)
{
var connectionOverrideProvider = GetHierarchicalAttributeOrNull(parameter, connectionProviderAttribute.GetValue<Type>("ProviderType"));
var connectionOverrideProvider = GetHierarchicalAttributeOrNull(parameter, connectionProviderAttribute.GetValue<Type>("ProviderType"))?.ToReflection();
if (connectionOverrideProvider != null &&
connectionOverrideProvider.GetType().GetTypeInfo().IsImplementing("IConnectionProvider"))
{
var iConnectionProvider = connectionOverrideProvider.GetType().GetTypeInfo().GetInterface("IConnectionProvider");
var propertyInfo = iConnectionProvider.GetProperty("Connection");
var connectionValue = (string) propertyInfo.GetValue(attribute);
connectionValue = connectionValue
var connectionValue = (string)propertyInfo.GetValue(attribute);
connectionValue = connectionValue
?? connectionOverrideProvider.GetValue<string>("Connection")
?? connectionOverrideProvider.GetValue<string>("Account");
if (!string.IsNullOrEmpty(connectionValue))
@ -95,5 +102,74 @@ namespace MakeFunctionJson
return attribute;
}
public static Attribute ToReflection(this CustomAttribute customAttribute)
{
var attributeType = customAttribute.AttributeType.ToReflectionType();
Type[] constructorParams = customAttribute.Constructor.Parameters
.Select(p => p.ParameterType.ToReflectionType())
.ToArray();
Attribute attribute = attributeType.GetConstructor(constructorParams)
.Invoke(customAttribute.ConstructorArguments.Select(p => NormalizeArg(p)).ToArray()) as Attribute;
foreach (var namedArgument in customAttribute.Properties)
{
attributeType.GetProperty(namedArgument.Name)?.SetValue(attribute, namedArgument.Argument.Value);
attributeType.GetField(namedArgument.Name)?.SetValue(attribute, namedArgument.Argument.Value);
}
return attribute;
}
public static Type ToReflectionType(this TypeReference typeDef)
{
Type t = Type.GetType(typeDef.GetReflectionFullName());
if (t == null)
{
#if NETCOREAPP2_1
Assembly a = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(typeDef.Resolve().Module.FileName));
#else
Assembly a = Assembly.LoadFrom(Path.GetFullPath(typeDef.Resolve().Module.FileName));
#endif
t = a.GetType(typeDef.GetReflectionFullName());
}
return t;
}
private static object NormalizeArg(CustomAttributeArgument arg)
{
if (arg.Type.IsArray)
{
var arguments = arg.Value as CustomAttributeArgument[];
Type arrayType = arg.Type.GetElementType().ToReflectionType();
var array = Array.CreateInstance(arrayType, arguments.Length);
for (int i = 0; i < array.Length; i++)
{
array.SetValue(arguments[i].Value, i);
}
return array;
}
if (arg.Value is TypeDefinition typeDef)
{
return typeDef.ToReflectionType();
}
return arg.Value;
}
public static CustomAttribute GetCustomAttribute(this Mono.Cecil.ICustomAttributeProvider provider, Type parameterType)
{
return provider.CustomAttributes.SingleOrDefault(p => p.AttributeType.FullName == parameterType.FullName);
}
public static string GetReflectionFullName(this TypeReference typeRef)
{
return typeRef.FullName.Replace("/", "+");
}
}
}

Просмотреть файл

@ -31,7 +31,7 @@ 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()
@ -44,7 +44,7 @@ namespace Microsoft.NET.Sdk.Functions.Tasks
}
string taskAssemblyDirectory = Path.GetDirectoryName(typeof(GenerateFunctions).GetTypeInfo().Assembly.Location);
string baseDirectory = Path.GetDirectoryName(taskAssemblyDirectory);
string baseDirectory = Path.GetDirectoryName(taskAssemblyDirectory);
ProcessStartInfo processStartInfo = null;
#if NET46
processStartInfo = GetProcessStartInfo(baseDirectory, isCore: false);
@ -68,9 +68,9 @@ namespace Microsoft.NET.Sdk.Functions.Tasks
var output = process.StandardOutput.ReadToEnd();
var error = process.StandardError.ReadToEnd();
process.WaitForExit();
if (!string.IsNullOrEmpty(output))
{
if (!string.IsNullOrEmpty(output))
{
Log.LogWarning(output);
}
@ -111,7 +111,7 @@ namespace Microsoft.NET.Sdk.Functions.Tasks
RedirectStandardOutput = true,
WorkingDirectory = workingDirectory,
FileName = exePath,
Arguments = arguments
Arguments = arguments
};
}
}

Просмотреть файл

@ -1,9 +1,10 @@
using System;
using System.Reflection;
using FluentAssertions;
using MakeFunctionJson;
using Microsoft.Azure.WebJobs;
using Mono.Cecil;
using Xunit;
using System.Reflection;
namespace Microsoft.NET.Sdk.Functions.Test
{
@ -53,8 +54,8 @@ namespace Microsoft.NET.Sdk.Functions.Test
[InlineData(typeof(FunctionsClass3), "Run", false)]
public void MethodsWithDisabledParametersShouldBeDisabled(Type type, string methodName, object expectedIsDisabled)
{
var method = type.GetMethod(methodName);
var funcJson = method.ToFunctionJson(string.Empty);
MethodDefinition methodDef = TestUtility.GetMethodDefinition(type, methodName);
FunctionJsonSchema funcJson = methodDef.ToFunctionJson(string.Empty);
funcJson.Disabled.Should().Be(expectedIsDisabled);
}
}

Просмотреть файл

@ -1,30 +0,0 @@
using FluentAssertions;
using FluentAssertions.Json;
using MakeFunctionJson;
using Microsoft.Azure.WebJobs.ServiceBus;
using Xunit;
namespace Microsoft.NET.Sdk.Functions.Test
{
public class EventHubAttributeTests
{
[Fact]
public static void EventHubTriggerAttribute_ShouldHaveV1vsV2Differences()
{
var attribute = new EventHubTriggerAttribute("eventHub");
var jObject = attribute.ToJObject();
#if NET46
jObject.Should().HaveElement("path");
jObject["path"].Should().Be("eventHub");
#else
jObject.Should().HaveElement("type");
jObject["type"].Should().Be("eventHubTrigger");
jObject.Should().HaveElement("path");
jObject["path"].Should().Be("eventHub");
#endif
}
}
}

Просмотреть файл

@ -4,9 +4,8 @@ using System.Linq;
using System.Net.Http;
using FluentAssertions;
using MakeFunctionJson;
using Microsoft.Azure.EventHubs;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.ServiceBus;
using Microsoft.ServiceBus.Messaging;
using Microsoft.WindowsAzure.Storage.Queue;
using Xunit;
@ -18,25 +17,25 @@ namespace Microsoft.NET.Sdk.Functions.Test
{
[FunctionName("MyHttpTrigger")]
public static void Run1([HttpTrigger] HttpRequestMessage request) { }
[FunctionName("MyBlobTrigger")]
public static void Run2([BlobTrigger("blob.txt")] string blobContent) { }
[FunctionName("MyQueueTrigger")]
public static void Run3([QueueTrigger("queue")] CloudQueue queue) { }
[FunctionName("MyEventHubTrigger")]
public static void Run4([EventHubTrigger("hub")] EventData message) { }
[FunctionName("MyTimerTrigger")]
public static void Run5([TimerTrigger("00:30:00")] TimerInfo timer) { }
[FunctionName("MyServiceBusTrigger")]
public static void Run6([ServiceBusTrigger("queue")] string message) { }
[FunctionName("MyManualTrigger"), NoAutomaticTrigger]
public static void Run7(string input) { }
[FunctionName("MyManualTriggerWithoutParameters"), NoAutomaticTrigger]
public static void Run8() { }
}
@ -53,16 +52,16 @@ namespace Microsoft.NET.Sdk.Functions.Test
public void FunctionMethodsAreExported(string functionName, string type, string parameterName)
{
var logger = new RecorderLogger();
var converter = new FunctionJsonConverter(logger, ".", ".", functionsInDependencies:false);
var functions = converter.GenerateFunctions(new [] {typeof(FunctionsClass)});
var converter = new FunctionJsonConverter(logger, ".", ".", functionsInDependencies: false);
var functions = converter.GenerateFunctions(new[] { TestUtility.GetTypeDefinition(typeof(FunctionsClass)) });
var schema = functions.Single(e => Path.GetFileName(e.Value.outputFile.DirectoryName) == functionName).Value.schema;
var binding = schema.Bindings.Single();
var binding = schema.Bindings.Single();
binding.Value<string>("type").Should().Be(type);
binding.Value<string>("name").Should().Be(parameterName);
logger.Errors.Should().BeEmpty();
logger.Warnings.Should().BeEmpty();
}
public class InvalidFunctionBecauseOfMissingTrigger
{
[FunctionName("MyServiceBusTrigger")]
@ -79,7 +78,7 @@ namespace Microsoft.NET.Sdk.Functions.Test
[FunctionName("MyServiceBusTrigger"), NoAutomaticTrigger]
public static void Run([ServiceBusTrigger("queue")] string message) { }
}
[Theory]
[InlineData(typeof(InvalidFunctionBecauseOfMissingTrigger), "Method Microsoft.NET.Sdk.Functions.Test.FunctionJsonConverterTests+InvalidFunctionBecauseOfMissingTrigger.Run is missing a trigger attribute. Both a trigger attribute and FunctionName attribute are required for an Azure function definition.")]
// [InlineData(typeof(InvalidFunctionBecauseOfMissingFunctionName), "Method Microsoft.NET.Sdk.Functions.Test.FunctionJsonConverterTests+InvalidFunctionBecauseOfMissingFunctionName.Run is missing the 'FunctionName' attribute. Both a trigger attribute and 'FunctionName' are required for an Azure function definition.")]
@ -87,8 +86,8 @@ namespace Microsoft.NET.Sdk.Functions.Test
public void InvalidFunctionMethodProducesWarning(Type type, string warningMessage)
{
var logger = new RecorderLogger();
var converter = new FunctionJsonConverter(logger, ".", ".", functionsInDependencies:false);
var functions = converter.GenerateFunctions(new [] {type});
var converter = new FunctionJsonConverter(logger, ".", ".", functionsInDependencies: false);
var functions = converter.GenerateFunctions(new[] { TestUtility.GetTypeDefinition(type) });
functions.Should().BeEmpty();
logger.Errors.Should().BeEmpty();
logger.Warnings.Should().ContainSingle();

Просмотреть файл

@ -1,26 +1,32 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using MakeFunctionJson;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.ServiceBus;
using Mono.Cecil;
using Xunit;
namespace Microsoft.NET.Sdk.Functions.Test
{
public class GeneralWebJobsAttributesTests
{
private static void FakeFunction(
[QueueTrigger("a")]
[BlobTrigger("b")]
[EventHubTrigger("c")]
[ServiceBusTrigger("d")] string abcd)
{
}
public static IEnumerable<object[]> GetAttributes()
{
yield return new object[] { new QueueTriggerAttribute(string.Empty) };
yield return new object[] { new BlobTriggerAttribute(string.Empty) };
yield return new object[] { new EventHubTriggerAttribute(string.Empty) };
yield return new object[] { new ServiceBusTriggerAttribute(string.Empty) };
return TestUtility.GetCustomAttributes(typeof(GeneralWebJobsAttributesTests), "FakeFunction", "abcd")
.Select(p => new object[] { p });
}
[Theory]
[MemberData(nameof(GetAttributes))]
public void IsWebJobsAttribute(Attribute attribute)
public void IsWebJobsAttribute(CustomAttribute attribute)
{
attribute.IsWebJobsAttribute().Should().BeTrue(because: $"{attribute.GetType().FullName} is a WebJob's attribute");
}

Просмотреть файл

@ -1,12 +1,8 @@
using FluentAssertions;
using System;
using System.Reflection;
using FluentAssertions;
using MakeFunctionJson;
using Microsoft.Azure.WebJobs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.NET.Sdk.Functions.Test
@ -43,7 +39,7 @@ namespace Microsoft.NET.Sdk.Functions.Test
[InlineData(typeof(FunctionsClass1), "Run6", true)]
public void HasUnsupportedAttributesWorksCorrectly(Type type, string methodName, bool expected)
{
var method = type.GetMethod(methodName);
var method = TestUtility.GetMethodDefinition(type, methodName);
var hasUnsuportedAttribute = method.HasUnsuportedAttributes(out string _);
hasUnsuportedAttribute.Should().Be(expected);
}

Просмотреть файл

@ -3,8 +3,6 @@ using FluentAssertions.Json;
using MakeFunctionJson;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using System;
using System.Linq;
using Xunit;
namespace Microsoft.NET.Sdk.Functions.Test
@ -22,18 +20,5 @@ namespace Microsoft.NET.Sdk.Functions.Test
jObject.Should().HaveElement("authLevel");
jObject["authLevel"].Should().Be("function");
}
[Fact]
public void HttpTriggerAttributeWithWebHookTypeShouldntHaveAnAuthLevel()
{
var attribute = new HttpTriggerAttribute()
{
WebHookType = "something"
};
var jObject = attribute.ToJObject();
jObject["authLevel"].Should().BeNull();
}
}
}

Просмотреть файл

@ -55,8 +55,10 @@ namespace Microsoft.NET.Sdk.Functions.Test
[InlineData(typeof(FunctionsClass4), "foobarfoobar")]
public void TestIConnectionProviderHierarchicalLogic(Type type, string expected)
{
var parameterInfo = type.GetMethod("Run").GetParameters().First();
var attribute = (Attribute) parameterInfo.GetCustomAttributes(typeof(QueueTriggerAttribute), false).First();
var method = TestUtility.GetMethodDefinition(type, "Run");
var parameterInfo = method.Parameters.First();
var attribute = parameterInfo.GetCustomAttribute(typeof(QueueTriggerAttribute));
var resolvedAttribute = TypeUtility.GetResolvedAttribute(parameterInfo, attribute);

Просмотреть файл

@ -125,12 +125,21 @@
<Reference Include="Microsoft.WindowsAzure.Storage, Version=8.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\WindowsAzure.Storage.8.6.0\lib\net45\Microsoft.WindowsAzure.Storage.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil, Version=0.11.1.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\..\packages\Mono.Cecil.0.11.1\lib\net40\Mono.Cecil.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Mdb, Version=0.11.1.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\..\packages\Mono.Cecil.0.11.1\lib\net40\Mono.Cecil.Mdb.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Pdb, Version=0.11.1.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\..\packages\Mono.Cecil.0.11.1\lib\net40\Mono.Cecil.Pdb.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Rocks, Version=0.11.1.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\..\packages\Mono.Cecil.0.11.1\lib\net40\Mono.Cecil.Rocks.dll</HintPath>
</Reference>
<Reference Include="NCrontab, Version=3.2.20120.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\ncrontab.3.3.0\lib\net35\NCrontab.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NSubstitute, Version=2.0.3.0, Culture=neutral, PublicKeyToken=92dd2e9066daa5ca, processorArchitecture=MSIL">
<HintPath>..\..\packages\NSubstitute.2.0.3\lib\net45\NSubstitute.dll</HintPath>
</Reference>
@ -225,18 +234,22 @@
<Reference Include="xunit.execution.desktop, Version=2.3.1.3858, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\..\packages\xunit.extensibility.execution.2.3.1\lib\net452\xunit.execution.desktop.dll</HintPath>
</Reference>
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.EventHubs" Version="3.0.6" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.ServiceBus" Version="3.2.0" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="3.0.10" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="Microsoft.Azure.WebJobs" Version="3.0.14" />
</ItemGroup>
<ItemGroup>
<Compile Include="DisableAttributeTests.cs" />
<Compile Include="EventHubAttributeTests.cs" />
<Compile Include="GeneralWebJobsAttributesTests.cs" />
<Compile Include="HasUnsupportedAttributesTests.cs" />
<Compile Include="HttpTriggerTests.cs" />
<Compile Include="IConnectionProviderTests.cs" />
<Compile Include="FunctionJsonConverterTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServiceBusTriggerTests.cs" />
<Compile Include="RecorderLogger.cs" />
<Compile Include="TestUtility.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />

Просмотреть файл

@ -1,24 +0,0 @@
using FluentAssertions;
using FluentAssertions.Json;
using MakeFunctionJson;
using Microsoft.Azure.WebJobs;
using Microsoft.ServiceBus.Messaging;
using Xunit;
namespace Microsoft.NET.Sdk.Functions.Test
{
public class ServiceBusTriggerTests
{
[Fact]
// https://github.com/Azure/azure-functions-vs-build-sdk/issues/1
public void ServiceBusTriggerShouldHaveStringEnumForAccessRights()
{
var attribute = new ServiceBusTriggerAttribute("queue1", AccessRights.Manage);
var jObject = attribute.ToJObject();
jObject.Should().HaveElement("accessRights");
jObject["accessRights"].Should().Be("manage");
}
}
}

Просмотреть файл

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
namespace Microsoft.NET.Sdk.Functions.Test
{
public static class TestUtility
{
public static MethodDefinition GetMethodDefinition(Type type, string methodName)
{
return GetTypeDefinition(type).Methods.SingleOrDefault(p => p.Name == methodName);
}
public static TypeDefinition GetTypeDefinition(Type type)
{
var module = ModuleDefinition.ReadModule(type.Assembly.Location);
return module.GetType(type.FullName.Replace("+", "/"));
}
public static IEnumerable<CustomAttribute> GetCustomAttributes(Type type, string methodName, string parameterName)
{
var methodDef = GetMethodDefinition(type, methodName);
var paramDef = methodDef.Parameters.SingleOrDefault(p => p.Name == parameterName);
return paramDef.CustomAttributes;
}
}
}

Просмотреть файл

@ -28,6 +28,7 @@
<package id="Microsoft.NETCore.Platforms" version="1.1.0" targetFramework="net46" />
<package id="Microsoft.Tpl.Dataflow" version="4.5.24" targetFramework="net46" />
<package id="Microsoft.Win32.Primitives" version="4.3.0" targetFramework="net46" />
<package id="Mono.Cecil" version="0.11.1" targetFramework="net461" />
<package id="ncrontab" version="3.3.0" targetFramework="net46" />
<package id="NETStandard.Library" version="1.6.1" targetFramework="net46" />
<package id="Newtonsoft.Json" version="11.0.2" targetFramework="net461" />