Add input caching for glob change detection
This commit is contained in:
		
					parent
					
						
							
								ef0ca39da1
							
						
					
				
			
			
				commit
				
					
						a3b7c85451
					
				
			
		
					 13 changed files with 541 additions and 167 deletions
				
			
		
							
								
								
									
										12
									
								
								TestAssets/TestProjects/TestSimpleIncrementalApp/Program2.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								TestAssets/TestProjects/TestSimpleIncrementalApp/Program2.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | ||||||
|  | using System; | ||||||
|  | 
 | ||||||
|  | namespace ConsoleApplication | ||||||
|  | { | ||||||
|  |     public class Program2 | ||||||
|  |     { | ||||||
|  |         public static void Foo(string[] args) | ||||||
|  |         { | ||||||
|  |             Console.WriteLine("Hello World!"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,8 +1,10 @@ | ||||||
| // Copyright (c) .NET Foundation and contributors. All rights reserved. | // Copyright (c) .NET Foundation and contributors. All rights reserved. | ||||||
| // Licensed under the MIT license. See LICENSE file in the project root for full license information. | // Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||||||
| 
 | 
 | ||||||
|  | using System; | ||||||
| using System.IO; | using System.IO; | ||||||
| using System.Reflection; | using System.Reflection; | ||||||
|  | using Microsoft.Extensions.PlatformAbstractions; | ||||||
| 
 | 
 | ||||||
| namespace Microsoft.DotNet.Cli.Utils | namespace Microsoft.DotNet.Cli.Utils | ||||||
| { | { | ||||||
|  | @ -12,5 +14,16 @@ namespace Microsoft.DotNet.Cli.Utils | ||||||
|         /// The CLI ships with a .version file that stores the commit information and CLI version |         /// The CLI ships with a .version file that stores the commit information and CLI version | ||||||
|         /// </summary> |         /// </summary> | ||||||
|         public static string VersionFile => Path.GetFullPath(Path.Combine(typeof(DotnetFiles).GetTypeInfo().Assembly.Location, "..", ".version")); |         public static string VersionFile => Path.GetFullPath(Path.Combine(typeof(DotnetFiles).GetTypeInfo().Assembly.Location, "..", ".version")); | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         /// Reads the version file and adds runtime specific information | ||||||
|  |         /// </summary> | ||||||
|  |         public static string ReadAndInterpretVersionFile() | ||||||
|  |         { | ||||||
|  |             var content = File.ReadAllText(DotnetFiles.VersionFile); | ||||||
|  |             content += Environment.NewLine; | ||||||
|  |             content += PlatformServices.Default.Runtime.GetRuntimeIdentifier(); | ||||||
|  |             return content; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -37,12 +37,14 @@ namespace Microsoft.DotNet.Cli.Utils | ||||||
|             { |             { | ||||||
|                 Output = new Reporter(AnsiConsole.GetOutput()); |                 Output = new Reporter(AnsiConsole.GetOutput()); | ||||||
|                 Error = new Reporter(AnsiConsole.GetError()); |                 Error = new Reporter(AnsiConsole.GetError()); | ||||||
|                 Verbose = CommandContext.IsVerbose() ? |                 Verbose = IsVerbose ? | ||||||
|                     new Reporter(AnsiConsole.GetOutput()) : |                     new Reporter(AnsiConsole.GetOutput()) : | ||||||
|                     NullReporter; |                     NullReporter; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         public static bool IsVerbose => CommandContext.IsVerbose(); | ||||||
|  | 
 | ||||||
|         public void WriteLine(string message) |         public void WriteLine(string message) | ||||||
|         { |         { | ||||||
|             lock (_lock) |             lock (_lock) | ||||||
|  |  | ||||||
|  | @ -37,6 +37,12 @@ namespace Microsoft.DotNet.Cli.Compiler.Common | ||||||
|             return Path.Combine(intermediatePath, ".SDKVersion"); |             return Path.Combine(intermediatePath, ".SDKVersion"); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         public static string IncrementalCacheFile(this ProjectContext context, string configuration, string buildBasePath, string outputPath) | ||||||
|  |         { | ||||||
|  |             var intermediatePath = context.GetOutputPaths(configuration, buildBasePath, outputPath).IntermediateOutputDirectoryPath; | ||||||
|  |             return Path.Combine(intermediatePath, ".IncrementalCache"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         // used in incremental compilation for the key file |         // used in incremental compilation for the key file | ||||||
|         public static CommonCompilerOptions ResolveCompilationOptions(this ProjectContext context, string configuration) |         public static CommonCompilerOptions ResolveCompilationOptions(this ProjectContext context, string configuration) | ||||||
|         { |         { | ||||||
|  |  | ||||||
|  | @ -11,6 +11,9 @@ namespace Microsoft.DotNet.TestFramework | ||||||
| { | { | ||||||
|     public class TestInstance |     public class TestInstance | ||||||
|     { |     { | ||||||
|  |         // made tolower because the rest of the class works with normalized tolower strings | ||||||
|  |         private static readonly IEnumerable<string> BuildArtifactBlackList = new List<string>() {".IncrementalCache", ".SDKVersion"}.Select(s => s.ToLower()).ToArray(); | ||||||
|  | 
 | ||||||
|         private string _testDestination; |         private string _testDestination; | ||||||
|         private string _testAssetRoot; |         private string _testAssetRoot; | ||||||
| 
 | 
 | ||||||
|  | @ -105,8 +108,13 @@ namespace Microsoft.DotNet.TestFramework | ||||||
|                                  .Where(file => |                                  .Where(file => | ||||||
|                                  { |                                  { | ||||||
|                                      file = file.ToLower(); |                                      file = file.ToLower(); | ||||||
|                                      return file.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}")  | 
 | ||||||
|  |                                      var isArtifact = file.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}")  | ||||||
|                                             || file.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}"); |                                             || file.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}"); | ||||||
|  | 
 | ||||||
|  |                                      var isBlackListed = BuildArtifactBlackList.Any(b => file.Contains(b)); | ||||||
|  | 
 | ||||||
|  |                                      return isArtifact && !isBlackListed; | ||||||
|                                  }); |                                  }); | ||||||
| 
 | 
 | ||||||
|             foreach (string binFile in binFiles) |             foreach (string binFile in binFiles) | ||||||
|  |  | ||||||
|  | @ -2,18 +2,43 @@ | ||||||
| // Licensed under the MIT license. See LICENSE file in the project root for full license information. | // Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||||||
| 
 | 
 | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
| 
 | 
 | ||||||
| namespace Microsoft.DotNet.Tools.Build | namespace Microsoft.DotNet.Tools.Build | ||||||
| { | { | ||||||
|     internal struct CompilerIO |     internal class CompilerIO | ||||||
|     { |     { | ||||||
|         public readonly List<string> Inputs; |         public readonly IEnumerable<string> Inputs; | ||||||
|         public readonly List<string> Outputs; |         public readonly IEnumerable<string> Outputs; | ||||||
| 
 | 
 | ||||||
|         public CompilerIO(List<string> inputs, List<string> outputs) |         public CompilerIO(IEnumerable<string> inputs, IEnumerable<string> outputs) | ||||||
|         { |         { | ||||||
|             Inputs = inputs; |             Inputs = inputs; | ||||||
|             Outputs = outputs; |             Outputs = outputs; | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         public DiffResult DiffInputs(CompilerIO other) | ||||||
|  |         { | ||||||
|  |             var myInputSet = new HashSet<string>(Inputs); | ||||||
|  |             var otherInputSet = new HashSet<string>(other.Inputs); | ||||||
|  | 
 | ||||||
|  |             var additions = myInputSet.Except(otherInputSet); | ||||||
|  |             var deletions = otherInputSet.Except(myInputSet); | ||||||
|  | 
 | ||||||
|  |             return new DiffResult(additions, deletions); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         internal class DiffResult | ||||||
|  |         { | ||||||
|  |             public IEnumerable<string> Additions { get; private set; } | ||||||
|  |             public IEnumerable<string> Deletions { get; private set; } | ||||||
|  | 
 | ||||||
|  |             public DiffResult(IEnumerable<string> additions, IEnumerable<string> deletions) | ||||||
|  |             { | ||||||
|  |                 Additions = additions; | ||||||
|  |                 Deletions = deletions; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -2,6 +2,7 @@ | ||||||
| // Licensed under the MIT license. See LICENSE file in the project root for full license information. | // Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||||||
| 
 | 
 | ||||||
| using System; | using System; | ||||||
|  | using System.Collections.Concurrent; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.IO; | using System.IO; | ||||||
| using System.Linq; | using System.Linq; | ||||||
|  | @ -19,6 +20,7 @@ namespace Microsoft.DotNet.Tools.Build | ||||||
|         private readonly string _buildBasePath; |         private readonly string _buildBasePath; | ||||||
|         private readonly IList<string> _runtimes; |         private readonly IList<string> _runtimes; | ||||||
|         private readonly WorkspaceContext _workspace; |         private readonly WorkspaceContext _workspace; | ||||||
|  | 		private readonly ConcurrentDictionary<ProjectContextIdentity, CompilerIO> _cache; | ||||||
| 
 | 
 | ||||||
|         public CompilerIOManager(string configuration, |         public CompilerIOManager(string configuration, | ||||||
|             string outputPath, |             string outputPath, | ||||||
|  | @ -31,49 +33,36 @@ namespace Microsoft.DotNet.Tools.Build | ||||||
|             _buildBasePath = buildBasePath; |             _buildBasePath = buildBasePath; | ||||||
|             _runtimes = runtimes.ToList(); |             _runtimes = runtimes.ToList(); | ||||||
|             _workspace = workspace; |             _workspace = workspace; | ||||||
|  |              | ||||||
|  |             _cache = new ConcurrentDictionary<ProjectContextIdentity, CompilerIO>(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public bool AnyMissingIO(ProjectContext project, IEnumerable<string> items, string itemsType) |  | ||||||
|         { |  | ||||||
|             var missingItems = items.Where(i => !File.Exists(i)).ToList(); |  | ||||||
| 
 |  | ||||||
|             if (!missingItems.Any()) |  | ||||||
|             { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             Reporter.Verbose.WriteLine($"Project {project.GetDisplayName()} will be compiled because expected {itemsType} are missing."); |  | ||||||
| 
 |  | ||||||
|             foreach (var missing in missingItems) |  | ||||||
|             { |  | ||||||
|                 Reporter.Verbose.WriteLine($" {missing}"); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             Reporter.Verbose.WriteLine(); ; |  | ||||||
| 
 |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         // computes all the inputs and outputs that would be used in the compilation of a project |         // computes all the inputs and outputs that would be used in the compilation of a project | ||||||
|         // ensures that all paths are files |  | ||||||
|         // ensures no missing inputs |  | ||||||
|         public CompilerIO GetCompileIO(ProjectGraphNode graphNode) |         public CompilerIO GetCompileIO(ProjectGraphNode graphNode) | ||||||
|         { |         { | ||||||
|  |             return _cache.GetOrAdd(graphNode.ProjectContext.Identity, i => ComputeIO(graphNode)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private CompilerIO ComputeIO(ProjectGraphNode graphNode) | ||||||
|  |         { | ||||||
|  |             var inputs = new List<string>(); | ||||||
|  |             var outputs = new List<string>(); | ||||||
|  | 
 | ||||||
|             var isRootProject = graphNode.IsRoot; |             var isRootProject = graphNode.IsRoot; | ||||||
|             var project = graphNode.ProjectContext; |             var project = graphNode.ProjectContext; | ||||||
| 
 | 
 | ||||||
|             var compilerIO = new CompilerIO(new List<string>(), new List<string>()); |  | ||||||
|             var calculator = project.GetOutputPaths(_configuration, _buildBasePath, _outputPath); |             var calculator = project.GetOutputPaths(_configuration, _buildBasePath, _outputPath); | ||||||
|             var binariesOutputPath = calculator.CompilationOutputPath; |             var binariesOutputPath = calculator.CompilationOutputPath; | ||||||
| 
 | 
 | ||||||
|             // input: project.json |             // input: project.json | ||||||
|             compilerIO.Inputs.Add(project.ProjectFile.ProjectFilePath); |             inputs.Add(project.ProjectFile.ProjectFilePath); | ||||||
| 
 | 
 | ||||||
|             // input: lock file; find when dependencies change |             // input: lock file; find when dependencies change | ||||||
|             AddLockFile(project, compilerIO); |             AddLockFile(project, inputs); | ||||||
| 
 | 
 | ||||||
|             // input: source files |             // input: source files | ||||||
|             compilerIO.Inputs.AddRange(CompilerUtil.GetCompilationSources(project)); |             inputs.AddRange(CompilerUtil.GetCompilationSources(project)); | ||||||
| 
 | 
 | ||||||
|             var allOutputPath = new HashSet<string>(calculator.CompilationFiles.All()); |             var allOutputPath = new HashSet<string>(calculator.CompilationFiles.All()); | ||||||
|             if (isRootProject && project.ProjectFile.HasRuntimeOutput(_configuration)) |             if (isRootProject && project.ProjectFile.HasRuntimeOutput(_configuration)) | ||||||
|  | @ -88,23 +77,22 @@ namespace Microsoft.DotNet.Tools.Build | ||||||
|             // output: compiler outputs |             // output: compiler outputs | ||||||
|             foreach (var path in allOutputPath) |             foreach (var path in allOutputPath) | ||||||
|             { |             { | ||||||
|                 compilerIO.Outputs.Add(path); |                 outputs.Add(path); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // input compilation options files |             // input compilation options files | ||||||
|             AddCompilationOptions(project, _configuration, compilerIO); |             AddCompilationOptions(project, _configuration, inputs); | ||||||
| 
 | 
 | ||||||
|             // input / output: resources with culture |             // input / output: resources with culture | ||||||
|             AddNonCultureResources(project, calculator.IntermediateOutputDirectoryPath, compilerIO); |             AddNonCultureResources(project, calculator.IntermediateOutputDirectoryPath, inputs, outputs); | ||||||
| 
 | 
 | ||||||
|             // input / output: resources without culture |             // input / output: resources without culture | ||||||
|             AddCultureResources(project, binariesOutputPath, compilerIO); |             AddCultureResources(project, binariesOutputPath, inputs, outputs); | ||||||
| 
 | 
 | ||||||
|             return compilerIO; |             return new CompilerIO(inputs, outputs); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 |         private static void AddLockFile(ProjectContext project, List<string> inputs) | ||||||
|         private static void AddLockFile(ProjectContext project, CompilerIO compilerIO) |  | ||||||
|         { |         { | ||||||
|             if (project.LockFile == null) |             if (project.LockFile == null) | ||||||
|             { |             { | ||||||
|  | @ -113,48 +101,48 @@ namespace Microsoft.DotNet.Tools.Build | ||||||
|                 throw new InvalidOperationException(errorMessage); |                 throw new InvalidOperationException(errorMessage); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             compilerIO.Inputs.Add(project.LockFile.LockFilePath); |             inputs.Add(project.LockFile.LockFilePath); | ||||||
| 
 | 
 | ||||||
|             if (project.LockFile.ExportFile != null) |             if (project.LockFile.ExportFile != null) | ||||||
|             { |             { | ||||||
|                 compilerIO.Inputs.Add(project.LockFile.ExportFile.ExportFilePath); |                 inputs.Add(project.LockFile.ExportFile.ExportFilePath); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         private static void AddCompilationOptions(ProjectContext project, string config, CompilerIO compilerIO) |         private static void AddCompilationOptions(ProjectContext project, string config, List<string> inputs) | ||||||
|         { |         { | ||||||
|             var compilerOptions = project.ResolveCompilationOptions(config); |             var compilerOptions = project.ResolveCompilationOptions(config); | ||||||
| 
 | 
 | ||||||
|             // input: key file |             // input: key file | ||||||
|             if (compilerOptions.KeyFile != null) |             if (compilerOptions.KeyFile != null) | ||||||
|             { |             { | ||||||
|                 compilerIO.Inputs.Add(compilerOptions.KeyFile); |                 inputs.Add(compilerOptions.KeyFile); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private static void AddNonCultureResources(ProjectContext project, string intermediaryOutputPath, CompilerIO compilerIO) |         private static void AddNonCultureResources(ProjectContext project, string intermediaryOutputPath, List<string> inputs, IList<string> outputs) | ||||||
|         { |         { | ||||||
|             foreach (var resourceIO in CompilerUtil.GetNonCultureResources(project.ProjectFile, intermediaryOutputPath)) |             foreach (var resourceIO in CompilerUtil.GetNonCultureResources(project.ProjectFile, intermediaryOutputPath)) | ||||||
|             { |             { | ||||||
|                 compilerIO.Inputs.Add(resourceIO.InputFile); |                 inputs.Add(resourceIO.InputFile); | ||||||
| 
 | 
 | ||||||
|                 if (resourceIO.OutputFile != null) |                 if (resourceIO.OutputFile != null) | ||||||
|                 { |                 { | ||||||
|                     compilerIO.Outputs.Add(resourceIO.OutputFile); |                     outputs.Add(resourceIO.OutputFile); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private static void AddCultureResources(ProjectContext project, string outputPath, CompilerIO compilerIO) |         private static void AddCultureResources(ProjectContext project, string outputPath, List<string> inputs, List<string> outputs) | ||||||
|         { |         { | ||||||
|             foreach (var cultureResourceIO in CompilerUtil.GetCultureResources(project.ProjectFile, outputPath)) |             foreach (var cultureResourceIO in CompilerUtil.GetCultureResources(project.ProjectFile, outputPath)) | ||||||
|             { |             { | ||||||
|                 compilerIO.Inputs.AddRange(cultureResourceIO.InputFileToMetadata.Keys); |                 inputs.AddRange(cultureResourceIO.InputFileToMetadata.Keys); | ||||||
| 
 | 
 | ||||||
|                 if (cultureResourceIO.OutputFile != null) |                 if (cultureResourceIO.OutputFile != null) | ||||||
|                 { |                 { | ||||||
|                     compilerIO.Outputs.Add(cultureResourceIO.OutputFile); |                     outputs.Add(cultureResourceIO.OutputFile); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -21,14 +21,17 @@ namespace Microsoft.DotNet.Tools.Build | ||||||
|         private readonly CompilerIOManager _compilerIOManager; |         private readonly CompilerIOManager _compilerIOManager; | ||||||
|         private readonly ScriptRunner _scriptRunner; |         private readonly ScriptRunner _scriptRunner; | ||||||
|         private readonly DotNetCommandFactory _commandFactory; |         private readonly DotNetCommandFactory _commandFactory; | ||||||
|  |         private readonly IncrementalManager _incrementalManager; | ||||||
| 
 | 
 | ||||||
|         public DotNetProjectBuilder(BuildCommandApp args) |         public DotNetProjectBuilder(BuildCommandApp args) | ||||||
|         { |         { | ||||||
|             _args = args; |             _args = args; | ||||||
|  | 
 | ||||||
|             _preconditionManager = new IncrementalPreconditionManager( |             _preconditionManager = new IncrementalPreconditionManager( | ||||||
|                 args.ShouldPrintIncrementalPreconditions, |                 args.ShouldPrintIncrementalPreconditions, | ||||||
|                 args.ShouldNotUseIncrementality, |                 args.ShouldNotUseIncrementality, | ||||||
|                 args.ShouldSkipDependencies); |                 args.ShouldSkipDependencies); | ||||||
|  | 
 | ||||||
|             _compilerIOManager = new CompilerIOManager( |             _compilerIOManager = new CompilerIOManager( | ||||||
|                 args.ConfigValue, |                 args.ConfigValue, | ||||||
|                 args.OutputValue, |                 args.OutputValue, | ||||||
|  | @ -36,7 +39,19 @@ namespace Microsoft.DotNet.Tools.Build | ||||||
|                 args.GetRuntimes(), |                 args.GetRuntimes(), | ||||||
|                 args.Workspace |                 args.Workspace | ||||||
|                 ); |                 ); | ||||||
|  | 
 | ||||||
|  |             _incrementalManager = new IncrementalManager( | ||||||
|  |                 this, | ||||||
|  |                 _compilerIOManager, | ||||||
|  |                 _preconditionManager, | ||||||
|  |                 _args.ShouldSkipDependencies, | ||||||
|  |                 _args.ConfigValue, | ||||||
|  |                 _args.BuildBasePathValue, | ||||||
|  |                 _args.OutputValue | ||||||
|  |                 ); | ||||||
|  | 
 | ||||||
|             _scriptRunner = new ScriptRunner(); |             _scriptRunner = new ScriptRunner(); | ||||||
|  | 
 | ||||||
|             _commandFactory = new DotNetCommandFactory(); |             _commandFactory = new DotNetCommandFactory(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -52,7 +67,7 @@ namespace Microsoft.DotNet.Tools.Build | ||||||
|                     Directory.CreateDirectory(parentDirectory); |                     Directory.CreateDirectory(parentDirectory); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 string content = ComputeCurrentVersionFileData(); |                 string content = DotnetFiles.ReadAndInterpretVersionFile(); | ||||||
| 
 | 
 | ||||||
|                 File.WriteAllText(projectVersionFile, content); |                 File.WriteAllText(projectVersionFile, content); | ||||||
|             } |             } | ||||||
|  | @ -62,14 +77,6 @@ namespace Microsoft.DotNet.Tools.Build | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private static string ComputeCurrentVersionFileData() |  | ||||||
|         { |  | ||||||
|             var content = File.ReadAllText(DotnetFiles.VersionFile); |  | ||||||
|             content += Environment.NewLine; |  | ||||||
|             content += PlatformServices.Default.Runtime.GetRuntimeIdentifier(); |  | ||||||
|             return content; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private void PrintSummary(ProjectGraphNode projectNode, bool success) |         private void PrintSummary(ProjectGraphNode projectNode, bool success) | ||||||
|         { |         { | ||||||
|             // todo: Ideally it's the builder's responsibility for adding the time elapsed. That way we avoid cross cutting display concerns between compile and build for printing time elapsed |             // todo: Ideally it's the builder's responsibility for adding the time elapsed. That way we avoid cross cutting display concerns between compile and build for printing time elapsed | ||||||
|  | @ -83,17 +90,6 @@ namespace Microsoft.DotNet.Tools.Build | ||||||
|             Reporter.Output.WriteLine(); |             Reporter.Output.WriteLine(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private void CreateOutputDirectories() |  | ||||||
|         { |  | ||||||
|             if (!string.IsNullOrEmpty(_args.OutputValue)) |  | ||||||
|             { |  | ||||||
|                 Directory.CreateDirectory(_args.OutputValue); |  | ||||||
|             } |  | ||||||
|             if (!string.IsNullOrEmpty(_args.BuildBasePathValue)) |  | ||||||
|             { |  | ||||||
|                 Directory.CreateDirectory(_args.BuildBasePathValue); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         private void CopyCompilationOutput(OutputPaths outputPaths) |         private void CopyCompilationOutput(OutputPaths outputPaths) | ||||||
|         { |         { | ||||||
|  | @ -151,118 +147,47 @@ namespace Microsoft.DotNet.Tools.Build | ||||||
|             finally |             finally | ||||||
|             { |             { | ||||||
|                 StampProjectWithSDKVersion(projectNode.ProjectContext); |                 StampProjectWithSDKVersion(projectNode.ProjectContext); | ||||||
|  |                 _incrementalManager.CacheIncrementalState(projectNode); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         protected override void ProjectSkiped(ProjectGraphNode projectNode) |         protected override void ProjectSkiped(ProjectGraphNode projectNode) | ||||||
|         { |         { | ||||||
|             StampProjectWithSDKVersion(projectNode.ProjectContext); |             StampProjectWithSDKVersion(projectNode.ProjectContext); | ||||||
|         } |             _incrementalManager.CacheIncrementalState(projectNode); | ||||||
| 
 |  | ||||||
|         private bool CLIChangedSinceLastCompilation(ProjectContext project) |  | ||||||
|         { |  | ||||||
|             var currentVersionFile = DotnetFiles.VersionFile; |  | ||||||
|             var versionFileFromLastCompile = project.GetSDKVersionFile(_args.ConfigValue, _args.BuildBasePathValue, _args.OutputValue); |  | ||||||
| 
 |  | ||||||
|             if (!File.Exists(currentVersionFile)) |  | ||||||
|             { |  | ||||||
|                 // this CLI does not have a version file; cannot tell if CLI changed |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (!File.Exists(versionFileFromLastCompile)) |  | ||||||
|             { |  | ||||||
|                 // this is the first compilation; cannot tell if CLI changed |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             var currentContent = ComputeCurrentVersionFileData(); |  | ||||||
| 
 |  | ||||||
|             var versionsAreEqual = string.Equals(currentContent, File.ReadAllText(versionFileFromLastCompile), StringComparison.OrdinalIgnoreCase); |  | ||||||
| 
 |  | ||||||
|             return !versionsAreEqual; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         protected override bool NeedsRebuilding(ProjectGraphNode graphNode) |         protected override bool NeedsRebuilding(ProjectGraphNode graphNode) | ||||||
|         { |         { | ||||||
|             var project = graphNode.ProjectContext; |             var result = _incrementalManager.NeedsRebuilding(graphNode); | ||||||
|             if (_args.ShouldNotUseIncrementality) | 
 | ||||||
|             { |             PrintIncrementalResult(graphNode.ProjectContext.GetDisplayName(), result); | ||||||
|                 return true; | 
 | ||||||
|             } |             return result.NeedsRebuilding; | ||||||
|             if (!_args.ShouldSkipDependencies && |  | ||||||
|                 graphNode.Dependencies.Any(d => GetCompilationResult(d) != CompilationResult.IncrementalSkip)) |  | ||||||
|             { |  | ||||||
|                 Reporter.Output.WriteLine($"Project {project.GetDisplayName()} will be compiled because some of it's dependencies changed"); |  | ||||||
|                 return true; |  | ||||||
|             } |  | ||||||
|             var preconditions = _preconditionManager.GetIncrementalPreconditions(graphNode); |  | ||||||
|             if (preconditions.PreconditionsDetected()) |  | ||||||
|             { |  | ||||||
|                 return true; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|             if (CLIChangedSinceLastCompilation(project)) |         private void PrintIncrementalResult(string projectName, IncrementalResult result) | ||||||
|         { |         { | ||||||
|                 Reporter.Output.WriteLine($"Project {project.GetDisplayName()} will be compiled because the version or bitness of the CLI changed since the last build"); |             if (result.NeedsRebuilding) | ||||||
|                 return true; |             { | ||||||
|  |                 Reporter.Output.WriteLine($"Project {projectName} will be compiled because {result.Reason}"); | ||||||
|  |                 PrintIncrementalItems(result); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 Reporter.Output.WriteLine($"Project {projectName} was previously compiled. Skipping compilation."); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|             var compilerIO = _compilerIOManager.GetCompileIO(graphNode); |         private static void PrintIncrementalItems(IncrementalResult result) | ||||||
| 
 |  | ||||||
|             // rebuild if empty inputs / outputs |  | ||||||
|             if (!(compilerIO.Outputs.Any() && compilerIO.Inputs.Any())) |  | ||||||
|         { |         { | ||||||
|                 Reporter.Output.WriteLine($"Project {project.GetDisplayName()} will be compiled because it either has empty inputs or outputs"); |             if (Reporter.IsVerbose) | ||||||
|                 return true; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             //rebuild if missing inputs / outputs |  | ||||||
|             if (_compilerIOManager.AnyMissingIO(project, compilerIO.Outputs, "outputs") || _compilerIOManager.AnyMissingIO(project, compilerIO.Inputs, "inputs")) |  | ||||||
|             { |             { | ||||||
|                 return true; |                 foreach (var item in result.Items) | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             // find the output with the earliest write time |  | ||||||
|             var minOutputPath = compilerIO.Outputs.First(); |  | ||||||
|             var minDateUtc = File.GetLastWriteTimeUtc(minOutputPath); |  | ||||||
| 
 |  | ||||||
|             foreach (var outputPath in compilerIO.Outputs) |  | ||||||
|                 { |                 { | ||||||
|                 if (File.GetLastWriteTimeUtc(outputPath) >= minDateUtc) |                     Reporter.Verbose.WriteLine($"\t{item}"); | ||||||
|                 { |  | ||||||
|                     continue; |  | ||||||
|                 } |                 } | ||||||
| 
 |  | ||||||
|                 minDateUtc = File.GetLastWriteTimeUtc(outputPath); |  | ||||||
|                 minOutputPath = outputPath; |  | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|             // find inputs that are older than the earliest output |  | ||||||
|             var newInputs = compilerIO.Inputs.FindAll(p => File.GetLastWriteTimeUtc(p) >= minDateUtc); |  | ||||||
| 
 |  | ||||||
|             if (!newInputs.Any()) |  | ||||||
|             { |  | ||||||
|                 Reporter.Output.WriteLine($"Project {project.GetDisplayName()} was previously compiled. Skipping compilation."); |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             Reporter.Output.WriteLine($"Project {project.GetDisplayName()} will be compiled because some of its inputs were newer than its oldest output."); |  | ||||||
|             Reporter.Verbose.WriteLine(); |  | ||||||
|             Reporter.Verbose.WriteLine($" Oldest output item:"); |  | ||||||
|             Reporter.Verbose.WriteLine($"  {minDateUtc.ToLocalTime()}: {minOutputPath}"); |  | ||||||
|             Reporter.Verbose.WriteLine(); |  | ||||||
| 
 |  | ||||||
|             Reporter.Verbose.WriteLine($" Inputs newer than the oldest output item:"); |  | ||||||
| 
 |  | ||||||
|             foreach (var newInput in newInputs) |  | ||||||
|             { |  | ||||||
|                 Reporter.Verbose.WriteLine($"  {File.GetLastWriteTime(newInput)}: {newInput}"); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             Reporter.Verbose.WriteLine(); |  | ||||||
| 
 |  | ||||||
|             return true; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
							
								
								
									
										92
									
								
								src/dotnet/commands/dotnet-build/IncrementalCache.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/dotnet/commands/dotnet-build/IncrementalCache.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,92 @@ | ||||||
|  | // Copyright (c) .NET Foundation and contributors. All rights reserved. | ||||||
|  | // 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 Newtonsoft.Json; | ||||||
|  | using Newtonsoft.Json.Linq; | ||||||
|  | 
 | ||||||
|  | namespace Microsoft.DotNet.Tools.Build | ||||||
|  | { | ||||||
|  |     internal class IncrementalCache | ||||||
|  |     { | ||||||
|  |         private const string InputsKeyName = "inputs"; | ||||||
|  |         private const string OutputsKeyNane = "outputs"; | ||||||
|  | 
 | ||||||
|  |         public CompilerIO CompilerIO { get; } | ||||||
|  | 
 | ||||||
|  |         public IncrementalCache(CompilerIO compilerIO) | ||||||
|  |         { | ||||||
|  |             CompilerIO = compilerIO; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public void WriteToFile(string cacheFile) | ||||||
|  |         { | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 CreatePathIfAbsent(cacheFile); | ||||||
|  | 
 | ||||||
|  |                 using (var streamWriter = new StreamWriter(new FileStream(cacheFile, FileMode.Create, FileAccess.Write, FileShare.None))) | ||||||
|  |                 { | ||||||
|  |                     var rootObject = new JObject(); | ||||||
|  |                     rootObject[InputsKeyName] = new JArray(CompilerIO.Inputs); | ||||||
|  |                     rootObject[OutputsKeyNane] = new JArray(CompilerIO.Outputs); | ||||||
|  | 
 | ||||||
|  |                     JsonSerializer.Create().Serialize(streamWriter, rootObject); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             catch (Exception e) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidDataException($"Could not write the incremental cache file: {cacheFile}", e); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static void CreatePathIfAbsent(string filePath) | ||||||
|  |         { | ||||||
|  |             var parentDir = Path.GetDirectoryName(filePath); | ||||||
|  | 
 | ||||||
|  |             if (!Directory.Exists(parentDir)) | ||||||
|  |             { | ||||||
|  |                 Directory.CreateDirectory(parentDir); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public static IncrementalCache ReadFromFile(string cacheFile) | ||||||
|  |         { | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 using (var streamReader = new StreamReader(new FileStream(cacheFile, FileMode.Open, FileAccess.Read, FileShare.Read))) | ||||||
|  |                 { | ||||||
|  |                     var jObject = JObject.Parse(streamReader.ReadToEnd()); | ||||||
|  | 
 | ||||||
|  |                     if (jObject == null) | ||||||
|  |                     { | ||||||
|  |                         throw new InvalidDataException(); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     var inputs = ReadArray<string>(jObject, InputsKeyName); | ||||||
|  |                     var outputs = ReadArray<string>(jObject, OutputsKeyNane); | ||||||
|  | 
 | ||||||
|  |                     return new IncrementalCache(new CompilerIO(inputs, outputs)); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             catch (Exception e) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidDataException($"Could not read the incremental cache file: {cacheFile}", e); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static IEnumerable<T> ReadArray<T>(JObject jObject, string keyName) | ||||||
|  |         { | ||||||
|  |             var array = jObject.Value<JToken>(keyName)?.Values<T>(); | ||||||
|  | 
 | ||||||
|  |             if (array == null) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidDataException($"Could not read key {keyName}"); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return array; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										207
									
								
								src/dotnet/commands/dotnet-build/IncrementalManager.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								src/dotnet/commands/dotnet-build/IncrementalManager.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,207 @@ | ||||||
|  | // Copyright (c) .NET Foundation and contributors. All rights reserved. | ||||||
|  | // 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 System.Linq; | ||||||
|  | using Microsoft.DotNet.Cli.Compiler.Common; | ||||||
|  | using Microsoft.DotNet.Cli.Utils; | ||||||
|  | using Microsoft.DotNet.ProjectModel; | ||||||
|  | using Microsoft.DotNet.ProjectModel.Utilities; | ||||||
|  | using Microsoft.DotNet.Tools.Compiler; | ||||||
|  | using Microsoft.Extensions.PlatformAbstractions; | ||||||
|  | using Microsoft.DotNet.ProjectModel.Compilation; | ||||||
|  | using NuGet.Protocol.Core.Types; | ||||||
|  | 
 | ||||||
|  | namespace Microsoft.DotNet.Tools.Build | ||||||
|  | { | ||||||
|  |     internal class IncrementalManager | ||||||
|  |     { | ||||||
|  |         private readonly ProjectBuilder _projectBuilder; | ||||||
|  |         private readonly CompilerIOManager _compilerIoManager; | ||||||
|  |         private readonly IncrementalPreconditionManager _preconditionManager; | ||||||
|  |         private readonly bool _shouldSkipDependencies; | ||||||
|  |         private readonly string _configuration; | ||||||
|  |         private readonly string _buildBasePath; | ||||||
|  |         private readonly string _outputPath; | ||||||
|  | 
 | ||||||
|  |         public IncrementalManager( | ||||||
|  |             ProjectBuilder projectBuilder, | ||||||
|  |             CompilerIOManager compilerIOManager, | ||||||
|  |             IncrementalPreconditionManager incrementalPreconditionManager, | ||||||
|  |             bool shouldSkipDependencies, | ||||||
|  |             string configuration, | ||||||
|  |             string buildBasePath, | ||||||
|  |             string outputPath) | ||||||
|  |         { | ||||||
|  |             _projectBuilder = projectBuilder; | ||||||
|  |             _compilerIoManager = compilerIOManager; | ||||||
|  |             _preconditionManager = incrementalPreconditionManager; | ||||||
|  |             _shouldSkipDependencies = shouldSkipDependencies; | ||||||
|  |             _configuration = configuration; | ||||||
|  |             _buildBasePath = buildBasePath; | ||||||
|  |             _outputPath = outputPath; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public IncrementalResult NeedsRebuilding(ProjectGraphNode graphNode) | ||||||
|  |         { | ||||||
|  |             if (!_shouldSkipDependencies && | ||||||
|  |                 graphNode.Dependencies.Any(d => _projectBuilder.GetCompilationResult(d) != CompilationResult.IncrementalSkip)) | ||||||
|  |             { | ||||||
|  |                 return new IncrementalResult("dependencies changed"); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var preconditions = _preconditionManager.GetIncrementalPreconditions(graphNode); | ||||||
|  |             if (preconditions.PreconditionsDetected()) | ||||||
|  |             { | ||||||
|  |                 return new IncrementalResult($"project is not safe for incremental compilation. Use {BuildCommandApp.BuildProfileFlag} flag for more information."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var compilerIO = _compilerIoManager.GetCompileIO(graphNode); | ||||||
|  | 
 | ||||||
|  |             var result = CLIChanged(graphNode); | ||||||
|  |             if (result.NeedsRebuilding) | ||||||
|  |             { | ||||||
|  |                 return result; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             result = InputItemsChanged(graphNode, compilerIO); | ||||||
|  |             if (result.NeedsRebuilding) | ||||||
|  |             { | ||||||
|  |                 return result; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             result = TimestampsChanged(compilerIO); | ||||||
|  |             if (result.NeedsRebuilding) | ||||||
|  |             { | ||||||
|  |                 return result; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return IncrementalResult.DoesNotNeedRebuild; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private IncrementalResult CLIChanged(ProjectGraphNode graphNode) | ||||||
|  |         { | ||||||
|  |             var currentVersionFile = DotnetFiles.VersionFile; | ||||||
|  |             var versionFileFromLastCompile = graphNode.ProjectContext.GetSDKVersionFile(_configuration, _buildBasePath, _outputPath); | ||||||
|  | 
 | ||||||
|  |             if (!File.Exists(currentVersionFile)) | ||||||
|  |             { | ||||||
|  |                 // this CLI does not have a version file; cannot tell if CLI changed | ||||||
|  |                 return IncrementalResult.DoesNotNeedRebuild; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (!File.Exists(versionFileFromLastCompile)) | ||||||
|  |             { | ||||||
|  |                 // this is the first compilation; cannot tell if CLI changed | ||||||
|  |                 return IncrementalResult.DoesNotNeedRebuild; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var currentContent = DotnetFiles.ReadAndInterpretVersionFile(); | ||||||
|  | 
 | ||||||
|  |             var versionsAreEqual = string.Equals(currentContent, File.ReadAllText(versionFileFromLastCompile), StringComparison.OrdinalIgnoreCase); | ||||||
|  | 
 | ||||||
|  |             return versionsAreEqual | ||||||
|  |                 ? IncrementalResult.DoesNotNeedRebuild | ||||||
|  |                 : new IncrementalResult("the version or bitness of the CLI changed since the last build"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private IncrementalResult InputItemsChanged(ProjectGraphNode graphNode, CompilerIO compilerIO) | ||||||
|  |         { | ||||||
|  |             // check empty inputs / outputs | ||||||
|  |             if (!compilerIO.Inputs.Any()) | ||||||
|  |             { | ||||||
|  |                 return new IncrementalResult("the project has no inputs"); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (!compilerIO.Outputs.Any()) | ||||||
|  |             { | ||||||
|  |                 return new IncrementalResult("the project has no outputs"); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // check non existent items | ||||||
|  |             var result = CheckMissingIO(compilerIO.Inputs, "inputs"); | ||||||
|  |             if (result.NeedsRebuilding) | ||||||
|  |             { | ||||||
|  |                 return result; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             result = CheckMissingIO(compilerIO.Outputs, "outputs"); | ||||||
|  |             if (result.NeedsRebuilding) | ||||||
|  |             { | ||||||
|  |                 return result; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return CheckInputGlobChanges(graphNode, compilerIO); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private IncrementalResult CheckInputGlobChanges(ProjectGraphNode graphNode, CompilerIO compilerIO) | ||||||
|  |         { | ||||||
|  |             // check cache against input glob pattern changes | ||||||
|  |             var incrementalCacheFile = graphNode.ProjectContext.IncrementalCacheFile(_configuration, _buildBasePath, _outputPath); | ||||||
|  | 
 | ||||||
|  |             if (!File.Exists(incrementalCacheFile)) | ||||||
|  |             { | ||||||
|  |                 // cache is not present (first compilation); can't determine if globs changed; cache will be generated after build processes project | ||||||
|  |                 return IncrementalResult.DoesNotNeedRebuild; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var incrementalCache = IncrementalCache.ReadFromFile(incrementalCacheFile); | ||||||
|  | 
 | ||||||
|  |             var diffResult = compilerIO.DiffInputs(incrementalCache.CompilerIO); | ||||||
|  | 
 | ||||||
|  |             if (diffResult.Deletions.Any()) | ||||||
|  |             { | ||||||
|  |                 return new IncrementalResult("Input items removed from last build", diffResult.Deletions); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (diffResult.Additions.Any()) | ||||||
|  |             { | ||||||
|  |                 return new IncrementalResult("Input items added from last build", diffResult.Additions); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return IncrementalResult.DoesNotNeedRebuild; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private IncrementalResult CheckMissingIO(IEnumerable<string> items, string itemsType) | ||||||
|  |         { | ||||||
|  |             var missingItems = items.Where(i => !File.Exists(i)).ToList(); | ||||||
|  | 
 | ||||||
|  |             return missingItems.Any() | ||||||
|  |                 ? new IncrementalResult($"expected {itemsType} are missing", missingItems) | ||||||
|  |                 : IncrementalResult.DoesNotNeedRebuild; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private IncrementalResult TimestampsChanged(CompilerIO compilerIO) | ||||||
|  |         { | ||||||
|  |             // find the output with the earliest write time | ||||||
|  |             var minDateUtc = DateTime.MaxValue; | ||||||
|  | 
 | ||||||
|  |             foreach (var outputPath in compilerIO.Outputs) | ||||||
|  |             { | ||||||
|  |                 var lastWriteTimeUtc = File.GetLastWriteTimeUtc(outputPath); | ||||||
|  | 
 | ||||||
|  |                 if (lastWriteTimeUtc < minDateUtc) | ||||||
|  |                 { | ||||||
|  |                     minDateUtc = lastWriteTimeUtc; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // find inputs that are older than the earliest output | ||||||
|  |             var newInputs = compilerIO.Inputs.Where(p => File.GetLastWriteTimeUtc(p) >= minDateUtc); | ||||||
|  | 
 | ||||||
|  |             return newInputs.Any() | ||||||
|  |                 ? new IncrementalResult("inputs were modified", newInputs) | ||||||
|  |                 : IncrementalResult.DoesNotNeedRebuild; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public void CacheIncrementalState(ProjectGraphNode graphNode) | ||||||
|  |         { | ||||||
|  |             var incrementalCacheFile = graphNode.ProjectContext.IncrementalCacheFile(_configuration, _buildBasePath, _outputPath); | ||||||
|  | 
 | ||||||
|  |             var incrementalCache = new IncrementalCache(_compilerIoManager.GetCompileIO(graphNode)); | ||||||
|  |             incrementalCache.WriteToFile(incrementalCacheFile); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								src/dotnet/commands/dotnet-build/IncrementalResult.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/dotnet/commands/dotnet-build/IncrementalResult.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | ||||||
|  | // Copyright (c) .NET Foundation and contributors. All rights reserved. | ||||||
|  | // Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||||||
|  | 
 | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | 
 | ||||||
|  | namespace Microsoft.DotNet.Tools.Build | ||||||
|  | { | ||||||
|  |     internal class IncrementalResult | ||||||
|  |     { | ||||||
|  |         public static readonly IncrementalResult DoesNotNeedRebuild = new IncrementalResult(false, "", Enumerable.Empty<string>()); | ||||||
|  | 
 | ||||||
|  |         public bool NeedsRebuilding { get; } | ||||||
|  |         public string Reason { get; } | ||||||
|  |         public IEnumerable<string> Items { get; } | ||||||
|  | 
 | ||||||
|  |         private IncrementalResult(bool needsRebuilding, string reason, IEnumerable<string> items) | ||||||
|  |         { | ||||||
|  |             NeedsRebuilding = needsRebuilding; | ||||||
|  |             Reason = reason; | ||||||
|  |             Items = items; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public IncrementalResult(string reason) | ||||||
|  |             : this(true, reason, Enumerable.Empty<string>()) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public IncrementalResult(string reason, IEnumerable<string> items) | ||||||
|  |             : this(true, reason, items) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -21,7 +21,7 @@ namespace Microsoft.DotNet.Tools.Build | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         protected CompilationResult? GetCompilationResult(ProjectGraphNode projectNode) |         public CompilationResult? GetCompilationResult(ProjectGraphNode projectNode) | ||||||
|         { |         { | ||||||
|             CompilationResult result; |             CompilationResult result; | ||||||
|             if (_compilationResults.TryGetValue(projectNode.ProjectContext.Identity, out result)) |             if (_compilationResults.TryGetValue(projectNode.ProjectContext.Identity, out result)) | ||||||
|  |  | ||||||
|  | @ -38,6 +38,7 @@ namespace Microsoft.DotNet.Tools.Builder.Tests | ||||||
|             buildResult.Should().HaveCompiledProject(MainProject, _appFrameworkFullName); |             buildResult.Should().HaveCompiledProject(MainProject, _appFrameworkFullName); | ||||||
| 
 | 
 | ||||||
|             buildResult = BuildProject(noIncremental: true); |             buildResult = BuildProject(noIncremental: true); | ||||||
|  |             buildResult.Should().HaveCompiledProject(MainProject, _appFrameworkFullName); | ||||||
|             Assert.Contains("[Forced Unsafe]", buildResult.StdOut); |             Assert.Contains("[Forced Unsafe]", buildResult.StdOut); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -85,12 +86,12 @@ namespace Microsoft.DotNet.Tools.Builder.Tests | ||||||
|             CreateTestInstance(); |             CreateTestInstance(); | ||||||
|             BuildProject().Should().HaveCompiledProject(MainProject, _appFrameworkFullName); |             BuildProject().Should().HaveCompiledProject(MainProject, _appFrameworkFullName); | ||||||
| 
 | 
 | ||||||
|             //change version file |             // change version file | ||||||
|             var versionFile = Path.Combine(GetIntermediaryOutputPath(), ".SDKVersion"); |             var versionFile = Path.Combine(GetIntermediaryOutputPath(), ".SDKVersion"); | ||||||
|             File.Exists(versionFile).Should().BeTrue(); |             File.Exists(versionFile).Should().BeTrue(); | ||||||
|             File.AppendAllText(versionFile, "text"); |             File.AppendAllText(versionFile, "text"); | ||||||
| 
 | 
 | ||||||
|             //assert rebuilt |             // assert rebuilt | ||||||
|             BuildProject().Should().HaveCompiledProject(MainProject, _appFrameworkFullName); |             BuildProject().Should().HaveCompiledProject(MainProject, _appFrameworkFullName); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -100,19 +101,80 @@ namespace Microsoft.DotNet.Tools.Builder.Tests | ||||||
|             CreateTestInstance(); |             CreateTestInstance(); | ||||||
|             BuildProject().Should().HaveCompiledProject(MainProject, _appFrameworkFullName); |             BuildProject().Should().HaveCompiledProject(MainProject, _appFrameworkFullName); | ||||||
| 
 | 
 | ||||||
|             //delete version file |             // delete version file | ||||||
|             var versionFile = Path.Combine(GetIntermediaryOutputPath(), ".SDKVersion"); |             var versionFile = Path.Combine(GetIntermediaryOutputPath(), ".SDKVersion"); | ||||||
|             File.Exists(versionFile).Should().BeTrue(); |             File.Exists(versionFile).Should().BeTrue(); | ||||||
|             File.Delete(versionFile); |             File.Delete(versionFile); | ||||||
|             File.Exists(versionFile).Should().BeFalse(); |             File.Exists(versionFile).Should().BeFalse(); | ||||||
| 
 | 
 | ||||||
|             //assert build skipped due to no version file |             // assert build skipped due to no version file | ||||||
|             BuildProject().Should().HaveSkippedProjectCompilation(MainProject, _appFrameworkFullName); |             BuildProject().Should().HaveSkippedProjectCompilation(MainProject, _appFrameworkFullName); | ||||||
| 
 | 
 | ||||||
|             //the version file should have been regenerated during the build, even if compilation got skipped |             // the version file should have been regenerated during the build, even if compilation got skipped | ||||||
|             File.Exists(versionFile).Should().BeTrue(); |             File.Exists(versionFile).Should().BeTrue(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         [Fact] | ||||||
|  |         public void TestRebuildDeletedSource() | ||||||
|  |         { | ||||||
|  |             CreateTestInstance(); | ||||||
|  |             BuildProject().Should().HaveCompiledProject(MainProject, _appFrameworkFullName); | ||||||
|  | 
 | ||||||
|  |             var sourceFile = Path.Combine(GetProjectDirectory(MainProject), "Program2.cs"); | ||||||
|  |             File.Delete(sourceFile); | ||||||
|  |             Assert.False(File.Exists(sourceFile)); | ||||||
|  | 
 | ||||||
|  |             // second build; should get rebuilt since we deleted a source file | ||||||
|  |             BuildProject().Should().HaveCompiledProject(MainProject, _appFrameworkFullName); | ||||||
|  | 
 | ||||||
|  |             // third build; incremental cache should have been regenerated and project skipped | ||||||
|  |             BuildProject().Should().HaveSkippedProjectCompilation(MainProject, _appFrameworkFullName); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Fact] | ||||||
|  |         public void TestRebuildRenamedSource() | ||||||
|  |         { | ||||||
|  |             CreateTestInstance(); | ||||||
|  |             var buildResult = BuildProject(); | ||||||
|  |             buildResult.Should().HaveCompiledProject(MainProject, _appFrameworkFullName); | ||||||
|  | 
 | ||||||
|  |             var sourceFile = Path.Combine(GetProjectDirectory(MainProject), "Program2.cs"); | ||||||
|  |             var destinationFile = Path.Combine(Path.GetDirectoryName(sourceFile), "ProgramNew.cs"); | ||||||
|  |             File.Move(sourceFile, destinationFile); | ||||||
|  |             Assert.False(File.Exists(sourceFile)); | ||||||
|  |             Assert.True(File.Exists(destinationFile)); | ||||||
|  | 
 | ||||||
|  |             // second build; should get rebuilt since we renamed a source file | ||||||
|  |             buildResult = BuildProject(); | ||||||
|  |             buildResult.Should().HaveCompiledProject(MainProject, _appFrameworkFullName); | ||||||
|  | 
 | ||||||
|  |             // third build; incremental cache should have been regenerated and project skipped | ||||||
|  |             BuildProject().Should().HaveSkippedProjectCompilation(MainProject, _appFrameworkFullName); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [Fact] | ||||||
|  |         public void TestRebuildDeletedSourceAfterCliChanged() | ||||||
|  |         { | ||||||
|  |             CreateTestInstance(); | ||||||
|  |             BuildProject().Should().HaveCompiledProject(MainProject, _appFrameworkFullName); | ||||||
|  | 
 | ||||||
|  |             // change version file | ||||||
|  |             var versionFile = Path.Combine(GetIntermediaryOutputPath(), ".SDKVersion"); | ||||||
|  |             File.Exists(versionFile).Should().BeTrue(); | ||||||
|  |             File.AppendAllText(versionFile, "text"); | ||||||
|  | 
 | ||||||
|  |             // delete a source file | ||||||
|  |             var sourceFile = Path.Combine(GetProjectDirectory(MainProject), "Program2.cs"); | ||||||
|  |             File.Delete(sourceFile); | ||||||
|  |             Assert.False(File.Exists(sourceFile)); | ||||||
|  | 
 | ||||||
|  |             // should get rebuilt since we changed version file and deleted source file | ||||||
|  |             BuildProject().Should().HaveCompiledProject(MainProject, _appFrameworkFullName); | ||||||
|  | 
 | ||||||
|  |             // third build; incremental cache should have been regenerated and project skipped | ||||||
|  |             BuildProject().Should().HaveSkippedProjectCompilation(MainProject, _appFrameworkFullName); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         [Fact] |         [Fact] | ||||||
|         public void TestRebuildChangedLockFile() |         public void TestRebuildChangedLockFile() | ||||||
|         { |         { | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Mihai Codoban
				Mihai Codoban