Compile incrementally
- Clone the args in the CompileContext constructor to bring uniformity to the way args are accessed - Compute IO for a project and have it shared between build and compile - Extract dependency logic into facade - Add tests for incremental build - Add precondition checks for compiler IO add --force-incremental-unsafe flag
This commit is contained in:
parent
28f01faae5
commit
bedeaaf2dc
10 changed files with 501 additions and 114 deletions
|
@ -7,13 +7,16 @@ namespace Microsoft.DotNet.Tools.Build
|
||||||
{
|
{
|
||||||
internal class BuilderCommandApp : CompilerCommandApp
|
internal class BuilderCommandApp : CompilerCommandApp
|
||||||
{
|
{
|
||||||
private const string BuildProfileFlag = "--build-profile";
|
public const string BuildProfileFlag = "--build-profile";
|
||||||
|
public const string ForceUnsafeFlag = "--force-incremental-unsafe";
|
||||||
|
|
||||||
public bool BuildProfileValue => OptionHasValue(BuildProfileFlag);
|
public bool BuildProfileValue => OptionHasValue(BuildProfileFlag);
|
||||||
|
public bool ForceUnsafeValue => OptionHasValue(ForceUnsafeFlag);
|
||||||
|
|
||||||
public BuilderCommandApp(string name, string fullName, string description) : base(name, fullName, description)
|
public BuilderCommandApp(string name, string fullName, string description) : base(name, fullName, description)
|
||||||
{
|
{
|
||||||
AddNoValueOption(BuildProfileFlag, "Set this flag to print the incremental safety checks that prevent incremental compilation");
|
AddNoValueOption(BuildProfileFlag, "Set this flag to print the incremental safety checks that prevent incremental compilation");
|
||||||
|
AddNoValueOption(ForceUnsafeFlag, "Set this flag to mark the entire build as not safe for incrementality");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ using Microsoft.DotNet.Cli.Compiler.Common;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Tools.Build
|
namespace Microsoft.DotNet.Tools.Build
|
||||||
{
|
{
|
||||||
|
// todo: Convert CompileContext into a DAG of dependencies: if a node needs recompilation, the entire path up to root needs compilation
|
||||||
// Knows how to orchestrate compilation for a ProjectContext
|
// Knows how to orchestrate compilation for a ProjectContext
|
||||||
// Collects icnremental safety checks and transitively compiles a project context
|
// Collects icnremental safety checks and transitively compiles a project context
|
||||||
internal class CompileContext
|
internal class CompileContext
|
||||||
|
@ -23,27 +25,27 @@ namespace Microsoft.DotNet.Tools.Build
|
||||||
|
|
||||||
private readonly ProjectContext _rootProject;
|
private readonly ProjectContext _rootProject;
|
||||||
private readonly BuilderCommandApp _args;
|
private readonly BuilderCommandApp _args;
|
||||||
private readonly Dictionary<string, ProjectDescription> _dependencies;
|
|
||||||
private readonly string _outputPath;
|
|
||||||
private readonly string _intermediateOutputPath;
|
|
||||||
private readonly IncrementalPreconditions _preconditions;
|
private readonly IncrementalPreconditions _preconditions;
|
||||||
|
private readonly ProjectDependenciesFacade _dependencies;
|
||||||
|
|
||||||
public bool IsSafeForIncrementalCompilation => _preconditions.PreconditionsDetected();
|
public bool IsSafeForIncrementalCompilation => !_preconditions.PreconditionsDetected();
|
||||||
|
|
||||||
public CompileContext(ProjectContext rootProject, BuilderCommandApp args)
|
public CompileContext(ProjectContext rootProject, BuilderCommandApp args)
|
||||||
{
|
{
|
||||||
_rootProject = rootProject;
|
_rootProject = rootProject;
|
||||||
_args = args;
|
|
||||||
|
// Cleaner to clone the args and mutate the clone than have separate CompileContext fields for mutated args
|
||||||
|
// and then reasoning which ones to get from args and which ones from fields.
|
||||||
|
_args = (BuilderCommandApp) args.ShallowCopy();
|
||||||
|
|
||||||
// Set up Output Paths. They are unique per each CompileContext
|
// Set up Output Paths. They are unique per each CompileContext
|
||||||
// Todo: clone args and mutate the clone so the rest of this class does not have special treatment for output paths
|
_args.OutputValue = _rootProject.GetOutputPath(_args.ConfigValue, _args.OutputValue);
|
||||||
_outputPath = _rootProject.GetOutputPath(_args.ConfigValue, _args.OutputValue);
|
_args.IntermediateValue = _rootProject.GetIntermediateOutputPath(_args.ConfigValue, _args.IntermediateValue, _args.OutputValue);
|
||||||
_intermediateOutputPath = _rootProject.GetIntermediateOutputPath(_args.ConfigValue, _args.IntermediateValue, _args.OutputValue);
|
|
||||||
|
|
||||||
// Set up dependencies
|
// Set up dependencies
|
||||||
_dependencies = GetProjectDependenciesWithSources(_rootProject, _args.ConfigValue);
|
_dependencies = new ProjectDependenciesFacade(_rootProject, _args.ConfigValue);
|
||||||
|
|
||||||
//gather preconditions
|
// gather preconditions
|
||||||
_preconditions = GatherIncrementalPreconditions();
|
_preconditions = GatherIncrementalPreconditions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,26 +53,116 @@ namespace Microsoft.DotNet.Tools.Build
|
||||||
{
|
{
|
||||||
CreateOutputDirectories();
|
CreateOutputDirectories();
|
||||||
|
|
||||||
//compile dependencies
|
// compile dependencies
|
||||||
foreach (var dependency in Sort(_dependencies))
|
foreach (var dependency in Sort(_dependencies.ProjectDependenciesWithSources))
|
||||||
{
|
{
|
||||||
if (!InvokeCompileOnDependency(dependency, _outputPath, _intermediateOutputPath))
|
if (incremental)
|
||||||
|
{
|
||||||
|
var dependencyContext = ProjectContext.Create(dependency.Path, dependency.Framework);
|
||||||
|
|
||||||
|
if (!NeedsRebuilding(dependencyContext, new ProjectDependenciesFacade(dependencyContext, _args.ConfigValue)))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!InvokeCompileOnDependency(dependency))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//compile project
|
if (incremental && !NeedsRebuilding(_rootProject, _dependencies))
|
||||||
var success = InvokeCompileOnRootProject(_outputPath, _intermediateOutputPath);
|
{
|
||||||
|
// todo: what if the previous build had errors / warnings and nothing changed? Need to propagate them in case of incremental
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// compile project
|
||||||
|
var success = InvokeCompileOnRootProject();
|
||||||
|
|
||||||
PrintSummary(success);
|
PrintSummary(success);
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool NeedsRebuilding(ProjectContext project, ProjectDependenciesFacade dependencies)
|
||||||
|
{
|
||||||
|
var compilerIO = GetCompileIO(project, _args.ConfigValue, _args.OutputValue, _args.IntermediateValue, dependencies);
|
||||||
|
|
||||||
|
// rebuild if empty inputs / outputs
|
||||||
|
if (!(compilerIO.Outputs.Any() && compilerIO.Inputs.Any()))
|
||||||
|
{
|
||||||
|
Reporter.Verbose.WriteLine($"\nProject {project.ProjectName()} will be compiled because it either has empty inputs or outputs");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//rebuild if missing inputs / outputs
|
||||||
|
if (AnyMissingIO(project, compilerIO.Outputs, "outputs") || AnyMissingIO(project, compilerIO.Inputs, "inputs"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the output with the earliest write time
|
||||||
|
var minOutputPath = compilerIO.Outputs.First();
|
||||||
|
var minDate = File.GetLastWriteTime(minOutputPath);
|
||||||
|
|
||||||
|
foreach (var outputPath in compilerIO.Outputs)
|
||||||
|
{
|
||||||
|
if (File.GetLastWriteTime(outputPath) >= minDate)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
minDate = File.GetLastWriteTime(outputPath);
|
||||||
|
minOutputPath = outputPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find inputs that are older than the earliest output
|
||||||
|
var newInputs = compilerIO.Inputs.FindAll(p => File.GetLastWriteTime(p) > minDate);
|
||||||
|
|
||||||
|
if (!newInputs.Any())
|
||||||
|
{
|
||||||
|
Reporter.Verbose.WriteLine($"\nSkipped compilation for project {project.ProjectName()}. All the input files were older than the output files.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reporter.Verbose.WriteLine($"\nProject {project.ProjectName()} was compiled because some of its inputs were newer than its oldest output:");
|
||||||
|
Reporter.Verbose.WriteLine($"Oldest output item was written at {minDate} : {minOutputPath}");
|
||||||
|
Reporter.Verbose.WriteLine($"Inputs newer than the oldest output item:");
|
||||||
|
|
||||||
|
foreach (var newInput in newInputs)
|
||||||
|
{
|
||||||
|
Reporter.Verbose.WriteLine($"\t{File.GetLastWriteTime(newInput)}\t:\t{newInput}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static 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($"\nProject {project.ProjectName()} will be compiled because expected {itemsType} are missing: ");
|
||||||
|
|
||||||
|
foreach (var missing in missingItems)
|
||||||
|
{
|
||||||
|
Reporter.Verbose.WriteLine($"\t {missing}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Reporter.Verbose.WriteLine();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void PrintSummary(bool success)
|
private void PrintSummary(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
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
Reporter.Output.Write(" " + _preconditions.LogMessage());
|
Reporter.Output.Write(" " + _preconditions.LogMessage());
|
||||||
|
@ -82,61 +174,40 @@ namespace Microsoft.DotNet.Tools.Build
|
||||||
|
|
||||||
private void CreateOutputDirectories()
|
private void CreateOutputDirectories()
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(_outputPath);
|
Directory.CreateDirectory(_args.OutputValue);
|
||||||
Directory.CreateDirectory(_intermediateOutputPath);
|
Directory.CreateDirectory(_args.IntermediateValue);
|
||||||
}
|
|
||||||
|
|
||||||
//todo make extension of ProjectContext?
|
|
||||||
//returns map with dependencies: string projectName -> ProjectDescription
|
|
||||||
private static Dictionary<string, ProjectDescription> GetProjectDependenciesWithSources(ProjectContext projectContext, string configuration)
|
|
||||||
{
|
|
||||||
var projects = new Dictionary<string, ProjectDescription>();
|
|
||||||
|
|
||||||
// Create the library exporter
|
|
||||||
var exporter = projectContext.CreateExporter(configuration);
|
|
||||||
|
|
||||||
// Gather exports for the project
|
|
||||||
var dependencies = exporter.GetDependencies().ToList();
|
|
||||||
|
|
||||||
// Build project references
|
|
||||||
foreach (var dependency in dependencies)
|
|
||||||
{
|
|
||||||
var projectDependency = dependency.Library as ProjectDescription;
|
|
||||||
|
|
||||||
if (projectDependency != null && projectDependency.Project.Files.SourceFiles.Any())
|
|
||||||
{
|
|
||||||
projects[projectDependency.Identity.Name] = projectDependency;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return projects;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IncrementalPreconditions GatherIncrementalPreconditions()
|
private IncrementalPreconditions GatherIncrementalPreconditions()
|
||||||
{
|
{
|
||||||
var preconditions = new IncrementalPreconditions(_args.BuildProfileValue);
|
var preconditions = new IncrementalPreconditions(_args.BuildProfileValue);
|
||||||
|
|
||||||
|
if (_args.ForceUnsafeValue)
|
||||||
|
{
|
||||||
|
preconditions.AddForceUnsafePrecondition();
|
||||||
|
}
|
||||||
|
|
||||||
var projectsToCheck = GetProjectsToCheck();
|
var projectsToCheck = GetProjectsToCheck();
|
||||||
|
|
||||||
foreach (var project in projectsToCheck)
|
foreach (var project in projectsToCheck)
|
||||||
{
|
{
|
||||||
CollectScriptPreconditions(project, preconditions);
|
CollectScriptPreconditions(project, preconditions);
|
||||||
CollectCompilerNamePreconditions(project, preconditions);
|
CollectCompilerNamePreconditions(project, preconditions);
|
||||||
CheckPathProbing(project, preconditions);
|
CollectCheckPathProbingPreconditions(project, preconditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
return preconditions;
|
return preconditions;
|
||||||
}
|
}
|
||||||
|
|
||||||
//check the entire project tree that needs to be compiled, duplicated for each framework
|
// check the entire project tree that needs to be compiled, duplicated for each framework
|
||||||
private List<ProjectContext> GetProjectsToCheck()
|
private List<ProjectContext> GetProjectsToCheck()
|
||||||
{
|
{
|
||||||
//include initial root project
|
// include initial root project
|
||||||
var contextsToCheck = new List<ProjectContext>(1 + _dependencies.Count) {_rootProject};
|
var contextsToCheck = new List<ProjectContext>(1 + _dependencies.ProjectDependenciesWithSources.Count) {_rootProject};
|
||||||
|
|
||||||
//convert ProjectDescription to ProjectContext
|
// convert ProjectDescription to ProjectContext
|
||||||
var dependencyContexts = _dependencies.Select
|
var dependencyContexts = _dependencies.ProjectDependenciesWithSources.Select
|
||||||
(keyValuePair => ProjectContext.Create(keyValuePair.Value.Path, keyValuePair.Value.TargetFrameworkInfo.FrameworkName));
|
(keyValuePair => ProjectContext.Create(keyValuePair.Value.Path, keyValuePair.Value.Framework));
|
||||||
|
|
||||||
contextsToCheck.AddRange(dependencyContexts);
|
contextsToCheck.AddRange(dependencyContexts);
|
||||||
|
|
||||||
|
@ -144,7 +215,7 @@ namespace Microsoft.DotNet.Tools.Build
|
||||||
return contextsToCheck;
|
return contextsToCheck;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckPathProbing(ProjectContext project, IncrementalPreconditions preconditions)
|
private void CollectCheckPathProbingPreconditions(ProjectContext project, IncrementalPreconditions preconditions)
|
||||||
{
|
{
|
||||||
var pathCommands = CompilerUtil.GetCommandsInvokedByCompile(project)
|
var pathCommands = CompilerUtil.GetCommandsInvokedByCompile(project)
|
||||||
.Select(commandName => Command.Create(commandName, "", project.TargetFramework))
|
.Select(commandName => Command.Create(commandName, "", project.TargetFramework))
|
||||||
|
@ -160,7 +231,7 @@ namespace Microsoft.DotNet.Tools.Build
|
||||||
{
|
{
|
||||||
var projectCompiler = CompilerUtil.ResolveCompilerName(project);
|
var projectCompiler = CompilerUtil.ResolveCompilerName(project);
|
||||||
|
|
||||||
if (KnownCompilers.Any(knownCompiler => knownCompiler.Equals(projectCompiler, StringComparison.Ordinal)))
|
if (!KnownCompilers.Any(knownCompiler => knownCompiler.Equals(projectCompiler, StringComparison.Ordinal)))
|
||||||
{
|
{
|
||||||
preconditions.AddUnknownCompilerPrecondition(project.ProjectName(), projectCompiler);
|
preconditions.AddUnknownCompilerPrecondition(project.ProjectName(), projectCompiler);
|
||||||
}
|
}
|
||||||
|
@ -182,13 +253,13 @@ namespace Microsoft.DotNet.Tools.Build
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool InvokeCompileOnDependency(ProjectDescription projectDependency, string outputPath, string intermediateOutputPath)
|
private bool InvokeCompileOnDependency(ProjectDescription projectDependency)
|
||||||
{
|
{
|
||||||
var compileResult = Command.Create("dotnet-compile",
|
var compileResult = Command.Create("dotnet-compile",
|
||||||
$"--framework {projectDependency.Framework} " +
|
$"--framework {projectDependency.Framework} " +
|
||||||
$"--configuration {_args.ConfigValue} " +
|
$"--configuration {_args.ConfigValue} " +
|
||||||
$"--output \"{outputPath}\" " +
|
$"--output \"{_args.OutputValue}\" " +
|
||||||
$"--temp-output \"{intermediateOutputPath}\" " +
|
$"--temp-output \"{_args.IntermediateValue}\" " +
|
||||||
(_args.NoHostValue ? "--no-host " : string.Empty) +
|
(_args.NoHostValue ? "--no-host " : string.Empty) +
|
||||||
$"\"{projectDependency.Project.ProjectDirectory}\"")
|
$"\"{projectDependency.Project.ProjectDirectory}\"")
|
||||||
.ForwardStdOut()
|
.ForwardStdOut()
|
||||||
|
@ -198,14 +269,14 @@ namespace Microsoft.DotNet.Tools.Build
|
||||||
return compileResult.ExitCode == 0;
|
return compileResult.ExitCode == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool InvokeCompileOnRootProject(string outputPath, string intermediateOutputPath)
|
private bool InvokeCompileOnRootProject()
|
||||||
{
|
{
|
||||||
//todo: add methods to CompilerCommandApp to generate the arg string
|
// todo: add methods to CompilerCommandApp to generate the arg string?
|
||||||
var compileResult = Command.Create("dotnet-compile",
|
var compileResult = Command.Create("dotnet-compile",
|
||||||
$"--framework {_rootProject.TargetFramework} " +
|
$"--framework {_rootProject.TargetFramework} " +
|
||||||
$"--configuration {_args.ConfigValue} " +
|
$"--configuration {_args.ConfigValue} " +
|
||||||
$"--output \"{outputPath}\" " +
|
$"--output \"{_args.OutputValue}\" " +
|
||||||
$"--temp-output \"{intermediateOutputPath}\" " +
|
$"--temp-output \"{_args.IntermediateValue}\" " +
|
||||||
(_args.NoHostValue ? "--no-host " : string.Empty) +
|
(_args.NoHostValue ? "--no-host " : string.Empty) +
|
||||||
//nativeArgs
|
//nativeArgs
|
||||||
(_args.IsNativeValue ? "--native " : string.Empty) +
|
(_args.IsNativeValue ? "--native " : string.Empty) +
|
||||||
|
@ -248,6 +319,96 @@ namespace Microsoft.DotNet.Tools.Build
|
||||||
|
|
||||||
outputs.Add(project);
|
outputs.Add(project);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct CompilerIO
|
||||||
|
{
|
||||||
|
public readonly List<string> Inputs;
|
||||||
|
public readonly List<string> Outputs;
|
||||||
|
|
||||||
|
public CompilerIO(List<string> inputs, List<string> outputs)
|
||||||
|
{
|
||||||
|
Inputs = inputs;
|
||||||
|
Outputs = outputs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 static CompilerIO GetCompileIO(ProjectContext project, string config, string outputPath, string intermediaryOutputPath, ProjectDependenciesFacade dependencies)
|
||||||
|
{
|
||||||
|
var compilerIO = new CompilerIO(new List<string>(), new List<string>());
|
||||||
|
|
||||||
|
// input: project.json
|
||||||
|
compilerIO.Inputs.Add(project.ProjectFile.ProjectFilePath);
|
||||||
|
|
||||||
|
// input: source files
|
||||||
|
compilerIO.Inputs.AddRange(CompilerUtil.GetCompilationSources(project));
|
||||||
|
|
||||||
|
// todo: Factor out dependency resolution between Build and Compile. Ideally Build injects the dependencies into Compile
|
||||||
|
// todo: use lock file insteaf of dependencies. One file vs many
|
||||||
|
// input: dependencies
|
||||||
|
AddDependencies(dependencies, compilerIO);
|
||||||
|
|
||||||
|
// input: key file
|
||||||
|
AddKeyFile(project, config, compilerIO);
|
||||||
|
|
||||||
|
// output: compiler output
|
||||||
|
compilerIO.Outputs.Add(CompilerUtil.GetCompilationOutput(project.ProjectFile, project.TargetFramework, config, outputPath));
|
||||||
|
|
||||||
|
// input / output: resources without culture
|
||||||
|
AddCultureResources(project, intermediaryOutputPath, compilerIO);
|
||||||
|
|
||||||
|
// input / output: resources with culture
|
||||||
|
AddNonCultureResources(project, outputPath, compilerIO);
|
||||||
|
|
||||||
|
return compilerIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddDependencies(ProjectDependenciesFacade dependencies, CompilerIO compilerIO)
|
||||||
|
{
|
||||||
|
// add dependency sources that need compilation
|
||||||
|
compilerIO.Inputs.AddRange(dependencies.ProjectDependenciesWithSources.Values.SelectMany(p => p.Project.Files.SourceFiles));
|
||||||
|
|
||||||
|
// add compilation binaries
|
||||||
|
compilerIO.Inputs.AddRange(dependencies.Dependencies.SelectMany(d => d.CompilationAssemblies.Select(ca => ca.ResolvedPath)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddKeyFile(ProjectContext project, string config, CompilerIO compilerIO)
|
||||||
|
{
|
||||||
|
var keyFile = CompilerUtil.ResolveCompilationOptions(project, config).KeyFile;
|
||||||
|
|
||||||
|
if (keyFile != null)
|
||||||
|
{
|
||||||
|
compilerIO.Inputs.Add(keyFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddNonCultureResources(ProjectContext project, string intermediaryOutputPath, CompilerIO compilerIO)
|
||||||
|
{
|
||||||
|
foreach (var resourceIO in CompilerUtil.GetNonCultureResources(project.ProjectFile, intermediaryOutputPath))
|
||||||
|
{
|
||||||
|
compilerIO.Inputs.Add(resourceIO.InputFile);
|
||||||
|
|
||||||
|
if (resourceIO.OutputFile != null)
|
||||||
|
{
|
||||||
|
compilerIO.Outputs.Add(resourceIO.OutputFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddCultureResources(ProjectContext project, string outputPath, CompilerIO compilerIO)
|
||||||
|
{
|
||||||
|
foreach (var cultureResourceIO in CompilerUtil.GetCultureResources(project.ProjectFile, outputPath))
|
||||||
|
{
|
||||||
|
compilerIO.Inputs.AddRange(cultureResourceIO.InputFileToMetadata.Keys);
|
||||||
|
|
||||||
|
if (cultureResourceIO.OutputFile != null)
|
||||||
|
{
|
||||||
|
compilerIO.Outputs.Add(cultureResourceIO.OutputFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,13 +10,13 @@ namespace Microsoft.DotNet.Tools.Build
|
||||||
{
|
{
|
||||||
internal class IncrementalPreconditions
|
internal class IncrementalPreconditions
|
||||||
{
|
{
|
||||||
private readonly List<string> _preconditions;
|
private readonly ISet<string> _preconditions;
|
||||||
private readonly bool _isProfile;
|
private readonly bool _isProfile;
|
||||||
|
|
||||||
public IncrementalPreconditions(bool isProfile)
|
public IncrementalPreconditions(bool isProfile)
|
||||||
{
|
{
|
||||||
_isProfile = isProfile;
|
_isProfile = isProfile;
|
||||||
_preconditions = new List<string>();
|
_preconditions = new HashSet<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddPrePostScriptPrecondition(string projectName, string scriptType)
|
public void AddPrePostScriptPrecondition(string projectName, string scriptType)
|
||||||
|
@ -34,6 +34,11 @@ namespace Microsoft.DotNet.Tools.Build
|
||||||
_preconditions.Add($"[PATH Probing] Project {projectName} is loading tool \"{commandName}\" from PATH");
|
_preconditions.Add($"[PATH Probing] Project {projectName} is loading tool \"{commandName}\" from PATH");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddForceUnsafePrecondition()
|
||||||
|
{
|
||||||
|
_preconditions.Add($"[Forced Unsafe] The build was marked as unsafe. Remove the {BuilderCommandApp.ForceUnsafeFlag} flag to enable incremental compilation");
|
||||||
|
}
|
||||||
|
|
||||||
public bool PreconditionsDetected()
|
public bool PreconditionsDetected()
|
||||||
{
|
{
|
||||||
return _preconditions.Any();
|
return _preconditions.Any();
|
||||||
|
@ -70,7 +75,7 @@ namespace Microsoft.DotNet.Tools.Build
|
||||||
{
|
{
|
||||||
if (PreconditionsDetected())
|
if (PreconditionsDetected())
|
||||||
{
|
{
|
||||||
return _isProfile ? PreconditionsMessage().Yellow() : "(The compilation time can be improved. Run \"dotnet build --profile\" for more information)";
|
return _isProfile ? PreconditionsMessage().Yellow() : $"(The compilation time can be improved. Run \"dotnet build {BuilderCommandApp.BuildProfileFlag}\" for more information)";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
using Microsoft.DotNet.ProjectModel;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Compilation;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.Tools.Build
|
||||||
|
{
|
||||||
|
// facade over the dependencies of a project context
|
||||||
|
internal class ProjectDependenciesFacade
|
||||||
|
{
|
||||||
|
// projectName -> ProjectDescription
|
||||||
|
public Dictionary<string, ProjectDescription> ProjectDependenciesWithSources { get; }
|
||||||
|
public List<LibraryExport> Dependencies { get; }
|
||||||
|
|
||||||
|
public ProjectDependenciesFacade(ProjectContext rootProject, string configValue)
|
||||||
|
{
|
||||||
|
Dependencies = GetProjectDependencies(rootProject, configValue);
|
||||||
|
|
||||||
|
ProjectDependenciesWithSources = new Dictionary<string, ProjectDescription>();
|
||||||
|
|
||||||
|
// Build project references
|
||||||
|
foreach (var dependency in Dependencies)
|
||||||
|
{
|
||||||
|
var projectDependency = dependency.Library as ProjectDescription;
|
||||||
|
|
||||||
|
if (projectDependency != null && projectDependency.Project.Files.SourceFiles.Any())
|
||||||
|
{
|
||||||
|
ProjectDependenciesWithSources[projectDependency.Identity.Name] = projectDependency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo make extension of ProjectContext?
|
||||||
|
private static List<LibraryExport> GetProjectDependencies(ProjectContext projectContext, string configuration)
|
||||||
|
{
|
||||||
|
// Create the library exporter
|
||||||
|
var exporter = projectContext.CreateExporter(configuration);
|
||||||
|
|
||||||
|
// Gather exports for the project
|
||||||
|
var dependencies = exporter.GetDependencies().ToList();
|
||||||
|
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"NETStandard.Library": "1.0.0-rc2-23704",
|
"NETStandard.Library": "1.0.0-rc2-23704",
|
||||||
"System.Linq": "4.0.1-rc2-23608",
|
"System.Linq": "4.0.1-rc2-23704",
|
||||||
"System.Reflection.Metadata": "1.1.0",
|
"System.Reflection.Metadata": "1.1.0",
|
||||||
|
|
||||||
"Microsoft.DotNet.ProjectModel": "1.0.0-*",
|
"Microsoft.DotNet.ProjectModel": "1.0.0-*",
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.Dnx.Runtime.Common.CommandLine;
|
using Microsoft.Dnx.Runtime.Common.CommandLine;
|
||||||
|
@ -10,8 +9,8 @@ using Microsoft.DotNet.ProjectModel;
|
||||||
using NuGet.Frameworks;
|
using NuGet.Frameworks;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
//This class is responsible with defining the arguments for the Compile verb.
|
// This class is responsible with defining the arguments for the Compile verb.
|
||||||
//It knows how to interpret them and set default values
|
// It knows how to interpret them and set default values
|
||||||
namespace Microsoft.DotNet.Tools.Compiler
|
namespace Microsoft.DotNet.Tools.Compiler
|
||||||
{
|
{
|
||||||
public delegate bool OnExecute(List<ProjectContext> contexts, CompilerCommandApp compilerCommand);
|
public delegate bool OnExecute(List<ProjectContext> contexts, CompilerCommandApp compilerCommand);
|
||||||
|
@ -20,7 +19,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
{
|
{
|
||||||
private readonly CommandLineApplication _app;
|
private readonly CommandLineApplication _app;
|
||||||
|
|
||||||
//options and arguments for compilation
|
// options and arguments for compilation
|
||||||
private CommandOption _outputOption;
|
private CommandOption _outputOption;
|
||||||
private CommandOption _intermediateOutputOption;
|
private CommandOption _intermediateOutputOption;
|
||||||
private CommandOption _frameworkOption;
|
private CommandOption _frameworkOption;
|
||||||
|
@ -36,7 +35,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
private CommandOption _cppModeOption;
|
private CommandOption _cppModeOption;
|
||||||
private CommandOption _cppCompilerFlagsOption;
|
private CommandOption _cppCompilerFlagsOption;
|
||||||
|
|
||||||
//resolved values for the options and arguments
|
// resolved values for the options and arguments
|
||||||
public string ProjectPathValue { get; set; }
|
public string ProjectPathValue { get; set; }
|
||||||
public string OutputValue { get; set; }
|
public string OutputValue { get; set; }
|
||||||
public string IntermediateValue { get; set; }
|
public string IntermediateValue { get; set; }
|
||||||
|
@ -51,7 +50,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
public string AppDepSdkPathValue { get; set; }
|
public string AppDepSdkPathValue { get; set; }
|
||||||
public string CppCompilerFlagsValue { get; set; }
|
public string CppCompilerFlagsValue { get; set; }
|
||||||
|
|
||||||
//workaround: CommandLineApplication is internal therefore I cannot make _app protected so baseclasses can add their own params
|
// workaround: CommandLineApplication is internal therefore I cannot make _app protected so baseclasses can add their own params
|
||||||
private readonly Dictionary<string, CommandOption> baseClassOptions;
|
private readonly Dictionary<string, CommandOption> baseClassOptions;
|
||||||
|
|
||||||
public CompilerCommandApp(string name, string fullName, string description)
|
public CompilerCommandApp(string name, string fullName, string description)
|
||||||
|
@ -115,7 +114,6 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
IsCppModeValue = _cppModeOption.HasValue();
|
IsCppModeValue = _cppModeOption.HasValue();
|
||||||
CppCompilerFlagsValue = _cppCompilerFlagsOption.Value();
|
CppCompilerFlagsValue = _cppCompilerFlagsOption.Value();
|
||||||
|
|
||||||
|
|
||||||
// Load project contexts for each framework
|
// Load project contexts for each framework
|
||||||
var contexts = _frameworkOption.HasValue() ?
|
var contexts = _frameworkOption.HasValue() ?
|
||||||
_frameworkOption.Values.Select(f => ProjectContext.Create(ProjectPathValue, NuGetFramework.Parse(f))) :
|
_frameworkOption.Values.Select(f => ProjectContext.Create(ProjectPathValue, NuGetFramework.Parse(f))) :
|
||||||
|
@ -129,7 +127,12 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
return _app.Execute(args);
|
return _app.Execute(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
//CommandOptionType is internal. Cannot pass it as argument. Therefore the method name encodes the option type.
|
public CompilerCommandApp ShallowCopy()
|
||||||
|
{
|
||||||
|
return (CompilerCommandApp) MemberwiseClone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandOptionType is internal. Cannot pass it as argument. Therefore the method name encodes the option type.
|
||||||
protected void AddNoValueOption(string optionTemplate, string descriptino){
|
protected void AddNoValueOption(string optionTemplate, string descriptino){
|
||||||
baseClassOptions[optionTemplate] = _app.Option(optionTemplate, descriptino, CommandOptionType.NoValue);
|
baseClassOptions[optionTemplate] = _app.Option(optionTemplate, descriptino, CommandOptionType.NoValue);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,19 @@
|
||||||
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
using Microsoft.DotNet.Cli.Utils;
|
|
||||||
using Microsoft.DotNet.ProjectModel;
|
using Microsoft.DotNet.ProjectModel;
|
||||||
using Microsoft.DotNet.Cli.Compiler.Common;
|
using Microsoft.DotNet.Cli.Compiler.Common;
|
||||||
using Microsoft.DotNet.ProjectModel.Compilation;
|
using Microsoft.DotNet.ProjectModel.Compilation;
|
||||||
using Microsoft.DotNet.Tools.Common;
|
using Microsoft.DotNet.Tools.Common;
|
||||||
|
using NuGet.Frameworks;
|
||||||
|
|
||||||
//This class is responsible with defining the arguments for the Compile verb.
|
// This class is responsible with defining the arguments for the Compile verb.
|
||||||
//It knows how to interpret them and set default values
|
// It knows how to interpret them and set default values
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Tools.Compiler
|
namespace Microsoft.DotNet.Tools.Compiler
|
||||||
{
|
{
|
||||||
|
@ -32,7 +33,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
public readonly string InputFile;
|
public readonly string InputFile;
|
||||||
public readonly string MetadataName;
|
public readonly string MetadataName;
|
||||||
|
|
||||||
//is non-null only when resgen needs to be invoked (inputFile is .resx)
|
// is non-null only when resgen needs to be invoked (inputFile is .resx)
|
||||||
public readonly string OutputFile;
|
public readonly string OutputFile;
|
||||||
|
|
||||||
public NonCultureResgenIO(string inputFile, string outputFile, string metadataName)
|
public NonCultureResgenIO(string inputFile, string outputFile, string metadataName)
|
||||||
|
@ -43,7 +44,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//used in incremental compilation
|
// used in incremental compilation
|
||||||
public static List<NonCultureResgenIO> GetNonCultureResources(Project project, string intermediateOutputPath)
|
public static List<NonCultureResgenIO> GetNonCultureResources(Project project, string intermediateOutputPath)
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
|
@ -70,7 +71,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//used in incremental compilation
|
// used in incremental compilation
|
||||||
public static List<CultureResgenIO> GetCultureResources(Project project, string outputPath)
|
public static List<CultureResgenIO> GetCultureResources(Project project, string outputPath)
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
|
@ -84,7 +85,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
).ToList();
|
).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
//used in incremental compilation
|
// used in incremental compilation
|
||||||
public static IList<string> GetReferencePathsForCultureResgen(List<LibraryExport> dependencies)
|
public static IList<string> GetReferencePathsForCultureResgen(List<LibraryExport> dependencies)
|
||||||
{
|
{
|
||||||
return dependencies.SelectMany(libraryExport => libraryExport.CompilationAssemblies).Select(r => r.ResolvedPath).ToList();
|
return dependencies.SelectMany(libraryExport => libraryExport.CompilationAssemblies).Select(r => r.ResolvedPath).ToList();
|
||||||
|
@ -99,7 +100,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
string resourcePath = resourceFile.Key;
|
string resourcePath = resourceFile.Key;
|
||||||
if (string.IsNullOrEmpty(resourceFile.Value))
|
if (string.IsNullOrEmpty(resourceFile.Value))
|
||||||
{
|
{
|
||||||
// No logical name, so use the file name
|
// No logical name, so use the file name
|
||||||
resourceName = ResourceUtility.GetResourceName(root, resourcePath);
|
resourceName = ResourceUtility.GetResourceName(root, resourcePath);
|
||||||
rootNamespace = project.Name;
|
rootNamespace = project.Name;
|
||||||
}
|
}
|
||||||
|
@ -113,6 +114,44 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used in incremental compilation
|
||||||
|
public static IEnumerable<string> GetCompilationSources(ProjectContext project) => project.ProjectFile.Files.SourceFiles;
|
||||||
|
|
||||||
|
// used in incremental compilation
|
||||||
|
public static string GetCompilationOutput(Project project, NuGetFramework framework, string configuration, string outputPath)
|
||||||
|
{
|
||||||
|
var compilationOptions = project.GetCompilerOptions(framework, configuration);
|
||||||
|
var outputExtension = ".dll";
|
||||||
|
|
||||||
|
if (framework.IsDesktop() && compilationOptions.EmitEntryPoint.GetValueOrDefault())
|
||||||
|
{
|
||||||
|
outputExtension = ".exe";
|
||||||
|
}
|
||||||
|
|
||||||
|
return Path.Combine(outputPath, project.Name + outputExtension);
|
||||||
|
}
|
||||||
|
|
||||||
|
// used in incremental compilation for the key file
|
||||||
|
public static CommonCompilerOptions ResolveCompilationOptions(ProjectContext context, string configuration)
|
||||||
|
{
|
||||||
|
var compilationOptions = context.ProjectFile.GetCompilerOptions(context.TargetFramework, configuration);
|
||||||
|
|
||||||
|
// Path to strong naming key in environment variable overrides path in project.json
|
||||||
|
var environmentKeyFile = Environment.GetEnvironmentVariable(EnvironmentNames.StrongNameKeyFile);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(environmentKeyFile))
|
||||||
|
{
|
||||||
|
compilationOptions.KeyFile = environmentKeyFile;
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrWhiteSpace(compilationOptions.KeyFile))
|
||||||
|
{
|
||||||
|
// Resolve full path to key file
|
||||||
|
compilationOptions.KeyFile =
|
||||||
|
Path.GetFullPath(Path.Combine(context.ProjectFile.ProjectDirectory, compilationOptions.KeyFile));
|
||||||
|
}
|
||||||
|
return compilationOptions;
|
||||||
|
}
|
||||||
|
|
||||||
public static IEnumerable<string> GetCommandsInvokedByCompile(ProjectContext project)
|
public static IEnumerable<string> GetCommandsInvokedByCompile(ProjectContext project)
|
||||||
{
|
{
|
||||||
return new List<string> {ResolveCompilerName(project)};
|
return new List<string> {ResolveCompilerName(project)};
|
||||||
|
|
|
@ -71,7 +71,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
|
|
||||||
var compilationOptions = context.ProjectFile.GetCompilerOptions(context.TargetFramework, args.ConfigValue);
|
var compilationOptions = context.ProjectFile.GetCompilerOptions(context.TargetFramework, args.ConfigValue);
|
||||||
var managedOutput =
|
var managedOutput =
|
||||||
GetProjectOutput(context.ProjectFile, context.TargetFramework, args.ConfigValue, outputPath);
|
CompilerUtil.GetCompilationOutput(context.ProjectFile, context.TargetFramework, args.ConfigValue, outputPath);
|
||||||
|
|
||||||
var nativeArgs = new List<string>();
|
var nativeArgs = new List<string>();
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get compilation options
|
// Get compilation options
|
||||||
var outputName = GetProjectOutput(context.ProjectFile, context.TargetFramework, args.ConfigValue, outputPath);
|
var outputName = CompilerUtil.GetCompilationOutput(context.ProjectFile, context.TargetFramework, args.ConfigValue, outputPath);
|
||||||
|
|
||||||
// Assemble args
|
// Assemble args
|
||||||
var compilerArgs = new List<string>()
|
var compilerArgs = new List<string>()
|
||||||
|
@ -208,20 +208,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
$"--out:{outputName}"
|
$"--out:{outputName}"
|
||||||
};
|
};
|
||||||
|
|
||||||
var compilationOptions = context.ProjectFile.GetCompilerOptions(context.TargetFramework, args.ConfigValue);
|
var compilationOptions = CompilerUtil.ResolveCompilationOptions(context, args.ConfigValue);
|
||||||
|
|
||||||
// Path to strong naming key in environment variable overrides path in project.json
|
|
||||||
var environmentKeyFile = Environment.GetEnvironmentVariable(EnvironmentNames.StrongNameKeyFile);
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(environmentKeyFile))
|
|
||||||
{
|
|
||||||
compilationOptions.KeyFile = environmentKeyFile;
|
|
||||||
}
|
|
||||||
else if (!string.IsNullOrWhiteSpace(compilationOptions.KeyFile))
|
|
||||||
{
|
|
||||||
// Resolve full path to key file
|
|
||||||
compilationOptions.KeyFile = Path.GetFullPath(Path.Combine(context.ProjectFile.ProjectDirectory, compilationOptions.KeyFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
var references = new List<string>();
|
var references = new List<string>();
|
||||||
|
|
||||||
|
@ -239,7 +226,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
{
|
{
|
||||||
if (projectDependency.Project.Files.SourceFiles.Any())
|
if (projectDependency.Project.Files.SourceFiles.Any())
|
||||||
{
|
{
|
||||||
var projectOutputPath = GetProjectOutput(projectDependency.Project, projectDependency.Framework, args.ConfigValue, outputPath);
|
var projectOutputPath = CompilerUtil.GetCompilationOutput(projectDependency.Project, projectDependency.Framework, args.ConfigValue, outputPath);
|
||||||
references.Add(projectOutputPath);
|
references.Add(projectOutputPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -288,7 +275,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Add project source files
|
// Add project source files
|
||||||
var sourceFiles = context.ProjectFile.Files.SourceFiles;
|
var sourceFiles = CompilerUtil.GetCompilationSources(context);
|
||||||
compilerArgs.AddRange(sourceFiles);
|
compilerArgs.AddRange(sourceFiles);
|
||||||
|
|
||||||
var compilerName = CompilerUtil.ResolveCompilerName(context);
|
var compilerName = CompilerUtil.ResolveCompilerName(context);
|
||||||
|
@ -367,6 +354,8 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
return PrintSummary(diagnostics, sw, success);
|
return PrintSummary(diagnostics, sw, success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static void RunScripts(ProjectContext context, string name, Dictionary<string, string> contextVariables)
|
private static void RunScripts(ProjectContext context, string name, Dictionary<string, string> contextVariables)
|
||||||
{
|
{
|
||||||
foreach (var script in context.ProjectFile.Scripts.GetOrEmpty(name))
|
foreach (var script in context.ProjectFile.Scripts.GetOrEmpty(name))
|
||||||
|
@ -378,18 +367,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetProjectOutput(Project project, NuGetFramework framework, string configuration, string outputPath)
|
|
||||||
{
|
|
||||||
var compilationOptions = project.GetCompilerOptions(framework, configuration);
|
|
||||||
var outputExtension = ".dll";
|
|
||||||
|
|
||||||
if (framework.IsDesktop() && compilationOptions.EmitEntryPoint.GetValueOrDefault())
|
|
||||||
{
|
|
||||||
outputExtension = ".exe";
|
|
||||||
}
|
|
||||||
|
|
||||||
return Path.Combine(outputPath, project.Name + outputExtension);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void CopyExport(string outputPath, LibraryExport export)
|
private static void CopyExport(string outputPath, LibraryExport export)
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
@ -47,6 +48,91 @@ namespace Microsoft.DotNet.Tests.EndToEnd
|
||||||
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName());
|
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestDotnetIncrementalBuild()
|
||||||
|
{
|
||||||
|
TestSetup();
|
||||||
|
|
||||||
|
// first build
|
||||||
|
var buildCommand = new BuildCommand(TestProject, output: OutputDirectory);
|
||||||
|
buildCommand.Execute().Should().Pass();
|
||||||
|
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName());
|
||||||
|
|
||||||
|
var latestWriteTimeFirstBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
|
||||||
|
|
||||||
|
// second build; should get skipped (incremental because no inputs changed)
|
||||||
|
buildCommand.Execute().Should().Pass();
|
||||||
|
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName());
|
||||||
|
|
||||||
|
var latestWriteTimeSecondBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
|
||||||
|
Assert.Equal(latestWriteTimeFirstBuild, latestWriteTimeSecondBuild);
|
||||||
|
|
||||||
|
TouchSourceFileInDirectory(TestDirectory);
|
||||||
|
|
||||||
|
// third build; should get compiled because the source file got touched
|
||||||
|
buildCommand.Execute().Should().Pass();
|
||||||
|
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName());
|
||||||
|
|
||||||
|
var latestWriteTimeThirdBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
|
||||||
|
Assert.NotEqual(latestWriteTimeSecondBuild, latestWriteTimeThirdBuild);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestDotnetForceIncrementalUnsafe()
|
||||||
|
{
|
||||||
|
TestSetup();
|
||||||
|
|
||||||
|
// first build
|
||||||
|
var buildCommand = new BuildCommand(TestProject, output: OutputDirectory);
|
||||||
|
buildCommand.Execute().Should().Pass();
|
||||||
|
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName());
|
||||||
|
|
||||||
|
var latestWriteTimeFirstBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
|
||||||
|
|
||||||
|
// second build; will get recompiled due to force unsafe flag
|
||||||
|
buildCommand = new BuildCommand(TestProject, output: OutputDirectory, forceIncrementalUnsafe:true);
|
||||||
|
buildCommand.Execute().Should().Pass();
|
||||||
|
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName());
|
||||||
|
|
||||||
|
var latestWriteTimeSecondBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
|
||||||
|
Assert.NotEqual(latestWriteTimeFirstBuild, latestWriteTimeSecondBuild);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestDotnetIncrementalBuildDeleteOutputFile()
|
||||||
|
{
|
||||||
|
TestSetup();
|
||||||
|
|
||||||
|
// first build
|
||||||
|
var buildCommand = new BuildCommand(TestProject, output: OutputDirectory);
|
||||||
|
buildCommand.Execute().Should().Pass();
|
||||||
|
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName());
|
||||||
|
|
||||||
|
var latestWriteTimeFirstBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
|
||||||
|
|
||||||
|
Reporter.Verbose.WriteLine($"Files in {OutputDirectory}");
|
||||||
|
foreach (var file in Directory.EnumerateFiles(OutputDirectory))
|
||||||
|
{
|
||||||
|
Reporter.Verbose.Write($"\t {file}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete output files
|
||||||
|
foreach (var outputFile in Directory.EnumerateFiles(OutputDirectory).Where(f => Path.GetFileName(f).StartsWith(s_testdirName, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
Reporter.Verbose.WriteLine($"Delete {outputFile}");
|
||||||
|
|
||||||
|
File.Delete(outputFile);
|
||||||
|
Assert.False(File.Exists(outputFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
// second build; should get rebuilt since we deleted output items
|
||||||
|
buildCommand.Execute().Should().Pass();
|
||||||
|
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName());
|
||||||
|
|
||||||
|
var latestWriteTimeSecondBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
|
||||||
|
Assert.NotEqual(latestWriteTimeFirstBuild, latestWriteTimeSecondBuild);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[ActiveIssue(712, PlatformID.Windows | PlatformID.OSX | PlatformID.Linux)]
|
[ActiveIssue(712, PlatformID.Windows | PlatformID.OSX | PlatformID.Linux)]
|
||||||
public void TestDotnetBuildNativeRyuJit()
|
public void TestDotnetBuildNativeRyuJit()
|
||||||
|
@ -82,6 +168,32 @@ namespace Microsoft.DotNet.Tests.EndToEnd
|
||||||
TestOutputExecutable(nativeOut, buildCommand.GetOutputExecutableName());
|
TestOutputExecutable(nativeOut, buildCommand.GetOutputExecutableName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestDotnetCompileNativeCppIncremental()
|
||||||
|
{
|
||||||
|
if (IsCentOS())
|
||||||
|
{
|
||||||
|
Console.WriteLine("Skipping native compilation tests on CentOS - https://github.com/dotnet/cli/issues/453");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nativeOut = Path.Combine(OutputDirectory, "native");
|
||||||
|
|
||||||
|
// first build
|
||||||
|
var buildCommand = new BuildCommand(TestProject, output: OutputDirectory, native: true, nativeCppMode: true);
|
||||||
|
buildCommand.Execute().Should().Pass();
|
||||||
|
TestOutputExecutable(nativeOut, buildCommand.GetOutputExecutableName());
|
||||||
|
|
||||||
|
var latestWriteTimeFirstBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
|
||||||
|
|
||||||
|
// second build; should be skipped because nothing changed
|
||||||
|
buildCommand.Execute().Should().Pass();
|
||||||
|
TestOutputExecutable(nativeOut, buildCommand.GetOutputExecutableName());
|
||||||
|
|
||||||
|
var latestWriteTimeSecondBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
|
||||||
|
Assert.Equal(latestWriteTimeFirstBuild, latestWriteTimeSecondBuild);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TestDotnetRun()
|
public void TestDotnetRun()
|
||||||
{
|
{
|
||||||
|
@ -160,5 +272,16 @@ namespace Microsoft.DotNet.Tests.EndToEnd
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static DateTime GetLastWriteTimeOfDirectoryFiles(string outputDirectory)
|
||||||
|
{
|
||||||
|
return Directory.EnumerateFiles(outputDirectory).Max(f => File.GetLastWriteTime(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TouchSourceFileInDirectory(string directory)
|
||||||
|
{
|
||||||
|
var csFile = Directory.EnumerateFiles(directory).First(f => Path.GetExtension(f).Equals(".cs"));
|
||||||
|
File.SetLastWriteTimeUtc(csFile, DateTime.UtcNow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -23,6 +23,8 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
private string _appDepSDKPath;
|
private string _appDepSDKPath;
|
||||||
private bool _nativeCppMode;
|
private bool _nativeCppMode;
|
||||||
private string _cppCompilerFlags;
|
private string _cppCompilerFlags;
|
||||||
|
private bool _buildProfile;
|
||||||
|
private bool _forceIncrementalUnsafe;
|
||||||
|
|
||||||
private string OutputOption
|
private string OutputOption
|
||||||
{
|
{
|
||||||
|
@ -134,6 +136,26 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string BuildProfile
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _buildProfile ?
|
||||||
|
"--build-profile" :
|
||||||
|
"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ForceIncrementalUnsafe
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _forceIncrementalUnsafe ?
|
||||||
|
"--force-incremental-unsafe" :
|
||||||
|
"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public BuildCommand(
|
public BuildCommand(
|
||||||
string projectPath,
|
string projectPath,
|
||||||
string output="",
|
string output="",
|
||||||
|
@ -146,7 +168,9 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
string ilcPath="",
|
string ilcPath="",
|
||||||
string appDepSDKPath="",
|
string appDepSDKPath="",
|
||||||
bool nativeCppMode=false,
|
bool nativeCppMode=false,
|
||||||
string cppCompilerFlags=""
|
string cppCompilerFlags="",
|
||||||
|
bool buildProfile=true,
|
||||||
|
bool forceIncrementalUnsafe=false
|
||||||
)
|
)
|
||||||
: base("dotnet")
|
: base("dotnet")
|
||||||
{
|
{
|
||||||
|
@ -165,12 +189,13 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
_appDepSDKPath = appDepSDKPath;
|
_appDepSDKPath = appDepSDKPath;
|
||||||
_nativeCppMode = nativeCppMode;
|
_nativeCppMode = nativeCppMode;
|
||||||
_cppCompilerFlags = cppCompilerFlags;
|
_cppCompilerFlags = cppCompilerFlags;
|
||||||
|
_buildProfile = buildProfile;
|
||||||
|
_forceIncrementalUnsafe = forceIncrementalUnsafe;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override CommandResult Execute(string args = "")
|
public override CommandResult Execute(string args = "")
|
||||||
{
|
{
|
||||||
args = $"build {BuildArgs()} {args}";
|
args = $"--verbose build {BuildArgs()} {args}";
|
||||||
return base.Execute(args);
|
return base.Execute(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,7 +214,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
|
|
||||||
private string BuildArgs()
|
private string BuildArgs()
|
||||||
{
|
{
|
||||||
return $"{_projectPath} {OutputOption} {TempOutputOption} {ConfigurationOption} {NoHostOption} {NativeOption} {ArchitectureOption} {IlcArgsOption} {IlcPathOption} {AppDepSDKPathOption} {NativeCppModeOption} {CppCompilerFlagsOption}";
|
return $"{BuildProfile} {ForceIncrementalUnsafe} {_projectPath} {OutputOption} {TempOutputOption} {ConfigurationOption} {NoHostOption} {NativeOption} {ArchitectureOption} {IlcArgsOption} {IlcPathOption} {AppDepSDKPathOption} {NativeCppModeOption} {CppCompilerFlagsOption}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue