diff --git a/DotNetWorker.sln b/DotNetWorker.sln index 0fc5bb848..e8aca308b 100644 --- a/DotNetWorker.sln +++ b/DotNetWorker.sln @@ -140,6 +140,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Worker.Extensions.Shared.Te EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Http.AspNetCore.Analyzers", "extensions\Worker.Extensions.Http.AspNetCore.Analyzers\Worker.Extensions.Http.AspNetCore.Analyzers.csproj", "{7B6C2920-7A02-43B2-8DA0-7B76B9FAFC6B}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Net7Worker", "samples\Net7Worker\Net7Worker.csproj", "{BE1F79C3-24FA-4BC8-BAB2-C1AD321B13FF}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependentAssemblyWithFunctions.NetStandard", "test\DependentAssemblyWithFunctions.NetStandard\DependentAssemblyWithFunctions.NetStandard.csproj", "{198DA072-F94F-4585-A744-97B3DAC21686}" EndProject Global @@ -344,6 +346,10 @@ Global {D8E79B53-9A44-46CC-9D7A-E9494FC8CAF4}.Debug|Any CPU.Build.0 = Debug|Any CPU {D8E79B53-9A44-46CC-9D7A-E9494FC8CAF4}.Release|Any CPU.ActiveCfg = Release|Any CPU {D8E79B53-9A44-46CC-9D7A-E9494FC8CAF4}.Release|Any CPU.Build.0 = Release|Any CPU + {BE1F79C3-24FA-4BC8-BAB2-C1AD321B13FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE1F79C3-24FA-4BC8-BAB2-C1AD321B13FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE1F79C3-24FA-4BC8-BAB2-C1AD321B13FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE1F79C3-24FA-4BC8-BAB2-C1AD321B13FF}.Release|Any CPU.Build.0 = Release|Any CPU {B6342174-5436-4846-B43C-39D8E34AE5CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B6342174-5436-4846-B43C-39D8E34AE5CF}.Debug|Any CPU.Build.0 = Debug|Any CPU {B6342174-5436-4846-B43C-39D8E34AE5CF}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -417,6 +423,7 @@ Global {D8E79B53-9A44-46CC-9D7A-E9494FC8CAF4} = {AA4D318D-101B-49E7-A4EC-B34165AEDB33} {B6342174-5436-4846-B43C-39D8E34AE5CF} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} {7B6C2920-7A02-43B2-8DA0-7B76B9FAFC6B} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {BE1F79C3-24FA-4BC8-BAB2-C1AD321B13FF} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} {198DA072-F94F-4585-A744-97B3DAC21686} = {B5821230-6E0A-4535-88A9-ED31B6F07596} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/build/Sdk.slnf b/build/Sdk.slnf new file mode 100644 index 000000000..5921585a6 --- /dev/null +++ b/build/Sdk.slnf @@ -0,0 +1,10 @@ +{ + "solution": { + "path": "..\\DotNetWorker.sln", + "projects": [ + "sdk\\Sdk.Analyzers\\Sdk.Analyzers.csproj", + "sdk\\Sdk.Generators\\Sdk.Generators.csproj", + "sdk\\Sdk\\Sdk.csproj" + ] + } +} \ No newline at end of file diff --git a/samples/FunctionApp/FunctionApp.csproj b/samples/FunctionApp/FunctionApp.csproj index 5edab0f0b..36e8ff2d5 100644 --- a/samples/FunctionApp/FunctionApp.csproj +++ b/samples/FunctionApp/FunctionApp.csproj @@ -7,6 +7,7 @@ <_FunctionsSkipCleanOutput>true true ..\..\key.snk + 1.17.2 DEBUG;TRACE @@ -14,7 +15,7 @@ - + diff --git a/sdk/Sdk/ExtensionsCsprojGenerator.cs b/sdk/Sdk/ExtensionsCsprojGenerator.cs index 384182d71..c08f92677 100644 --- a/sdk/Sdk/ExtensionsCsprojGenerator.cs +++ b/sdk/Sdk/ExtensionsCsprojGenerator.cs @@ -10,7 +10,7 @@ namespace Microsoft.Azure.Functions.Worker.Sdk { internal class ExtensionsCsprojGenerator { - private const string ExtensionsProjectName = "WorkerExtensions.csproj"; + internal const string ExtensionsProjectName = "WorkerExtensions.csproj"; private readonly IDictionary _extensions; private readonly string _outputPath; @@ -31,9 +31,20 @@ public void Generate() { var extensionsCsprojFilePath = Path.Combine(_outputPath, ExtensionsProjectName); - RecreateDirectory(_outputPath); + string csproj = GetCsProjContent(); + if (File.Exists(extensionsCsprojFilePath)) + { + string existing = File.ReadAllText(extensionsCsprojFilePath); + if (string.Equals(csproj, existing, StringComparison.Ordinal)) + { + // If contents are the same, only touch the file to update timestamp. + File.SetLastWriteTimeUtc(extensionsCsprojFilePath, DateTime.UtcNow); + return; + } + } - WriteExtensionsCsProj(extensionsCsprojFilePath); + RecreateDirectory(_outputPath); + File.WriteAllText(extensionsCsprojFilePath, csproj); } private void RecreateDirectory(string directoryPath) @@ -46,13 +57,6 @@ private void RecreateDirectory(string directoryPath) Directory.CreateDirectory(directoryPath); } - private void WriteExtensionsCsProj(string filePath) - { - string csprojContent = GetCsProjContent(); - - File.WriteAllText(filePath, csprojContent); - } - internal string GetCsProjContent() { string extensionReferences = GetExtensionReferences(); diff --git a/sdk/Sdk/FunctionMetadataGenerator.cs b/sdk/Sdk/FunctionMetadataGenerator.cs index 31e30ac09..6fe392c49 100644 --- a/sdk/Sdk/FunctionMetadataGenerator.cs +++ b/sdk/Sdk/FunctionMetadataGenerator.cs @@ -7,6 +7,7 @@ using System.Dynamic; using System.IO; using System.Linq; +using Microsoft.Build.Framework; using Mono.Cecil; using Mono.Collections.Generic; @@ -42,44 +43,33 @@ public IDictionary Extensions } } - public IEnumerable GenerateFunctionMetadata(string assemblyPath, IEnumerable referencePaths) + public IEnumerable GenerateFunctionMetadata(string assemblyPath, IEnumerable referencePaths) { - string sourcePath = Path.GetDirectoryName(assemblyPath); + var targetAssemblies = new List() { assemblyPath }; - var targetAssemblies = new List(Directory.GetFiles(sourcePath, "*.dll")); + // We don't need to scan assemblies that come from a framework (.NET, AspNetCore), they won't have functions types in them. + targetAssemblies.AddRange(referencePaths.Where(x => x.GetMetadata("FrameworkReferenceName") == "").Select(x => x.ItemSpec)); + _logger.LogMessage($"Found {targetAssemblies.Count} assemblies to evaluate."); - if (!assemblyPath.EndsWith(".dll")) + var resolver = new DefaultAssemblyResolver(); + foreach (string referencePath in referencePaths.Select(p => Path.GetDirectoryName(p.ItemSpec)).Distinct()) { - targetAssemblies.Add(assemblyPath); + resolver.AddSearchDirectory(referencePath); } + ReaderParameters readerParams = new ReaderParameters { AssemblyResolver = resolver }; var functions = new List(); - - _logger.LogMessage($"Found {targetAssemblies.Count} assemblies to evaluate in '{sourcePath}':"); - foreach (var path in targetAssemblies) { using (_logger.Indent()) { - _logger.LogMessage($"{Path.GetFileName(path)}"); + _logger.LogMessage($"Collecting function metadata from {Path.GetFileName(path)}."); using (_logger.Indent()) { try { - BaseAssemblyResolver resolver = new DefaultAssemblyResolver(); - - foreach (string referencePath in referencePaths.Select(p => Path.GetDirectoryName(p)).Distinct()) - { - resolver.AddSearchDirectory(referencePath); - } - - resolver.AddSearchDirectory(Path.GetDirectoryName(path)); - - ReaderParameters readerParams = new ReaderParameters { AssemblyResolver = resolver }; - var moduleDefinition = ModuleDefinition.ReadModule(path, readerParams); - functions.AddRange(GenerateFunctionMetadata(moduleDefinition)); } catch (BadImageFormatException) diff --git a/sdk/Sdk/Sdk.csproj b/sdk/Sdk/Sdk.csproj index 6dd2ae84e..8d70b189b 100644 --- a/sdk/Sdk/Sdk.csproj +++ b/sdk/Sdk/Sdk.csproj @@ -2,7 +2,8 @@ 17 - 2 + 3 + -preview1 netstandard2.0;net472 Microsoft.Azure.Functions.Worker.Sdk This package provides development time support for the Azure Functions .NET Worker. diff --git a/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.targets b/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.targets index 7ad914b23..6e807f0b8 100644 --- a/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.targets +++ b/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.targets @@ -8,437 +8,215 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and *********************************************************************************************** --> - - - <_ToolingSuffix> - <_AzureFunctionsNotSet Condition="'$(AzureFunctionsVersion)' == ''">true - v3 - <_ToolingSuffix Condition="($(AzureFunctionsVersion.StartsWith('v3',StringComparison.OrdinalIgnoreCase)) Or $(AzureFunctionsVersion.StartsWith('v4',StringComparison.OrdinalIgnoreCase))) And '$(TargetFrameworkIdentifier)' == '.NETCoreApp' And '$(TargetFrameworkVersion)' == 'v5.0'">net5-isolated - <_ToolingSuffix Condition="$(AzureFunctionsVersion.StartsWith('v4',StringComparison.OrdinalIgnoreCase)) And '$(TargetFrameworkIdentifier)' == '.NETCoreApp' And '$(TargetFrameworkVersion)' == 'v6.0'">net6-isolated - <_ToolingSuffix Condition="$(AzureFunctionsVersion.StartsWith('v4',StringComparison.OrdinalIgnoreCase)) And '$(TargetFrameworkIdentifier)' == '.NETCoreApp' And '$(TargetFrameworkVersion)' == 'v7.0'">net7-isolated - <_ToolingSuffix Condition="$(AzureFunctionsVersion.StartsWith('v4',StringComparison.OrdinalIgnoreCase)) And '$(TargetFrameworkIdentifier)' == '.NETCoreApp' And '$(TargetFrameworkVersion)' == 'v8.0'">net8-isolated - <_ToolingSuffix Condition="$(AzureFunctionsVersion.StartsWith('v4',StringComparison.OrdinalIgnoreCase)) And '$(TargetFrameworkIdentifier)' == '.NETFramework'">netfx-isolated - $(_ToolingSuffix) - <_FunctionsTaskFramework Condition=" '$(MSBuildRuntimeType)' == 'Core'">netstandard2.0 - <_FunctionsTaskFramework Condition=" '$(_FunctionsTaskFramework)' == ''">net472 - <_FunctionsTasksDir Condition=" '$(_FunctionsTasksDir)'=='' ">$(MSBuildThisFileDirectory)..\tools\$(_FunctionsTaskFramework)\ - <_FunctionsTaskAssemblyFullPath Condition=" '$(_FunctionsTaskAssemblyFullPath)'=='' ">$(_FunctionsTasksDir)\Microsoft.Azure.Functions.Worker.Sdk.dll - - <_FunctionsWorkerConfigInputFile>$(MSBuildThisFileDirectory)\..\tools\worker.config.json - - <_FunctionsMetadataLoaderExtensionFile>$(MSBuildThisFileDirectory)\..\tools\netstandard2.0\Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader.dll - <_FunctionsExtensionsDirectory>.azurefunctions - <_FunctionsExtensionsJsonName>extensions.json - <_FunctionsExtensionsFullPublish Condition="$(NoBuild) == '' And $(_FunctionsExtensionsFullPublish) == ''">True - <_FunctionsExtensionsFullPublish Condition="$(_FunctionsExtensionsFullPublish) == ''">!$(NoBuild) - $(MSBuildExtensionsPath)\Microsoft\VisualStudio\Managed.Functions\ - - false - true - $(FunctionsEnableWorkerIndexing) - $(FunctionsEnableWorkerIndexing) - true - true - true - $(RootNamespace.Replace("-", "_")) - - - - - + + + <_ToolingSuffix> + <_AzureFunctionsNotSet Condition="'$(AzureFunctionsVersion)' == ''">true + v4 + <_ToolingSuffix Condition="($(AzureFunctionsVersion.StartsWith('v3',StringComparison.OrdinalIgnoreCase)) Or $(AzureFunctionsVersion.StartsWith('v4',StringComparison.OrdinalIgnoreCase))) And '$(TargetFrameworkIdentifier)' == '.NETCoreApp' And '$(TargetFrameworkVersion)' == 'v5.0'">net5-isolated + <_ToolingSuffix Condition="$(AzureFunctionsVersion.StartsWith('v4',StringComparison.OrdinalIgnoreCase)) And '$(TargetFrameworkIdentifier)' == '.NETCoreApp' And '$(TargetFrameworkVersion)' == 'v6.0'">net6-isolated + <_ToolingSuffix Condition="$(AzureFunctionsVersion.StartsWith('v4',StringComparison.OrdinalIgnoreCase)) And '$(TargetFrameworkIdentifier)' == '.NETCoreApp' And '$(TargetFrameworkVersion)' == 'v7.0'">net7-isolated + <_ToolingSuffix Condition="$(AzureFunctionsVersion.StartsWith('v4',StringComparison.OrdinalIgnoreCase)) And '$(TargetFrameworkIdentifier)' == '.NETCoreApp' And '$(TargetFrameworkVersion)' == 'v8.0'">net8-isolated + <_ToolingSuffix Condition="$(AzureFunctionsVersion.StartsWith('v4',StringComparison.OrdinalIgnoreCase)) And '$(TargetFrameworkIdentifier)' == '.NETFramework'">netfx-isolated + $(_ToolingSuffix) + <_FunctionsTaskFramework Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard2.0 + <_FunctionsTaskFramework Condition="'$(_FunctionsTaskFramework)' == ''">net472 + <_FunctionsTasksDir Condition="'$(_FunctionsTasksDir)'==''">$(MSBuildThisFileDirectory)..\tools\$(_FunctionsTaskFramework)\ + <_FunctionsTaskAssemblyFullPath Condition=" '$(_FunctionsTaskAssemblyFullPath)'=='' ">$(_FunctionsTasksDir)\Microsoft.Azure.Functions.Worker.Sdk.dll + + <_FunctionsExtensionCommonProps>ImportDirectoryBuildProps=false;ImportDirectoryBuildTargets=false;ImportDirectoryPackagesProps=false + <_FunctionsExtensionRemoveProps>TargetFramework;Platform;RuntimeIdentifier;SelfContained;PublishSingleFile;PublishReadyToRun;UseCurrentRuntimeIdentifier;WebPublishMethod;PublishProfile;DeployOnBuild;PublishDir + <_FunctionsWorkerConfigInputFile>$(MSBuildThisFileDirectory)\..\tools\worker.config.json + + <_FunctionsMetadataLoaderExtensionFile>$(MSBuildThisFileDirectory)\..\tools\netstandard2.0\Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader.dll + <_FunctionsExtensionsDirectory>.azurefunctions + <_FunctionsExtensionsJsonName>extensions.json + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\Managed.Functions\ + + false + true + $(FunctionsEnableWorkerIndexing) + $(FunctionsEnableWorkerIndexing) + + true + true + + <_FunctionsGenerateExtensionProject Condition="'$(DesignTimeBuild)' != 'true'">true + $(RootNamespace.Replace("-", "_")) + + + + + + + + + + + + + + + + + + - + + - + + - - - - - - - - + + - - <_FunctionsWorkerExecutableFileName >$(TargetName)$(TargetExt) - - - - <_FunctionsNativeExecutableExtension Condition="($(RuntimeIdentifier.StartsWith('win')) or $(DefaultAppHostRuntimeIdentifier.StartsWith('win')))">.exe - <_FunctionsWorkerExecutableFileName Condition="'$(PublishAot)' != 'true'">$(TargetName).dll - <_FunctionsWorkerExecutableFileName Condition="'$(PublishAot)' == 'true'">$(TargetName)$(_FunctionsNativeExecutableExtension) + + + + <_FunctionsMetadataPath>$(IntermediateOutputPath)functions.metadata + <_FunctionsWorkerConfigPath>$(IntermediateOutputPath)worker.config.json + $(IntermediateOutputPath)WorkerExtensions + $([System.IO.Path]::GetFullPath($(ExtensionsCsProjDirectory))) + $([System.IO.Path]::Combine($(ExtensionsCsProjDirectory), WorkerExtensions.csproj)) + <_FunctionsIntermediateExtensionJsonPath>$(ExtensionsCsProjDirectory)\buildout\bin\$(_FunctionsExtensionsJsonName) + <_FunctionsIntermediateExtensionUpdatedJsonPath>$(IntermediateOutputPath)$(_FunctionsExtensionsJsonName) - - - - - - $(TargetDir)\worker.config.json - $([System.IO.Path]::Combine($([System.IO.Path]::GetTempPath()), $([System.IO.Path]::GetRandomFileName()))) - - - - - - - - - - - - - - $(TargetDir)\worker.config.json - $([System.IO.Path]::Combine($([System.IO.Path]::GetTempPath()), $([System.IO.Path]::GetRandomFileName()))) - - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_FilesInTargetDir Include="$(TargetDir)**\*" /> - - - - - - - - - - - - - - - - - $(FunctionsDir) - $(PublishDir)\ - $(PublishDir)bin\$(TargetFileName) - $(FunctionsDir) - - - - - - - - - - - - $(PublishIntermediateOutputPath) - $(PublishDir)\ - $(PublishDir)bin\$(TargetFileName) - $(PublishIntermediateOutputPath) - - - - - <_PublishTempFiles Include="$(PublishIntermediateOutputPath)**\*.*" /> - - - - - - - - - - - - - - + + + + + + - ============================================================ - --> + + + + + + - - _InitializeDeployOnBuildProperties; - Publish; - $(_DotNetPublishFiles); - + <_FunctionsExecutable>dotnet + + + <_FunctionsExecutable Condition="'$(SelfContained)' == 'true' AND '%(Identity)' == '$(AppHostIntermediatePath)'">{WorkerRoot}%(None.Link) + <_FunctionsExecutable Condition="$(TargetFrameworkIdentifier) == '.NETFramework'">{WorkerRoot}$(TargetName)$(TargetExt) - - - - - - $(PublishDir)\worker.config.json - $([System.IO.Path]::Combine($([System.IO.Path]::GetTempPath()), $([System.IO.Path]::GetRandomFileName()))) - - - - - - - - - - - - - $(PublishDir)\worker.config.json - $([System.IO.Path]::Combine($([System.IO.Path]::GetTempPath()), $([System.IO.Path]::GetRandomFileName()))) - - - - - - - + - + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - _GenerateFunctionsAndCopyContentFiles; - _WorkerExtensionsRestorePublish; - _WorkerExtensionsFullPublish; - _WorkerExtensionsPublishCopy; - _EnhanceFunctionsExtensionsMetadataPostPublish - - - _GenerateFunctionsAndCopyContentFiles; - _WorkerExtensionsPublishCopyNoBuild - - + + + + + + + + + <_ExtensionBinaries Include="$(ExtensionsCsProjDirectory)\buildout\bin\**" + Exclude="$(ExtensionsCsProjDirectory)\buildout\bin\runtimes\**;$(_FunctionsIntermediateExtensionJsonPath)" + CopyToOutputDirectory="PreserveNewest" + CopyToPublishDirectory="PreserveNewest" /> + <_ExtensionRuntimeBinaries Include="$(ExtensionsCsProjDirectory)\buildout\runtimes\**" + CopyToOutputDirectory="PreserveNewest" + CopyToPublishDirectory="PreserveNewest" /> + + + + + + + + + + + + + + <_NoneWithTargetPath Include="@(_ExtensionFilesWithTargetPath)" TargetPath="$(_FunctionsExtensionsDirectory)/%(_ExtensionFilesWithTargetPath.TargetPath)" /> + + + + + + <_WorkerExtFilesToClean Include="$(ExtensionsCsProjDirectory)\**" Condition="'$(ExtensionsCsProjDirectory)' != ''" /> + <_WorkerExtFilesToClean Include="$(TargetDir)$(_FunctionsExtensionsDirectory)\**" /> + <_WorkerExtFilesToClean Include="$(_FunctionsMetadataPath)" /> + <_WorkerExtFilesToClean Include="$(_FunctionsWorkerConfigPath)" /> + <_WorkerExtFilesToClean Include="$(TargetDir)worker.config.json" /> + <_WorkerExtFilesToClean Include="$(TargetDir)extensions.json" /> + <_WorkerExtFilesToClean Include="$(TargetDir)functions.metadata" /> + <_WorkerExtFilesToClean Include="$(_FunctionsIntermediateExtensionUpdatedJsonPath)" /> + + + + + + - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/sdk/Sdk/Tasks/EnhanceExtensionsMetadata.cs b/sdk/Sdk/Tasks/EnhanceExtensionsMetadata.cs index 8fbaf5c13..c76bb4871 100644 --- a/sdk/Sdk/Tasks/EnhanceExtensionsMetadata.cs +++ b/sdk/Sdk/Tasks/EnhanceExtensionsMetadata.cs @@ -34,8 +34,6 @@ public override bool Execute() string newJson = JsonSerializer.Serialize(extensionsMetadata, _serializerOptions); File.WriteAllText(OutputPath, newJson); - File.Delete(ExtensionsJsonPath); - return true; } } diff --git a/sdk/Sdk/Tasks/GenerateFunctionMetadata.cs b/sdk/Sdk/Tasks/GenerateFunctionMetadata.cs index 8b66d6b49..e8e42cee2 100644 --- a/sdk/Sdk/Tasks/GenerateFunctionMetadata.cs +++ b/sdk/Sdk/Tasks/GenerateFunctionMetadata.cs @@ -45,7 +45,7 @@ public override bool Execute() { var functionGenerator = new FunctionMetadataGenerator(MSBuildLogger); - var functions = functionGenerator.GenerateFunctionMetadata(AssemblyPath!, ReferencePaths.Select(p => p.ItemSpec)); + var functions = functionGenerator.GenerateFunctionMetadata(AssemblyPath!, ReferencePaths ?? Enumerable.Empty()); var extensions = functionGenerator.Extensions; var extensionsCsProjGenerator = new ExtensionsCsprojGenerator(extensions, ExtensionsCsProjFilePath!, AzureFunctionsVersion!, TargetFrameworkIdentifier!, TargetFrameworkVersion!); diff --git a/sdk/release_notes.md b/sdk/release_notes.md index 5332de1d4..d9c704edd 100644 --- a/sdk/release_notes.md +++ b/sdk/release_notes.md @@ -4,7 +4,7 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Sdk 1.17.2 (meta package) - -- Revert changes from #1946 +### Microsoft.Azure.Functions.Worker.Sdk 1.17.13-preview1 (meta package) +- Re-add SDK refactors that were reverted in #2313 (#2347) +- Address assembly scanning regression (#2347) diff --git a/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj b/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj index 887a44c4a..2469eba63 100644 --- a/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj +++ b/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj @@ -44,7 +44,7 @@ - + \ No newline at end of file diff --git a/test/FunctionMetadataGeneratorTests/ExtensionsCsProjGeneratorTests.cs b/test/FunctionMetadataGeneratorTests/ExtensionsCsProjGeneratorTests.cs index c0b32d779..ce06d4e86 100644 --- a/test/FunctionMetadataGeneratorTests/ExtensionsCsProjGeneratorTests.cs +++ b/test/FunctionMetadataGeneratorTests/ExtensionsCsProjGeneratorTests.cs @@ -1,7 +1,9 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; using System.Collections.Generic; +using System.IO; using Microsoft.Azure.Functions.Worker.Sdk; using Xunit; @@ -9,8 +11,48 @@ namespace Microsoft.Azure.Functions.SdkTests { public class ExtensionsCsProjGeneratorTests { - [Fact] - public void GetCsProjContent_Succeeds_functions_v3() + public enum FuncVersion + { + V3, + V4, + } + + [Theory] + [InlineData(FuncVersion.V3)] + [InlineData(FuncVersion.V4)] + public void GetCsProjContent_Succeeds(FuncVersion version) + { + var generator = GetGenerator(version); + string actual = generator.GetCsProjContent().Replace("\r\n", "\n"); + string expected = ExpectedCsproj(version).Replace("\r\n", "\n"); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(FuncVersion.V3)] + [InlineData(FuncVersion.V4)] + public void GetCsProjContent_IncrementalSupport(FuncVersion version) + { + DateTime RunGenerate(string subPath, out string contents) + { + var generator = GetGenerator(version, subPath); + generator.Generate(); + + string path = Path.Combine(subPath, ExtensionsCsprojGenerator.ExtensionsProjectName); + contents = File.ReadAllText(path); + var csproj = new FileInfo(Path.Combine(subPath, ExtensionsCsprojGenerator.ExtensionsProjectName)); + return csproj.LastWriteTimeUtc; + } + + string subPath = Guid.NewGuid().ToString(); + DateTime firstRun = RunGenerate(subPath, out string first); + DateTime secondRun = RunGenerate(subPath, out string second); + + Assert.NotEqual(firstRun, secondRun); + Assert.Equal(first, second); + } + + static ExtensionsCsprojGenerator GetGenerator(FuncVersion version, string subPath = "") { IDictionary extensions = new Dictionary { @@ -19,13 +61,22 @@ public void GetCsProjContent_Succeeds_functions_v3() { "Microsoft.Azure.WebJobs.Extensions", "2.0.0" }, }; - var generator = new ExtensionsCsprojGenerator(extensions, "", "v3", Constants.NetCoreApp, Constants.NetCoreVersion31); - - string actualCsproj = generator.GetCsProjContent().Replace("\r\n", "\n"); - - Assert.Equal(ExpectedCsProjV3(), actualCsproj); + return version switch + { + FuncVersion.V3 => new ExtensionsCsprojGenerator(extensions, subPath, "v3", Constants.NetCoreApp, Constants.NetCoreVersion31), + FuncVersion.V4 => new ExtensionsCsprojGenerator(extensions, subPath, "v4", Constants.NetCoreApp, Constants.NetCoreVersion6), + _ => throw new ArgumentOutOfRangeException(nameof(version)), + }; } + private static string ExpectedCsproj(FuncVersion version) + => version switch + { + FuncVersion.V3 => ExpectedCsProjV3(), + FuncVersion.V4 => ExpectedCsProjV4(), + _ => throw new ArgumentOutOfRangeException(nameof(version)), + }; + private static string ExpectedCsProjV3() { return @" @@ -51,27 +102,10 @@ private static string ExpectedCsProjV3() "; - } - - [Fact] - public void GetCsProjContent_Succeeds_functions_v4() - { - IDictionary extensions = new Dictionary - { - { "Microsoft.Azure.WebJobs.Extensions.Storage", "4.0.3" }, - { "Microsoft.Azure.WebJobs.Extensions.Http", "3.0.0" }, - { "Microsoft.Azure.WebJobs.Extensions", "2.0.0" }, - }; - - var generator = new ExtensionsCsprojGenerator(extensions, "", "v4", Constants.NetCoreApp, Constants.NetCoreVersion6); - - string actualCsproj = generator.GetCsProjContent().Replace("\r\n", "\n"); - - Assert.Equal(ExpectedCsProjV4(), actualCsproj); - } - - private static string ExpectedCsProjV4() - { + } + + private static string ExpectedCsProjV4() + { return @" @@ -94,7 +128,7 @@ private static string ExpectedCsProjV4() -"; - } +"; + } } } diff --git a/test/Resources/Projects/FunctionApp01/FunctionApp01.csproj b/test/Resources/Projects/FunctionApp01/FunctionApp01.csproj new file mode 100644 index 000000000..88de5f212 --- /dev/null +++ b/test/Resources/Projects/FunctionApp01/FunctionApp01.csproj @@ -0,0 +1,25 @@ + + + + Exe + net8.0 + enable + enable + 1.17.2 + + + + + + + + + + + + + + + + + diff --git a/test/Resources/Projects/FunctionApp01/HttpFunction.cs b/test/Resources/Projects/FunctionApp01/HttpFunction.cs new file mode 100644 index 000000000..747abb8bc --- /dev/null +++ b/test/Resources/Projects/FunctionApp01/HttpFunction.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Extensions.Logging; + +namespace FunctionApp01 +{ + public class HttpFunction + { + [Function("FunctionApp01_Hello")] + public HttpResponseData Hello([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req) + { + return req.CreateResponse(HttpStatusCode.OK); + } + } +} diff --git a/test/Resources/Projects/FunctionApp01/Program.cs b/test/Resources/Projects/FunctionApp01/Program.cs new file mode 100644 index 000000000..3751555cb --- /dev/null +++ b/test/Resources/Projects/FunctionApp01/Program.cs @@ -0,0 +1,2 @@ +// See https://aka.ms/new-console-template for more information +Console.WriteLine("Hello, World!"); diff --git a/test/Resources/Projects/FunctionLib01/FunctionLib01.csproj b/test/Resources/Projects/FunctionLib01/FunctionLib01.csproj new file mode 100644 index 000000000..8a8610cef --- /dev/null +++ b/test/Resources/Projects/FunctionLib01/FunctionLib01.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/test/Resources/Projects/FunctionLib01/HttpFunction.cs b/test/Resources/Projects/FunctionLib01/HttpFunction.cs new file mode 100644 index 000000000..456d7f5cf --- /dev/null +++ b/test/Resources/Projects/FunctionLib01/HttpFunction.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Extensions.Logging; + +namespace FunctionLib01 +{ + public class HttpFunction + { + [Function("FunctionLib01_Hello")] + public HttpResponseData Hello([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req) + { + return req.CreateResponse(HttpStatusCode.OK); + } + } +} diff --git a/test/SdkE2ETests/InnerBuildTests.cs b/test/SdkE2ETests/InnerBuildTests.cs new file mode 100644 index 000000000..e0f73a152 --- /dev/null +++ b/test/SdkE2ETests/InnerBuildTests.cs @@ -0,0 +1,119 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.IO; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.Azure.Functions.SdkE2ETests +{ + public class InnerBuildTests + { + private readonly ITestOutputHelper _testOutputHelper; + + public InnerBuildTests(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + + [Fact] + public async Task Build_ScansReferences() + { + string outputDir = await TestUtility.InitializeTestAsync(_testOutputHelper, nameof(Build_ScansReferences)); + string projectFileDirectory = Path.Combine(TestUtility.TestResourcesProjectsRoot, "FunctionApp01", "FunctionApp01.csproj"); + + await TestUtility.RestoreAndBuildProjectAsync(projectFileDirectory, outputDir, null, _testOutputHelper); + + // Verify extensions.json contents + string extensionsJsonPath = Path.Combine(outputDir, "extensions.json"); + Assert.True(File.Exists(extensionsJsonPath)); + + JToken extensionsJsonContents = JObject.Parse(File.ReadAllText(extensionsJsonPath)); + JToken expectedExtensionsJson = JObject.Parse(@"{ + ""extensions"": [ + { + ""name"": ""SqlDurabilityProvider"", + ""typeName"": ""DurableTask.SqlServer.AzureFunctions.SqlDurabilityProviderStartup, DurableTask.SqlServer.AzureFunctions, Version=1.2.0.0, Culture=neutral, PublicKeyToken=2ea3c3a96309d850"", + ""hintPath"": ""./.azurefunctions/DurableTask.SqlServer.AzureFunctions.dll"" + }, + { + ""name"": ""DurableTask"", + ""typeName"": ""Microsoft.Azure.WebJobs.Extensions.DurableTask.DurableTaskWebJobsStartup, Microsoft.Azure.WebJobs.Extensions.DurableTask, Version=2.0.0.0, Culture=neutral, PublicKeyToken=014045d636e89289"", + ""hintPath"": ""./.azurefunctions/Microsoft.Azure.WebJobs.Extensions.DurableTask.dll"" + }, + { + ""name"": ""Startup"", + ""typeName"": ""Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader.Startup, Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader, Version=1.0.0.0, Culture=neutral, PublicKeyToken=551316b6919f366c"", + ""hintPath"": ""./.azurefunctions/Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader.dll"" + } + ] +}"); + + Assert.True(JToken.DeepEquals(expectedExtensionsJson, extensionsJsonContents)); + + // Verify functions.metadata contents + string functionsMetadataPath = Path.Combine(outputDir, "functions.metadata"); + Assert.True(File.Exists(functionsMetadataPath)); + + JToken functionsMetadataContents = JArray.Parse(File.ReadAllText(functionsMetadataPath)); + JToken expectedFunctionsMetadata = JArray.Parse(@"[ + { + ""name"": ""FunctionApp01_Hello"", + ""scriptFile"": ""FunctionApp01.dll"", + ""entryPoint"": ""FunctionApp01.HttpFunction.Hello"", + ""language"": ""dotnet-isolated"", + ""properties"": { + ""IsCodeless"": false + }, + ""bindings"": [ + { + ""name"": ""req"", + ""direction"": ""In"", + ""type"": ""httpTrigger"", + ""authLevel"": ""Anonymous"", + ""methods"": [ + ""get"" + ], + ""properties"": {} + }, + { + ""name"": ""$return"", + ""type"": ""http"", + ""direction"": ""Out"" + } + ] + }, + { + ""name"": ""FunctionLib01_Hello"", + ""scriptFile"": ""FunctionLib01.dll"", + ""entryPoint"": ""FunctionLib01.HttpFunction.Hello"", + ""language"": ""dotnet-isolated"", + ""properties"": { + ""IsCodeless"": false + }, + ""bindings"": [ + { + ""name"": ""req"", + ""direction"": ""In"", + ""type"": ""httpTrigger"", + ""authLevel"": ""Anonymous"", + ""methods"": [ + ""get"" + ], + ""properties"": {} + }, + { + ""name"": ""$return"", + ""type"": ""http"", + ""direction"": ""Out"" + } + ] + } +]"); + + Assert.True(JToken.DeepEquals(expectedFunctionsMetadata, functionsMetadataContents)); + } + } +} diff --git a/test/SdkE2ETests/TestUtility.cs b/test/SdkE2ETests/TestUtility.cs index e78028079..b636691fd 100644 --- a/test/SdkE2ETests/TestUtility.cs +++ b/test/SdkE2ETests/TestUtility.cs @@ -27,7 +27,6 @@ public static class TestUtility // Paths and executables public static readonly string DotNetExecutable = "dotnet"; - public static readonly string PathToRepoRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, @"..\..\..\..\..\")); public static readonly string SrcRoot = Path.Combine(PathToRepoRoot, "src"); public static readonly string SdkSolutionRoot = Path.Combine(PathToRepoRoot, "sdk"); @@ -36,9 +35,12 @@ public static class TestUtility public static readonly string SamplesRoot = Path.Combine(PathToRepoRoot, "samples"); public static readonly string LocalPackages = Path.Combine(PathToRepoRoot, "local"); public static readonly string TestOutputDir = Path.Combine(Path.GetTempPath(), "FunctionsWorkerSdkE2ETests"); - public static readonly string DevPackPath = Path.Combine(PathToRepoRoot, "tools", "devpack.ps1"); + public static readonly string TestResourcesProjectsRoot = Path.Combine(TestRoot, "Resources", "Projects"); + public static readonly string NuGetOrgPackages = "https://api.nuget.org/v3/index.json"; public static readonly string NuGetPackageSource = LocalPackages; + public static readonly string SdkVersion = "99.99.99-test"; + public static readonly string SdkBuildProj = Path.Combine(PathToRepoRoot, "build", "Sdk.slnf"); private static bool _isInitialized = false; @@ -46,14 +48,10 @@ public static async Task InitializeTestAsync(ITestOutputHelper testOutpu { if (!_isInitialized) { - testOutputHelper.WriteLine($"Running {DevPackPath}"); - - int? exitCode = await new ProcessWrapper().RunProcess("powershell", DevPackPath, SrcRoot, testOutputHelper); - Assert.True(exitCode.HasValue && exitCode.Value == 0); + testOutputHelper.WriteLine($"Packing {SdkBuildProj} with version {SdkVersion}"); + string arguments = $"pack {SdkBuildProj} -c {Configuration} -o {LocalPackages} -p:Version={SdkVersion}"; - // Build .NET Worker - string dotnetArgs = $"build --configuration {Configuration}"; - exitCode = await new ProcessWrapper().RunProcess(DotNetExecutable, dotnetArgs, Path.Combine(SrcRoot, "DotNetWorker"), testOutputHelper: testOutputHelper); + int? exitCode = await new ProcessWrapper().RunProcess(DotNetExecutable, arguments, SrcRoot, testOutputHelper); Assert.True(exitCode.HasValue && exitCode.Value == 0); _isInitialized = true; @@ -82,27 +80,44 @@ public static void ValidateFunctionsMetadata(string actualFilePath, string embed } } - public static async Task RestoreAndPublishProjectAsync(string fullPathToProjFile, string outputDir, string additionalParams, ITestOutputHelper outputHelper) + public static async Task RestoreAndBuildProjectAsync(string fullPathToProjFile, string outputDir, string additionalParams, ITestOutputHelper outputHelper) { - await PackWorkerSdk(outputHelper); + // Name of the csproj + string projectNameToTest = Path.GetFileName(fullPathToProjFile); + string projectFileDirectory = Path.GetDirectoryName(fullPathToProjFile); - await UpdateNugetPackagesForApp(fullPathToProjFile, outputHelper); + // Restore + outputHelper.WriteLine($"[{DateTime.UtcNow:O}] Restoring..."); + string dotnetArgs = $"restore {projectNameToTest} -s {NuGetOrgPackages} -s {LocalPackages} -p:SdkVersion={SdkVersion}"; + int? exitCode = await new ProcessWrapper().RunProcess(DotNetExecutable, dotnetArgs, projectFileDirectory, testOutputHelper: outputHelper); + Assert.True(exitCode.HasValue && exitCode.Value == 0); + outputHelper.WriteLine($"[{DateTime.UtcNow:O}] Done."); + + // Build + outputHelper.WriteLine($"[{DateTime.UtcNow:O}] Building..."); + dotnetArgs = $"build {projectNameToTest} --configuration {Configuration} -o {outputDir} -p:SdkVersion={SdkVersion} {additionalParams}"; + exitCode = await new ProcessWrapper().RunProcess(DotNetExecutable, dotnetArgs, projectFileDirectory, testOutputHelper: outputHelper); + Assert.True(exitCode.HasValue && exitCode.Value == 0); + outputHelper.WriteLine($"[{DateTime.UtcNow:O}] Done."); + } + public static async Task RestoreAndPublishProjectAsync(string fullPathToProjFile, string outputDir, string additionalParams, ITestOutputHelper outputHelper) + { // Name of the csproj string projectNameToTest = Path.GetFileName(fullPathToProjFile); string projectFileDirectory = Path.GetDirectoryName(fullPathToProjFile); // Restore outputHelper.WriteLine($"[{DateTime.UtcNow:O}] Restoring..."); - string dotnetArgs = $"restore {projectNameToTest} --source {TestUtility.LocalPackages}"; - int? exitCode = await new ProcessWrapper().RunProcess(TestUtility.DotNetExecutable, dotnetArgs, projectFileDirectory, testOutputHelper: outputHelper); + string dotnetArgs = $"restore {projectNameToTest} -s {NuGetOrgPackages} -s {LocalPackages} -p:SdkVersion={SdkVersion}"; + int? exitCode = await new ProcessWrapper().RunProcess(DotNetExecutable, dotnetArgs, projectFileDirectory, testOutputHelper: outputHelper); Assert.True(exitCode.HasValue && exitCode.Value == 0); outputHelper.WriteLine($"[{DateTime.UtcNow:O}] Done."); // Publish outputHelper.WriteLine($"[{DateTime.UtcNow:O}] Publishing..."); - dotnetArgs = $"publish {projectNameToTest} --configuration {TestUtility.Configuration} -o {outputDir} {additionalParams}"; - exitCode = await new ProcessWrapper().RunProcess(TestUtility.DotNetExecutable, dotnetArgs, projectFileDirectory, testOutputHelper: outputHelper); + dotnetArgs = $"publish {projectNameToTest} --configuration {Configuration} -o {outputDir} -p:SdkVersion={SdkVersion} {additionalParams}"; + exitCode = await new ProcessWrapper().RunProcess(DotNetExecutable, dotnetArgs, projectFileDirectory, testOutputHelper: outputHelper); Assert.True(exitCode.HasValue && exitCode.Value == 0); outputHelper.WriteLine($"[{DateTime.UtcNow:O}] Done."); } @@ -120,25 +135,5 @@ private static string InitializeOutputDir(string testName) return outputDir; } - - private static async Task PackWorkerSdk(ITestOutputHelper testOutputHelper) - { - // Build solution - int? exitCode = await new ProcessWrapper().RunProcess(DotNetExecutable, $"build --configuration {Configuration}", SdkProjectRoot, testOutputHelper: testOutputHelper); - Assert.True(exitCode.HasValue && exitCode.Value == 0); - - // Pack Sdk project - exitCode = await new ProcessWrapper().RunProcess(DotNetExecutable, $"pack --configuration {Configuration} -o {LocalPackages} --no-build", SdkProjectRoot, testOutputHelper: testOutputHelper); - Assert.True(exitCode.HasValue && exitCode.Value == 0); - } - - private static async Task UpdateNugetPackagesForApp(string projectFile, ITestOutputHelper testOutputHelper) - { - int? exitCode = await new ProcessWrapper().RunProcess(DotNetExecutable, $"remove {projectFile} package {WorkerSdkPackageName}", PathToRepoRoot, testOutputHelper: testOutputHelper); - // If a previous run failed, this may have a -1 exit code. We'll continue anyway. - - exitCode = await new ProcessWrapper().RunProcess(DotNetExecutable, $"add {projectFile} package {WorkerSdkPackageName} -s {LocalPackages} --prerelease", SdkProjectRoot, testOutputHelper: testOutputHelper); - Assert.True(exitCode.HasValue && exitCode.Value == 0); - } } }