diff --git a/sdk/Sdk/ExtensionsCsprojGenerator.cs b/sdk/Sdk/ExtensionsCsprojGenerator.cs index 96c66beb1..ad565f787 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,23 @@ public void Generate() { var extensionsCsprojFilePath = Path.Combine(_outputPath, ExtensionsProjectName); - RecreateDirectory(_outputPath); + string csproj = GetCsProjContent(); + + // Incremental build support: write the new csproj only if contents have changed. + // By keeping one from a previous build around, our restore & build has a chance + // to follow incremental build and no-op if nothing needs to be done. + if (File.Exists(extensionsCsprojFilePath)) + { + string existing = File.ReadAllText(extensionsCsprojFilePath); + if (string.Equals(csproj, existing, StringComparison.Ordinal)) + { + // Up to date, nothing to do. + return; + } + } - WriteExtensionsCsProj(extensionsCsprojFilePath); + RecreateDirectory(_outputPath); + File.WriteAllText(extensionsCsprojFilePath, csproj); } private void RecreateDirectory(string directoryPath) @@ -46,13 +60,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/Sdk.csproj b/sdk/Sdk/Sdk.csproj index 34e2e84e6..081876fce 100644 --- a/sdk/Sdk/Sdk.csproj +++ b/sdk/Sdk/Sdk.csproj @@ -3,6 +3,7 @@ 14 0 + -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 9974aa5a9..77767d968 100644 --- a/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.targets +++ b/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.targets @@ -66,14 +66,17 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and - - - + + + - $(TargetDir)\worker.config.json - $([System.IO.Path]::Combine($([System.IO.Path]::GetTempPath()), $([System.IO.Path]::GetRandomFileName()))) + <_FunctionsWorkerConfigOutputFile>$(TargetDir)\worker.config.json + $([System.IO.Path]::GetFullPath($(IntermediateOutputPath)WorkExtensions)) + + + + File="$(_FunctionsWorkerConfigOutputFile)" + Lines="$([System.IO.File]::ReadAllText($(_FunctionsWorkerConfigInputFile)) + .Replace('$functionExe$', '{WorkerRoot}$(TargetName).exe') + .Replace('$functionWorker$', '$(TargetName).exe') + .Replace('$enableWorkerIndexing$', '$(FunctionsEnableWorkerIndexing)'))" + Overwrite="true" /> - - $(TargetDir)\worker.config.json - $([System.IO.Path]::Combine($([System.IO.Path]::GetTempPath()), $([System.IO.Path]::GetRandomFileName()))) - - + + Condition="$(SelfContained)" + File="$(_FunctionsWorkerConfigOutputFile)" + Lines="$([System.IO.File]::ReadAllText($(_FunctionsWorkerConfigInputFile)) + .Replace('$functionExe$', '{WorkerRoot}$(TargetName)') + .Replace('$functionWorker$', '$(TargetName).dll') + .Replace('$enableWorkerIndexing$', '$(FunctionsEnableWorkerIndexing)'))" + Overwrite="true" /> + Condition="!$(SelfContained)" + File="$(_FunctionsWorkerConfigOutputFile)" + Lines="$([System.IO.File]::ReadAllText($(_FunctionsWorkerConfigInputFile)) + .Replace('$functionExe$', 'dotnet') + .Replace('$functionWorker$', '$(TargetName).dll') + .Replace('$enableWorkerIndexing$', '$(FunctionsEnableWorkerIndexing)'))" + Overwrite="true" /> @@ -157,8 +158,8 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and - - + + diff --git a/sdk/release_notes.md b/sdk/release_notes.md index 8b1885c82..ef0f9fa27 100644 --- a/sdk/release_notes.md +++ b/sdk/release_notes.md @@ -4,9 +4,12 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Sdk (meta package) -- +### Microsoft.Azure.Functions.Worker.Sdk 1.14.0-preview1 + +- Improve incremental build support for worker extension project inner build [#1749](https://github.com/Azure/azure-functions-dotnet-worker/pull/1749) + - Now builds to intermediate output path + - Avoids disk writes for generated .csproj and file copies if nothing has changed ### Microsoft.Azure.Functions.Worker.Sdk.Analyzers (delete if not updated) diff --git a/test/FunctionMetadataGeneratorTests/ExtensionsCsProjGeneratorTests.cs b/test/FunctionMetadataGeneratorTests/ExtensionsCsProjGeneratorTests.cs index 1c2014785..05f148941 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,45 @@ 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) + { + var generator = GetGenerator(version, subPath); + generator.Generate(); + + var csproj = new FileInfo(Path.Combine(subPath, ExtensionsCsprojGenerator.ExtensionsProjectName)); + return csproj.LastWriteTimeUtc; + } + + string subPath = Guid.NewGuid().ToString(); + DateTime firstRun = RunGenerate(subPath); + DateTime secondRun = RunGenerate(subPath); + + Assert.Equal(firstRun, secondRun); + } + + static ExtensionsCsprojGenerator GetGenerator(FuncVersion version, string subPath = "") { IDictionary extensions = new Dictionary { @@ -19,13 +58,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 @" @@ -52,27 +100,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 @" @@ -96,7 +127,7 @@ private static string ExpectedCsProjV4() -"; - } +"; + } } }