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()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue