From d5b1ee138f098c1047a54e2176352d4a46d9e007 Mon Sep 17 00:00:00 2001 From: Andrew Stanton-Nurse Date: Fri, 27 May 2016 10:49:50 -0700 Subject: [PATCH] Add version suffix to build cache to ensure incremental builds are reset when it changes (#3246) * add test for #2687 * fix #2687 by writing version suffix to build cache --- .../TestInstance.cs | 10 +++---- .../dotnet-build/DotnetProjectBuilder.cs | 9 +++++- .../commands/dotnet-build/IncrementalCache.cs | 26 +++++++++++++++-- .../dotnet-build/IncrementalManager.cs | 22 ++++++++++++-- .../IncrementalTestsVersionSuffix.cs | 29 +++++++++++++++++++ 5 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 test/dotnet-build.Tests/IncrementalTestsVersionSuffix.cs diff --git a/src/Microsoft.DotNet.TestFramework/TestInstance.cs b/src/Microsoft.DotNet.TestFramework/TestInstance.cs index c3cfdfc86..558f0f762 100644 --- a/src/Microsoft.DotNet.TestFramework/TestInstance.cs +++ b/src/Microsoft.DotNet.TestFramework/TestInstance.cs @@ -11,7 +11,7 @@ namespace Microsoft.DotNet.TestFramework { public class TestInstance: TestDirectory { - // made tolower because the rest of the class works with normalized tolower strings + // made tolower because the rest of the class works with normalized tolower strings private static readonly IEnumerable BuildArtifactBlackList = new List() {".IncrementalCache", ".SDKVersion"}.Select(s => s.ToLower()).ToArray(); private string _testAssetRoot; @@ -78,9 +78,9 @@ namespace Microsoft.DotNet.TestFramework .Where(dir => { dir = dir.ToLower(); - return dir.EndsWith($"{System.IO.Path.DirectorySeparatorChar}bin") + return dir.EndsWith($"{System.IO.Path.DirectorySeparatorChar}bin") || dir.Contains($"{System.IO.Path.DirectorySeparatorChar}bin{System.IO.Path.DirectorySeparatorChar}") - || dir.EndsWith($"{System.IO.Path.DirectorySeparatorChar}obj") + || dir.EndsWith($"{System.IO.Path.DirectorySeparatorChar}obj") || dir.Contains($"{System.IO.Path.DirectorySeparatorChar}obj{System.IO.Path.DirectorySeparatorChar}"); }); @@ -94,10 +94,10 @@ namespace Microsoft.DotNet.TestFramework { file = file.ToLower(); - var isArtifact = file.Contains($"{System.IO.Path.DirectorySeparatorChar}bin{System.IO.Path.DirectorySeparatorChar}") + var isArtifact = file.Contains($"{System.IO.Path.DirectorySeparatorChar}bin{System.IO.Path.DirectorySeparatorChar}") || file.Contains($"{System.IO.Path.DirectorySeparatorChar}obj{System.IO.Path.DirectorySeparatorChar}"); - var isBlackListed = BuildArtifactBlackList.Any(b => file.Contains(b)); + var isBlackListed = BuildArtifactBlackList.Any(b => file.Contains(b)); return isArtifact && !isBlackListed; }); diff --git a/src/dotnet/commands/dotnet-build/DotnetProjectBuilder.cs b/src/dotnet/commands/dotnet-build/DotnetProjectBuilder.cs index 91cc77f05..0b1937321 100644 --- a/src/dotnet/commands/dotnet-build/DotnetProjectBuilder.cs +++ b/src/dotnet/commands/dotnet-build/DotnetProjectBuilder.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using System.IO; using Microsoft.DotNet.Cli; using Microsoft.DotNet.Cli.Compiler.Common; @@ -44,7 +45,8 @@ namespace Microsoft.DotNet.Tools.Build _args.ShouldSkipDependencies, _args.ConfigValue, _args.BuildBasePathValue, - _args.OutputValue + _args.OutputValue, + BuildIncrementalArgumentList(_args) ); _scriptRunner = new ScriptRunner(); @@ -52,6 +54,11 @@ namespace Microsoft.DotNet.Tools.Build _commandFactory = new DotNetCommandFactory(); } + private static IDictionary BuildIncrementalArgumentList(BuildCommandApp args) => new Dictionary() + { + ["version-suffix"] = args.VersionSuffixValue + }; + private void StampProjectWithSDKVersion(ProjectContext project) { if (File.Exists(DotnetFiles.VersionFile)) diff --git a/src/dotnet/commands/dotnet-build/IncrementalCache.cs b/src/dotnet/commands/dotnet-build/IncrementalCache.cs index af48ca082..13335af17 100644 --- a/src/dotnet/commands/dotnet-build/IncrementalCache.cs +++ b/src/dotnet/commands/dotnet-build/IncrementalCache.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -11,14 +12,21 @@ namespace Microsoft.DotNet.Tools.Build { internal class IncrementalCache { + private const string BuildArgumentsKeyName = "buildArguments"; private const string InputsKeyName = "inputs"; private const string OutputsKeyNane = "outputs"; public CompilerIO CompilerIO { get; } - public IncrementalCache(CompilerIO compilerIO) + /// + /// Captures parameters that affect compilation outputs. + /// + public IDictionary BuildArguments { get; } + + public IncrementalCache(CompilerIO compilerIO, IEnumerable> parameters) { CompilerIO = compilerIO; + BuildArguments = parameters.ToDictionary(p => p.Key, p => p.Value); } public void WriteToFile(string cacheFile) @@ -32,6 +40,7 @@ namespace Microsoft.DotNet.Tools.Build var rootObject = new JObject(); rootObject[InputsKeyName] = new JArray(CompilerIO.Inputs); rootObject[OutputsKeyNane] = new JArray(CompilerIO.Outputs); + rootObject[BuildArgumentsKeyName] = JObject.FromObject(BuildArguments); JsonSerializer.Create().Serialize(streamWriter, rootObject); } @@ -67,8 +76,9 @@ namespace Microsoft.DotNet.Tools.Build var inputs = ReadArray(jObject, InputsKeyName); var outputs = ReadArray(jObject, OutputsKeyNane); + var parameters = ReadDictionary(jObject, BuildArgumentsKeyName); - return new IncrementalCache(new CompilerIO(inputs, outputs)); + return new IncrementalCache(new CompilerIO(inputs, outputs), parameters); } } catch (Exception e) @@ -77,6 +87,18 @@ namespace Microsoft.DotNet.Tools.Build } } + private static IEnumerable> ReadDictionary(JObject jObject, string keyName) + { + var obj = jObject[keyName] as JObject; + + if(obj == null) + { + return Enumerable.Empty>(); + } + + return obj.Properties().ToDictionary(p => p.Name, p => p.Value.ToString()); + } + private static IEnumerable ReadArray(JObject jObject, string keyName) { var array = jObject.Value(keyName)?.Values(); diff --git a/src/dotnet/commands/dotnet-build/IncrementalManager.cs b/src/dotnet/commands/dotnet-build/IncrementalManager.cs index 3f2bec256..251147752 100644 --- a/src/dotnet/commands/dotnet-build/IncrementalManager.cs +++ b/src/dotnet/commands/dotnet-build/IncrementalManager.cs @@ -20,6 +20,7 @@ namespace Microsoft.DotNet.Tools.Build private readonly string _configuration; private readonly string _buildBasePath; private readonly string _outputPath; + private readonly IDictionary _incrementalAffectingArguments; public IncrementalManager( ProjectBuilder projectBuilder, @@ -28,7 +29,8 @@ namespace Microsoft.DotNet.Tools.Build bool shouldSkipDependencies, string configuration, string buildBasePath, - string outputPath) + string outputPath, + IDictionary incrementalAffectingArguments) { _projectBuilder = projectBuilder; _compilerIoManager = compilerIOManager; @@ -37,6 +39,7 @@ namespace Microsoft.DotNet.Tools.Build _configuration = configuration; _buildBasePath = buildBasePath; _outputPath = outputPath; + _incrementalAffectingArguments = incrementalAffectingArguments; } public IncrementalResult NeedsRebuilding(ProjectGraphNode graphNode) @@ -156,6 +159,21 @@ namespace Microsoft.DotNet.Tools.Build return new IncrementalResult("Input items added from last build", diffResult.Additions); } + var keys = incrementalCache.BuildArguments.Keys.Union(_incrementalAffectingArguments.Keys); + var mismatchedKeys = keys.Where(k => + { + string cachedVal; + string currentVal; + + return !incrementalCache.BuildArguments.TryGetValue(k, out cachedVal) || + !_incrementalAffectingArguments.TryGetValue(k, out currentVal) || + !string.Equals(cachedVal ?? string.Empty, currentVal ?? string.Empty, StringComparison.Ordinal); + }); + if (mismatchedKeys.Any()) + { + return new IncrementalResult("Build arguments changed since last build", mismatchedKeys); + } + return IncrementalResult.DoesNotNeedRebuild; } @@ -195,7 +213,7 @@ namespace Microsoft.DotNet.Tools.Build { var incrementalCacheFile = graphNode.ProjectContext.IncrementalCacheFile(_configuration, _buildBasePath, _outputPath); - var incrementalCache = new IncrementalCache(_compilerIoManager.GetCompileIO(graphNode)); + var incrementalCache = new IncrementalCache(_compilerIoManager.GetCompileIO(graphNode), _incrementalAffectingArguments); incrementalCache.WriteToFile(incrementalCacheFile); } } diff --git a/test/dotnet-build.Tests/IncrementalTestsVersionSuffix.cs b/test/dotnet-build.Tests/IncrementalTestsVersionSuffix.cs new file mode 100644 index 000000000..024a62760 --- /dev/null +++ b/test/dotnet-build.Tests/IncrementalTestsVersionSuffix.cs @@ -0,0 +1,29 @@ +using Microsoft.DotNet.Tools.Test.Utilities; +using Xunit; + +namespace Microsoft.DotNet.Tools.Builder.Tests +{ + public class IncrementalTestsVersionSuffix : IncrementalTestBase + { + [Fact] + public void TestRebuildWhenVersionSuffixChanged() + { + var testInstance = TestAssetsManager.CreateTestInstance("TestSimpleIncrementalApp") + .WithLockFiles(); + + // Build with Version Suffix 1 + var command = new BuildCommand(testInstance.TestRoot, versionSuffix: "1"); + var result = command.ExecuteWithCapturedOutput(); + + // Verify the result + result.Should().HaveCompiledProject("TestSimpleIncrementalApp", ".NETCoreApp,Version=v1.0"); + + // Build with Version Suffix 2 + command = new BuildCommand(testInstance.TestRoot, versionSuffix: "2"); + result = command.ExecuteWithCapturedOutput(); + + // Verify the result + result.Should().HaveCompiledProject("TestSimpleIncrementalApp", ".NETCoreApp,Version=v1.0"); + } + } +}