Merge pull request #2574 from dotnet/pakrym/graph
Add multiple input project support
This commit is contained in:
commit
88ffa548ba
33 changed files with 1231 additions and 1160 deletions
|
@ -18,7 +18,7 @@ using Newtonsoft.Json.Linq;
|
|||
using Newtonsoft.Json;
|
||||
using System.Reflection.PortableExecutable;
|
||||
|
||||
namespace Microsoft.Dotnet.Cli.Compiler.Common
|
||||
namespace Microsoft.DotNet.Cli.Compiler.Common
|
||||
{
|
||||
public class Executable
|
||||
{
|
||||
|
|
|
@ -14,8 +14,6 @@ namespace Microsoft.DotNet.ProjectModel.Compilation
|
|||
|
||||
private IList<LibraryAsset> _compilationAssemblies;
|
||||
|
||||
private IList<LibraryAsset> _compilationAssets;
|
||||
|
||||
private IList<LibraryAsset> _sourceReferences;
|
||||
|
||||
private IList<LibraryAssetGroup> _nativeLibraryGroups;
|
||||
|
@ -34,8 +32,6 @@ namespace Microsoft.DotNet.ProjectModel.Compilation
|
|||
|
||||
public IEnumerable<LibraryAsset> CompilationAssemblies => _compilationAssemblies;
|
||||
|
||||
public IEnumerable<LibraryAsset> CompilationAssets => _compilationAssets;
|
||||
|
||||
public IEnumerable<LibraryAsset> SourceReferences => _sourceReferences;
|
||||
|
||||
public IEnumerable<LibraryAssetGroup> NativeLibraryGroups => _nativeLibraryGroups;
|
||||
|
|
|
@ -16,6 +16,8 @@ namespace Microsoft.DotNet.ProjectModel
|
|||
{
|
||||
private string[] _runtimeFallbacks;
|
||||
|
||||
public ProjectContextIdentity Identity { get; }
|
||||
|
||||
public GlobalSettings GlobalSettings { get; }
|
||||
|
||||
public ProjectDescription RootProject { get; }
|
||||
|
@ -51,6 +53,7 @@ namespace Microsoft.DotNet.ProjectModel
|
|||
LibraryManager libraryManager,
|
||||
LockFile lockfile)
|
||||
{
|
||||
Identity = new ProjectContextIdentity(rootProject?.Path, targetFramework);
|
||||
GlobalSettings = globalSettings;
|
||||
RootProject = rootProject;
|
||||
PlatformLibrary = platformLibrary;
|
||||
|
|
|
@ -489,6 +489,11 @@ namespace Microsoft.DotNet.ProjectModel
|
|||
if (Project == null && ProjectDirectory != null)
|
||||
{
|
||||
Project = ProjectResolver(ProjectDirectory);
|
||||
if (Project == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not resolve project at: {ProjectDirectory}. " +
|
||||
$"This could happen when project.lock.json was moved after restore.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
39
src/Microsoft.DotNet.ProjectModel/ProjectContextIdentity.cs
Normal file
39
src/Microsoft.DotNet.ProjectModel/ProjectContextIdentity.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
// 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 Microsoft.Extensions.Internal;
|
||||
using NuGet.Frameworks;
|
||||
|
||||
namespace Microsoft.DotNet.ProjectModel
|
||||
{
|
||||
public struct ProjectContextIdentity
|
||||
{
|
||||
public ProjectContextIdentity(string path, NuGetFramework targetFramework)
|
||||
{
|
||||
Path = path;
|
||||
TargetFramework = targetFramework;
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
public NuGetFramework TargetFramework { get; }
|
||||
|
||||
public bool Equals(ProjectContextIdentity other)
|
||||
{
|
||||
return string.Equals(Path, other.Path) && Equals(TargetFramework, other.TargetFramework);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
return obj is ProjectContextIdentity && Equals((ProjectContextIdentity) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var combiner = HashCodeCombiner.Start();
|
||||
combiner.Add(Path);
|
||||
combiner.Add(TargetFramework);
|
||||
return combiner.CombinedHash;
|
||||
}
|
||||
}
|
||||
}
|
65
src/dotnet/ProjectGlobbingResolver.cs
Normal file
65
src/dotnet/ProjectGlobbingResolver.cs
Normal file
|
@ -0,0 +1,65 @@
|
|||
// 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.Extensions.FileSystemGlobbing;
|
||||
using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Compiler
|
||||
{
|
||||
internal class ProjectGlobbingResolver
|
||||
{
|
||||
internal IEnumerable<string> Resolve(IEnumerable<string> values)
|
||||
{
|
||||
var currentDirectory = Directory.GetCurrentDirectory();
|
||||
if (!values.Any())
|
||||
{
|
||||
var fileName = Path.Combine(currentDirectory, Project.FileName);
|
||||
if (!File.Exists(fileName))
|
||||
{
|
||||
throw new InvalidOperationException($"Couldn't find '{Project.FileName}' in current directory");
|
||||
}
|
||||
yield return fileName;
|
||||
yield break;
|
||||
}
|
||||
foreach (var value in values)
|
||||
{
|
||||
var fileName = Path.Combine(currentDirectory, value);
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
yield return value;
|
||||
continue;
|
||||
}
|
||||
|
||||
fileName = Path.Combine(currentDirectory, value, Project.FileName);
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
yield return fileName;
|
||||
continue;
|
||||
}
|
||||
|
||||
var matcher = new Matcher();
|
||||
matcher.AddInclude(value);
|
||||
var result = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(currentDirectory)));
|
||||
if (result.Files.Any())
|
||||
{
|
||||
foreach (var filePatternMatch in result.Files)
|
||||
{
|
||||
yield return filePatternMatch.Path;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Could not resolve project path from '{value}':" +
|
||||
"1. It's not project file" +
|
||||
"2. It's not directory containing project.json file" +
|
||||
"3. Globbing returned no mathces");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
134
src/dotnet/commands/dotnet-build/BuildCommandApp.cs
Normal file
134
src/dotnet/commands/dotnet-build/BuildCommandApp.cs
Normal file
|
@ -0,0 +1,134 @@
|
|||
// 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.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Dnx.Runtime.Common.CommandLine;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
using Microsoft.Extensions.PlatformAbstractions;
|
||||
using NuGet.Frameworks;
|
||||
|
||||
// This class is responsible with defining the arguments for the Compile verb.
|
||||
// It knows how to interpret them and set default values
|
||||
namespace Microsoft.DotNet.Tools.Compiler
|
||||
{
|
||||
public delegate bool OnExecute(IEnumerable<string> files, IEnumerable<NuGetFramework> frameworks, BuildCommandApp buildCommand);
|
||||
|
||||
public class BuildCommandApp
|
||||
{
|
||||
public static readonly string NoIncrementalFlag = "--no-incremental";
|
||||
public static readonly string BuildProfileFlag = "--build-profile";
|
||||
|
||||
private readonly CommandLineApplication _app;
|
||||
|
||||
// options and arguments for compilation
|
||||
private CommandOption _outputOption;
|
||||
private CommandOption _buildBasePath;
|
||||
private CommandOption _frameworkOption;
|
||||
private CommandOption _runtimeOption;
|
||||
private CommandOption _versionSuffixOption;
|
||||
private CommandOption _configurationOption;
|
||||
private CommandArgument _projectArgument;
|
||||
|
||||
private CommandOption _shouldPrintIncrementalPreconditionsArgument;
|
||||
private CommandOption _shouldNotUseIncrementalityArgument;
|
||||
private CommandOption _shouldSkipDependenciesArgument;
|
||||
|
||||
|
||||
public string BuildBasePathValue { get; set; }
|
||||
public string RuntimeValue { get; set; }
|
||||
public string OutputValue { get; set; }
|
||||
public string VersionSuffixValue { get; set; }
|
||||
public string ConfigValue { get; set; }
|
||||
public bool IsNativeValue { get; set; }
|
||||
public bool ShouldPrintIncrementalPreconditions { get; set; }
|
||||
public bool ShouldNotUseIncrementality { get; set; }
|
||||
public bool ShouldSkipDependencies { get; set; }
|
||||
|
||||
// workaround: CommandLineApplication is internal therefore I cannot make _app protected so baseclasses can add their own params
|
||||
private readonly Dictionary<string, CommandOption> baseClassOptions;
|
||||
|
||||
public BuildCommandApp(string name, string fullName, string description)
|
||||
{
|
||||
_app = new CommandLineApplication
|
||||
{
|
||||
Name = name,
|
||||
FullName = fullName,
|
||||
Description = description
|
||||
};
|
||||
|
||||
baseClassOptions = new Dictionary<string, CommandOption>();
|
||||
|
||||
AddCompileParameters();
|
||||
}
|
||||
|
||||
private void AddCompileParameters()
|
||||
{
|
||||
_app.HelpOption("-h|--help");
|
||||
|
||||
_outputOption = _app.Option("-o|--output <OUTPUT_DIR>", "Directory in which to place outputs", CommandOptionType.SingleValue);
|
||||
_buildBasePath = _app.Option("-b|--build-base-path <OUTPUT_DIR>", "Directory in which to place temporary outputs", CommandOptionType.SingleValue);
|
||||
_frameworkOption = _app.Option("-f|--framework <FRAMEWORK>", "Compile a specific framework", CommandOptionType.SingleValue);
|
||||
_runtimeOption = _app.Option("-r|--runtime <RUNTIME_IDENTIFIER>", "Produce runtime-specific assets for the specified runtime", CommandOptionType.SingleValue);
|
||||
_configurationOption = _app.Option("-c|--configuration <CONFIGURATION>", "Configuration under which to build", CommandOptionType.SingleValue);
|
||||
_versionSuffixOption = _app.Option("--version-suffix <VERSION_SUFFIX>", "Defines what `*` should be replaced with in version field in project.json", CommandOptionType.SingleValue);
|
||||
_projectArgument = _app.Argument("<PROJECT>", "The project to compile, defaults to the current directory. " +
|
||||
"Can be one or multiple paths to project.json, project directory " +
|
||||
"or globbing patter that matches project.json files", multipleValues: true);
|
||||
|
||||
_shouldPrintIncrementalPreconditionsArgument = _app.Option(BuildProfileFlag, "Set this flag to print the incremental safety checks that prevent incremental compilation", CommandOptionType.NoValue);
|
||||
_shouldNotUseIncrementalityArgument = _app.Option(NoIncrementalFlag, "Set this flag to turn off incremental build", CommandOptionType.NoValue);
|
||||
_shouldSkipDependenciesArgument = _app.Option("--no-dependencies", "Set this flag to ignore project to project references and only build the root project", CommandOptionType.NoValue);
|
||||
}
|
||||
|
||||
public int Execute(OnExecute execute, string[] args)
|
||||
{
|
||||
_app.OnExecute(() =>
|
||||
{
|
||||
if (_outputOption.HasValue() && !_frameworkOption.HasValue())
|
||||
{
|
||||
Reporter.Error.WriteLine("When the '--output' option is provided, the '--framework' option must also be provided.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
OutputValue = _outputOption.Value();
|
||||
BuildBasePathValue = _buildBasePath.Value();
|
||||
ConfigValue = _configurationOption.Value() ?? Constants.DefaultConfiguration;
|
||||
RuntimeValue = _runtimeOption.Value();
|
||||
VersionSuffixValue = _versionSuffixOption.Value();
|
||||
ShouldPrintIncrementalPreconditions = _shouldPrintIncrementalPreconditionsArgument.HasValue();
|
||||
ShouldNotUseIncrementality = _shouldNotUseIncrementalityArgument.HasValue();
|
||||
ShouldSkipDependencies = _shouldSkipDependenciesArgument.HasValue();
|
||||
|
||||
var files = new ProjectGlobbingResolver().Resolve(_projectArgument.Values);
|
||||
IEnumerable<NuGetFramework> frameworks = null;
|
||||
if (_frameworkOption.HasValue())
|
||||
{
|
||||
frameworks = new [] { NuGetFramework.Parse(_frameworkOption.Value()) };
|
||||
}
|
||||
var success = execute(files, frameworks, this);
|
||||
|
||||
return success ? 0 : 1;
|
||||
});
|
||||
|
||||
return _app.Execute(args);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetRuntimes()
|
||||
{
|
||||
var rids = new List<string>();
|
||||
if (string.IsNullOrEmpty(RuntimeValue))
|
||||
{
|
||||
return PlatformServices.Default.Runtime.GetAllCandidateRuntimeIdentifiers();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new[] { RuntimeValue };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// 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 Microsoft.DotNet.Tools.Compiler;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Build
|
||||
{
|
||||
internal class BuilderCommandApp : CompilerCommandApp
|
||||
{
|
||||
public const string BuildProfileFlag = "--build-profile";
|
||||
public const string NoIncrementalFlag = "--no-incremental";
|
||||
public const string NoDependenciesFlag = "--no-dependencies";
|
||||
|
||||
public bool ShouldPrintIncrementalPreconditions => OptionHasValue(BuildProfileFlag);
|
||||
public bool ShouldNotUseIncrementality => OptionHasValue(NoIncrementalFlag);
|
||||
public bool ShouldSkipDependencies => OptionHasValue(NoDependenciesFlag);
|
||||
|
||||
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(NoIncrementalFlag, "Set this flag to turn off incremental build");
|
||||
AddNoValueOption(NoDependenciesFlag, "Set this flag to ignore project to project references and only build the root project");
|
||||
}
|
||||
}
|
||||
}
|
10
src/dotnet/commands/dotnet-build/CompilationResult.cs
Normal file
10
src/dotnet/commands/dotnet-build/CompilationResult.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Build
|
||||
{
|
||||
internal enum CompilationResult
|
||||
{
|
||||
IncrementalSkip, Success, Failure
|
||||
}
|
||||
}
|
|
@ -1,684 +0,0 @@
|
|||
// 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.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;
|
||||
|
||||
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
|
||||
// Collects icnremental safety checks and transitively compiles a project context
|
||||
internal class CompileContext
|
||||
{
|
||||
public static readonly string[] KnownCompilers = { "csc", "vbc", "fsc" };
|
||||
|
||||
private readonly ProjectContext _rootProject;
|
||||
private readonly ProjectDependenciesFacade _rootProjectDependencies;
|
||||
private readonly BuilderCommandApp _args;
|
||||
private readonly IncrementalPreconditions _preconditions;
|
||||
|
||||
public bool IsSafeForIncrementalCompilation => !_preconditions.PreconditionsDetected();
|
||||
|
||||
public CompileContext(ProjectContext rootProject, BuilderCommandApp args)
|
||||
{
|
||||
_rootProject = rootProject;
|
||||
|
||||
// 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 dependencies
|
||||
_rootProjectDependencies = new ProjectDependenciesFacade(_rootProject, _args.ConfigValue);
|
||||
|
||||
// gather preconditions
|
||||
_preconditions = GatherIncrementalPreconditions();
|
||||
}
|
||||
|
||||
public bool Compile(bool incremental)
|
||||
{
|
||||
CreateOutputDirectories();
|
||||
|
||||
return CompileDependencies(incremental) && CompileRootProject(incremental);
|
||||
}
|
||||
|
||||
private bool CompileRootProject(bool incremental)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (incremental && !NeedsRebuilding(_rootProject, _rootProjectDependencies))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var success = InvokeCompileOnRootProject();
|
||||
|
||||
PrintSummary(success);
|
||||
|
||||
return success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
StampProjectWithSDKVersion(_rootProject);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CompileDependencies(bool incremental)
|
||||
{
|
||||
if (_args.ShouldSkipDependencies)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var dependency in Sort(_rootProjectDependencies))
|
||||
{
|
||||
var dependencyProjectContext = ProjectContext.Create(dependency.Path, dependency.Framework, new[] { _rootProject.RuntimeIdentifier });
|
||||
|
||||
try
|
||||
{
|
||||
if (incremental && !NeedsRebuilding(dependencyProjectContext, new ProjectDependenciesFacade(dependencyProjectContext, _args.ConfigValue)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!InvokeCompileOnDependency(dependency))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
StampProjectWithSDKVersion(dependencyProjectContext);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool NeedsRebuilding(ProjectContext project, ProjectDependenciesFacade dependencies)
|
||||
{
|
||||
if (CLIChangedSinceLastCompilation(project))
|
||||
{
|
||||
Reporter.Output.WriteLine($"Project {project.GetDisplayName()} will be compiled because the version or bitness of the CLI changed since the last build");
|
||||
return true;
|
||||
}
|
||||
|
||||
var compilerIO = GetCompileIO(project, dependencies);
|
||||
|
||||
// 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");
|
||||
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 minDateUtc = File.GetLastWriteTimeUtc(minOutputPath);
|
||||
|
||||
foreach (var outputPath in compilerIO.Outputs)
|
||||
{
|
||||
if (File.GetLastWriteTimeUtc(outputPath) >= minDateUtc)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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($"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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private void StampProjectWithSDKVersion(ProjectContext project)
|
||||
{
|
||||
if (File.Exists(DotnetFiles.VersionFile))
|
||||
{
|
||||
var projectVersionFile = project.GetSDKVersionFile(_args.ConfigValue, _args.BuildBasePathValue, _args.OutputValue);
|
||||
var parentDirectory = Path.GetDirectoryName(projectVersionFile);
|
||||
|
||||
if (!Directory.Exists(parentDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(parentDirectory);
|
||||
}
|
||||
|
||||
string content = ComputeCurrentVersionFileData();
|
||||
|
||||
File.WriteAllText(projectVersionFile, content);
|
||||
}
|
||||
else
|
||||
{
|
||||
Reporter.Verbose.WriteLine($"Project {project.GetDisplayName()} was not stamped with a CLI version because the version file does not exist: {DotnetFiles.VersionFile}");
|
||||
}
|
||||
}
|
||||
|
||||
private static string ComputeCurrentVersionFileData()
|
||||
{
|
||||
var content = File.ReadAllText(DotnetFiles.VersionFile);
|
||||
content += Environment.NewLine;
|
||||
content += PlatformServices.Default.Runtime.GetRuntimeIdentifier();
|
||||
return content;
|
||||
}
|
||||
|
||||
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
|
||||
if (success)
|
||||
{
|
||||
Reporter.Output.Write(" " + _preconditions.LogMessage());
|
||||
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 IncrementalPreconditions GatherIncrementalPreconditions()
|
||||
{
|
||||
var preconditions = new IncrementalPreconditions(_args.ShouldPrintIncrementalPreconditions);
|
||||
|
||||
if (_args.ShouldNotUseIncrementality)
|
||||
{
|
||||
preconditions.AddForceUnsafePrecondition();
|
||||
}
|
||||
|
||||
var projectsToCheck = GetProjectsToCheck();
|
||||
|
||||
foreach (var project in projectsToCheck)
|
||||
{
|
||||
CollectScriptPreconditions(project, preconditions);
|
||||
CollectCompilerNamePreconditions(project, preconditions);
|
||||
CollectCheckPathProbingPreconditions(project, preconditions);
|
||||
}
|
||||
|
||||
return preconditions;
|
||||
}
|
||||
|
||||
// check the entire project tree that needs to be compiled, duplicated for each framework
|
||||
private List<ProjectContext> GetProjectsToCheck()
|
||||
{
|
||||
if (_args.ShouldSkipDependencies)
|
||||
{
|
||||
return new List<ProjectContext>(1) { _rootProject };
|
||||
}
|
||||
|
||||
// include initial root project
|
||||
var contextsToCheck = new List<ProjectContext>(1 + _rootProjectDependencies.ProjectDependenciesWithSources.Count) { _rootProject };
|
||||
|
||||
// convert ProjectDescription to ProjectContext
|
||||
var dependencyContexts = _rootProjectDependencies.ProjectDependenciesWithSources.Select
|
||||
(keyValuePair => ProjectContext.Create(keyValuePair.Value.Path, keyValuePair.Value.Framework));
|
||||
|
||||
contextsToCheck.AddRange(dependencyContexts);
|
||||
|
||||
|
||||
return contextsToCheck;
|
||||
}
|
||||
|
||||
private void CollectCheckPathProbingPreconditions(ProjectContext project, IncrementalPreconditions preconditions)
|
||||
{
|
||||
var pathCommands = CompilerUtil.GetCommandsInvokedByCompile(project)
|
||||
.Select(commandName => Command.CreateDotNet(commandName, Enumerable.Empty<string>(), project.TargetFramework))
|
||||
.Where(c => c.ResolutionStrategy.Equals(CommandResolutionStrategy.Path));
|
||||
|
||||
foreach (var pathCommand in pathCommands)
|
||||
{
|
||||
preconditions.AddPathProbingPrecondition(project.ProjectName(), pathCommand.CommandName);
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectCompilerNamePreconditions(ProjectContext project, IncrementalPreconditions preconditions)
|
||||
{
|
||||
if (project.ProjectFile != null)
|
||||
{
|
||||
var projectCompiler = project.ProjectFile.CompilerName;
|
||||
|
||||
if (!KnownCompilers.Any(knownCompiler => knownCompiler.Equals(projectCompiler, StringComparison.Ordinal)))
|
||||
{
|
||||
preconditions.AddUnknownCompilerPrecondition(project.ProjectName(), projectCompiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectScriptPreconditions(ProjectContext project, IncrementalPreconditions preconditions)
|
||||
{
|
||||
if (project.ProjectFile != null)
|
||||
{
|
||||
var preCompileScripts = project.ProjectFile.Scripts.GetOrEmpty(ScriptNames.PreCompile);
|
||||
var postCompileScripts = project.ProjectFile.Scripts.GetOrEmpty(ScriptNames.PostCompile);
|
||||
|
||||
if (preCompileScripts.Any())
|
||||
{
|
||||
preconditions.AddPrePostScriptPrecondition(project.ProjectName(), ScriptNames.PreCompile);
|
||||
}
|
||||
|
||||
if (postCompileScripts.Any())
|
||||
{
|
||||
preconditions.AddPrePostScriptPrecondition(project.ProjectName(), ScriptNames.PostCompile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool InvokeCompileOnDependency(ProjectDescription projectDependency)
|
||||
{
|
||||
var args = new List<string>();
|
||||
|
||||
args.Add("--framework");
|
||||
args.Add($"{projectDependency.Framework}");
|
||||
|
||||
args.Add("--configuration");
|
||||
args.Add(_args.ConfigValue);
|
||||
args.Add(projectDependency.Project.ProjectDirectory);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_args.RuntimeValue))
|
||||
{
|
||||
args.Add("--runtime");
|
||||
args.Add(_args.RuntimeValue);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_args.VersionSuffixValue))
|
||||
{
|
||||
args.Add("--version-suffix");
|
||||
args.Add(_args.VersionSuffixValue);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_args.BuildBasePathValue))
|
||||
{
|
||||
args.Add("--build-base-path");
|
||||
args.Add(_args.BuildBasePathValue);
|
||||
}
|
||||
|
||||
var compileResult = CompileCommand.Run(args.ToArray());
|
||||
|
||||
return compileResult == 0;
|
||||
}
|
||||
|
||||
private bool InvokeCompileOnRootProject()
|
||||
{
|
||||
// todo: add methods to CompilerCommandApp to generate the arg string?
|
||||
var args = new List<string>();
|
||||
args.Add("--framework");
|
||||
args.Add(_rootProject.TargetFramework.ToString());
|
||||
args.Add("--configuration");
|
||||
args.Add(_args.ConfigValue);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_args.RuntimeValue))
|
||||
{
|
||||
args.Add("--runtime");
|
||||
args.Add(_args.RuntimeValue);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_args.OutputValue))
|
||||
{
|
||||
args.Add("--output");
|
||||
args.Add(_args.OutputValue);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_args.VersionSuffixValue))
|
||||
{
|
||||
args.Add("--version-suffix");
|
||||
args.Add(_args.VersionSuffixValue);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_args.BuildBasePathValue))
|
||||
{
|
||||
args.Add("--build-base-path");
|
||||
args.Add(_args.BuildBasePathValue);
|
||||
}
|
||||
|
||||
//native args
|
||||
if (_args.IsNativeValue)
|
||||
{
|
||||
args.Add("--native");
|
||||
}
|
||||
|
||||
if (_args.IsCppModeValue)
|
||||
{
|
||||
args.Add("--cpp");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_args.CppCompilerFlagsValue))
|
||||
{
|
||||
args.Add("--cppcompilerflags");
|
||||
args.Add(_args.CppCompilerFlagsValue);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_args.ArchValue))
|
||||
{
|
||||
args.Add("--arch");
|
||||
args.Add(_args.ArchValue);
|
||||
}
|
||||
|
||||
foreach (var ilcArg in _args.IlcArgsValue)
|
||||
{
|
||||
args.Add("--ilcarg");
|
||||
args.Add(ilcArg);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_args.IlcPathValue))
|
||||
{
|
||||
args.Add("--ilcpath");
|
||||
args.Add(_args.IlcPathValue);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_args.IlcSdkPathValue))
|
||||
{
|
||||
args.Add("--ilcsdkpath");
|
||||
args.Add(_args.IlcSdkPathValue);
|
||||
}
|
||||
|
||||
args.Add(_rootProject.ProjectDirectory);
|
||||
|
||||
var compileResult = CompileCommand.Run(args.ToArray());
|
||||
|
||||
var succeeded = compileResult == 0;
|
||||
|
||||
if (succeeded)
|
||||
{
|
||||
MakeRunnable();
|
||||
}
|
||||
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
private void CopyCompilationOutput(OutputPaths outputPaths)
|
||||
{
|
||||
var dest = outputPaths.RuntimeOutputPath;
|
||||
var source = outputPaths.CompilationOutputPath;
|
||||
|
||||
// No need to copy if dest and source are the same
|
||||
if(string.Equals(dest, source, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var file in outputPaths.CompilationFiles.All())
|
||||
{
|
||||
var destFileName = file.Replace(source, dest);
|
||||
var directoryName = Path.GetDirectoryName(destFileName);
|
||||
if (!Directory.Exists(directoryName))
|
||||
{
|
||||
Directory.CreateDirectory(directoryName);
|
||||
}
|
||||
File.Copy(file, destFileName, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void MakeRunnable()
|
||||
{
|
||||
var runtimeContext = _rootProject.ProjectFile.HasRuntimeOutput(_args.ConfigValue) ?
|
||||
_rootProject.CreateRuntimeContext(_args.GetRuntimes()) :
|
||||
_rootProject;
|
||||
|
||||
var outputPaths = runtimeContext.GetOutputPaths(_args.ConfigValue, _args.BuildBasePathValue, _args.OutputValue);
|
||||
var libraryExporter = runtimeContext.CreateExporter(_args.ConfigValue, _args.BuildBasePathValue);
|
||||
|
||||
CopyCompilationOutput(outputPaths);
|
||||
|
||||
var executable = new Executable(runtimeContext, outputPaths, libraryExporter, _args.ConfigValue);
|
||||
executable.MakeCompilationOutputRunnable();
|
||||
}
|
||||
|
||||
private static IEnumerable<ProjectDescription> Sort(ProjectDependenciesFacade dependencies)
|
||||
{
|
||||
var outputs = new List<ProjectDescription>();
|
||||
|
||||
foreach (var pair in dependencies.Dependencies)
|
||||
{
|
||||
Sort(pair.Value, dependencies, outputs);
|
||||
}
|
||||
|
||||
return outputs.Distinct(new ProjectComparer());
|
||||
}
|
||||
|
||||
private static void Sort(LibraryExport node, ProjectDependenciesFacade dependencies, IList<ProjectDescription> outputs)
|
||||
{
|
||||
// Sorts projects in dependency order so that we only build them once per chain
|
||||
ProjectDescription projectDependency;
|
||||
foreach (var dependency in node.Library.Dependencies)
|
||||
{
|
||||
// Sort the children
|
||||
Sort(dependencies.Dependencies[dependency.Name], dependencies, outputs);
|
||||
}
|
||||
|
||||
// Add this node to the list if it is a project
|
||||
if (dependencies.ProjectDependenciesWithSources.TryGetValue(node.Library.Identity.Name, out projectDependency))
|
||||
{
|
||||
// Add to the list of projects to build
|
||||
outputs.Add(projectDependency);
|
||||
}
|
||||
}
|
||||
|
||||
private class ProjectComparer : IEqualityComparer<ProjectDescription>
|
||||
{
|
||||
public bool Equals(ProjectDescription x, ProjectDescription y) => string.Equals(x.Identity.Name, y.Identity.Name, StringComparison.Ordinal);
|
||||
public int GetHashCode(ProjectDescription obj) => obj.Identity.Name.GetHashCode();
|
||||
}
|
||||
|
||||
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 CompilerIO GetCompileIO(ProjectContext project, ProjectDependenciesFacade dependencies)
|
||||
{
|
||||
var buildConfiguration = _args.ConfigValue;
|
||||
var buildBasePath = _args.BuildBasePathValue;
|
||||
var outputPath = _args.OutputValue;
|
||||
var isRootProject = project == _rootProject;
|
||||
|
||||
var compilerIO = new CompilerIO(new List<string>(), new List<string>());
|
||||
var calculator = project.GetOutputPaths(buildConfiguration, buildBasePath, outputPath);
|
||||
var binariesOutputPath = calculator.CompilationOutputPath;
|
||||
|
||||
// input: project.json
|
||||
compilerIO.Inputs.Add(project.ProjectFile.ProjectFilePath);
|
||||
|
||||
// input: lock file; find when dependencies change
|
||||
AddLockFile(project, compilerIO);
|
||||
|
||||
// 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
|
||||
// input: dependencies
|
||||
AddDependencies(dependencies, compilerIO);
|
||||
|
||||
var allOutputPath = new HashSet<string>(calculator.CompilationFiles.All());
|
||||
if (isRootProject && project.ProjectFile.HasRuntimeOutput(buildConfiguration))
|
||||
{
|
||||
var runtimeContext = project.CreateRuntimeContext(_args.GetRuntimes());
|
||||
foreach (var path in runtimeContext.GetOutputPaths(buildConfiguration, buildBasePath, outputPath).RuntimeFiles.All())
|
||||
{
|
||||
allOutputPath.Add(path);
|
||||
}
|
||||
}
|
||||
|
||||
// output: compiler outputs
|
||||
foreach (var path in allOutputPath)
|
||||
{
|
||||
compilerIO.Outputs.Add(path);
|
||||
}
|
||||
|
||||
// input compilation options files
|
||||
AddCompilationOptions(project, buildConfiguration, compilerIO);
|
||||
|
||||
// input / output: resources with culture
|
||||
AddNonCultureResources(project, calculator.IntermediateOutputDirectoryPath, compilerIO);
|
||||
|
||||
// input / output: resources without culture
|
||||
AddCultureResources(project, binariesOutputPath, compilerIO);
|
||||
|
||||
return compilerIO;
|
||||
}
|
||||
|
||||
private static void AddLockFile(ProjectContext project, CompilerIO compilerIO)
|
||||
{
|
||||
if (project.LockFile == null)
|
||||
{
|
||||
var errorMessage = $"Project {project.ProjectName()} does not have a lock file.";
|
||||
Reporter.Error.WriteLine(errorMessage);
|
||||
throw new InvalidOperationException(errorMessage);
|
||||
}
|
||||
|
||||
compilerIO.Inputs.Add(project.LockFile.LockFilePath);
|
||||
|
||||
if (project.LockFile.ExportFile != null)
|
||||
{
|
||||
compilerIO.Inputs.Add(project.LockFile.ExportFile.ExportFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
// non project dependencies get captured by changes in the lock file
|
||||
}
|
||||
|
||||
private static void AddCompilationOptions(ProjectContext project, string config, CompilerIO compilerIO)
|
||||
{
|
||||
var compilerOptions = project.ResolveCompilationOptions(config);
|
||||
|
||||
// input: key file
|
||||
if (compilerOptions.KeyFile != null)
|
||||
{
|
||||
compilerIO.Inputs.Add(compilerOptions.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
19
src/dotnet/commands/dotnet-build/CompilerIO.cs
Normal file
19
src/dotnet/commands/dotnet-build/CompilerIO.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Build
|
||||
{
|
||||
internal struct CompilerIO
|
||||
{
|
||||
public readonly List<string> Inputs;
|
||||
public readonly List<string> Outputs;
|
||||
|
||||
public CompilerIO(List<string> inputs, List<string> outputs)
|
||||
{
|
||||
Inputs = inputs;
|
||||
Outputs = outputs;
|
||||
}
|
||||
}
|
||||
}
|
159
src/dotnet/commands/dotnet-build/CompilerIOManager.cs
Normal file
159
src/dotnet/commands/dotnet-build/CompilerIOManager.cs
Normal file
|
@ -0,0 +1,159 @@
|
|||
// 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.Tools.Compiler;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.DotNet.Cli.Compiler.Common;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Build
|
||||
{
|
||||
internal class CompilerIOManager
|
||||
{
|
||||
private readonly string _configuration;
|
||||
private readonly string _outputPath;
|
||||
private readonly string _buildBasePath;
|
||||
private readonly IList<string> _runtimes;
|
||||
|
||||
public CompilerIOManager(string configuration,
|
||||
string outputPath,
|
||||
string buildBasePath,
|
||||
IEnumerable<string> runtimes)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_outputPath = outputPath;
|
||||
_buildBasePath = buildBasePath;
|
||||
_runtimes = runtimes.ToList();
|
||||
}
|
||||
|
||||
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
|
||||
// ensures that all paths are files
|
||||
// ensures no missing inputs
|
||||
public CompilerIO GetCompileIO(ProjectGraphNode graphNode)
|
||||
{
|
||||
var isRootProject = graphNode.IsRoot;
|
||||
var project = graphNode.ProjectContext;
|
||||
|
||||
var compilerIO = new CompilerIO(new List<string>(), new List<string>());
|
||||
var calculator = project.GetOutputPaths(_configuration, _buildBasePath, _outputPath);
|
||||
var binariesOutputPath = calculator.CompilationOutputPath;
|
||||
|
||||
// input: project.json
|
||||
compilerIO.Inputs.Add(project.ProjectFile.ProjectFilePath);
|
||||
|
||||
// input: lock file; find when dependencies change
|
||||
AddLockFile(project, compilerIO);
|
||||
|
||||
// input: source files
|
||||
compilerIO.Inputs.AddRange(CompilerUtil.GetCompilationSources(project));
|
||||
|
||||
var allOutputPath = new HashSet<string>(calculator.CompilationFiles.All());
|
||||
if (isRootProject && project.ProjectFile.HasRuntimeOutput(_configuration))
|
||||
{
|
||||
var runtimeContext = project.CreateRuntimeContext(_runtimes);
|
||||
foreach (var path in runtimeContext.GetOutputPaths(_configuration, _buildBasePath, _outputPath).RuntimeFiles.All())
|
||||
{
|
||||
allOutputPath.Add(path);
|
||||
}
|
||||
}
|
||||
|
||||
// output: compiler outputs
|
||||
foreach (var path in allOutputPath)
|
||||
{
|
||||
compilerIO.Outputs.Add(path);
|
||||
}
|
||||
|
||||
// input compilation options files
|
||||
AddCompilationOptions(project, _configuration, compilerIO);
|
||||
|
||||
// input / output: resources with culture
|
||||
AddNonCultureResources(project, calculator.IntermediateOutputDirectoryPath, compilerIO);
|
||||
|
||||
// input / output: resources without culture
|
||||
AddCultureResources(project, binariesOutputPath, compilerIO);
|
||||
|
||||
return compilerIO;
|
||||
}
|
||||
|
||||
|
||||
private static void AddLockFile(ProjectContext project, CompilerIO compilerIO)
|
||||
{
|
||||
if (project.LockFile == null)
|
||||
{
|
||||
var errorMessage = $"Project {project.ProjectName()} does not have a lock file.";
|
||||
Reporter.Error.WriteLine(errorMessage);
|
||||
throw new InvalidOperationException(errorMessage);
|
||||
}
|
||||
|
||||
compilerIO.Inputs.Add(project.LockFile.LockFilePath);
|
||||
|
||||
if (project.LockFile.ExportFile != null)
|
||||
{
|
||||
compilerIO.Inputs.Add(project.LockFile.ExportFile.ExportFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void AddCompilationOptions(ProjectContext project, string config, CompilerIO compilerIO)
|
||||
{
|
||||
var compilerOptions = project.ResolveCompilationOptions(config);
|
||||
|
||||
// input: key file
|
||||
if (compilerOptions.KeyFile != null)
|
||||
{
|
||||
compilerIO.Inputs.Add(compilerOptions.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
267
src/dotnet/commands/dotnet-build/DotnetProjectBuilder.cs
Normal file
267
src/dotnet/commands/dotnet-build/DotnetProjectBuilder.cs
Normal file
|
@ -0,0 +1,267 @@
|
|||
// 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;
|
||||
using Microsoft.DotNet.Cli.Compiler.Common;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
using Microsoft.DotNet.Tools.Compiler;
|
||||
using Microsoft.Extensions.PlatformAbstractions;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Build
|
||||
{
|
||||
internal class DotNetProjectBuilder : ProjectBuilder
|
||||
{
|
||||
private readonly BuildCommandApp _args;
|
||||
private readonly IncrementalPreconditionManager _preconditionManager;
|
||||
private readonly CompilerIOManager _compilerIOManager;
|
||||
private readonly ScriptRunner _scriptRunner;
|
||||
private readonly DotNetCommandFactory _commandFactory;
|
||||
|
||||
public DotNetProjectBuilder(BuildCommandApp args)
|
||||
{
|
||||
_args = args;
|
||||
_preconditionManager = new IncrementalPreconditionManager(
|
||||
args.ShouldPrintIncrementalPreconditions,
|
||||
args.ShouldNotUseIncrementality,
|
||||
args.ShouldSkipDependencies);
|
||||
_compilerIOManager = new CompilerIOManager(
|
||||
args.ConfigValue,
|
||||
args.OutputValue,
|
||||
args.BuildBasePathValue,
|
||||
args.GetRuntimes()
|
||||
);
|
||||
_scriptRunner = new ScriptRunner();
|
||||
_commandFactory = new DotNetCommandFactory();
|
||||
}
|
||||
|
||||
private void StampProjectWithSDKVersion(ProjectContext project)
|
||||
{
|
||||
if (File.Exists(DotnetFiles.VersionFile))
|
||||
{
|
||||
var projectVersionFile = project.GetSDKVersionFile(_args.ConfigValue, _args.BuildBasePathValue, _args.OutputValue);
|
||||
var parentDirectory = Path.GetDirectoryName(projectVersionFile);
|
||||
|
||||
if (!Directory.Exists(parentDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(parentDirectory);
|
||||
}
|
||||
|
||||
string content = ComputeCurrentVersionFileData();
|
||||
|
||||
File.WriteAllText(projectVersionFile, content);
|
||||
}
|
||||
else
|
||||
{
|
||||
Reporter.Verbose.WriteLine($"Project {project.GetDisplayName()} was not stamped with a CLI version because the version file does not exist: {DotnetFiles.VersionFile}");
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
var preconditions = _preconditionManager.GetIncrementalPreconditions(projectNode);
|
||||
Reporter.Output.Write(" " + preconditions.LogMessage());
|
||||
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)
|
||||
{
|
||||
var dest = outputPaths.RuntimeOutputPath;
|
||||
var source = outputPaths.CompilationOutputPath;
|
||||
|
||||
// No need to copy if dest and source are the same
|
||||
if (string.Equals(dest, source, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var file in outputPaths.CompilationFiles.All())
|
||||
{
|
||||
var destFileName = file.Replace(source, dest);
|
||||
var directoryName = Path.GetDirectoryName(destFileName);
|
||||
if (!Directory.Exists(directoryName))
|
||||
{
|
||||
Directory.CreateDirectory(directoryName);
|
||||
}
|
||||
File.Copy(file, destFileName, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void MakeRunnable(ProjectGraphNode graphNode)
|
||||
{
|
||||
var runtimeContext = graphNode.ProjectContext.ProjectFile.HasRuntimeOutput(_args.ConfigValue) ?
|
||||
graphNode.ProjectContext.CreateRuntimeContext(_args.GetRuntimes()) :
|
||||
graphNode.ProjectContext;
|
||||
|
||||
var outputPaths = runtimeContext.GetOutputPaths(_args.ConfigValue, _args.BuildBasePathValue, _args.OutputValue);
|
||||
var libraryExporter = runtimeContext.CreateExporter(_args.ConfigValue, _args.BuildBasePathValue);
|
||||
|
||||
CopyCompilationOutput(outputPaths);
|
||||
|
||||
var executable = new Executable(runtimeContext, outputPaths, libraryExporter, _args.ConfigValue);
|
||||
executable.MakeCompilationOutputRunnable();
|
||||
}
|
||||
|
||||
protected override CompilationResult RunCompile(ProjectGraphNode projectNode)
|
||||
{
|
||||
try
|
||||
{
|
||||
var managedCompiler = new ManagedCompiler(_scriptRunner, _commandFactory);
|
||||
|
||||
var success = managedCompiler.Compile(projectNode.ProjectContext, _args);
|
||||
if (projectNode.IsRoot)
|
||||
{
|
||||
MakeRunnable(projectNode);
|
||||
PrintSummary(projectNode, success);
|
||||
}
|
||||
|
||||
return success ? CompilationResult.Success : CompilationResult.Failure;
|
||||
}
|
||||
finally
|
||||
{
|
||||
StampProjectWithSDKVersion(projectNode.ProjectContext);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ProjectSkiped(ProjectGraphNode projectNode)
|
||||
{
|
||||
StampProjectWithSDKVersion(projectNode.ProjectContext);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var project = graphNode.ProjectContext;
|
||||
if (_args.ShouldNotUseIncrementality)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
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))
|
||||
{
|
||||
Reporter.Output.WriteLine($"Project {project.GetDisplayName()} will be compiled because the version or bitness of the CLI changed since the last build");
|
||||
return true;
|
||||
}
|
||||
|
||||
var compilerIO = _compilerIOManager.GetCompileIO(graphNode);
|
||||
|
||||
// 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");
|
||||
return true;
|
||||
}
|
||||
|
||||
//rebuild if missing inputs / outputs
|
||||
if (_compilerIOManager.AnyMissingIO(project, compilerIO.Outputs, "outputs") || _compilerIOManager.AnyMissingIO(project, compilerIO.Inputs, "inputs"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
// 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.Linq;
|
||||
using Microsoft.DotNet.Tools.Compiler;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.DotNet.Cli.Compiler.Common;
|
||||
using Microsoft.DotNet.ProjectModel.Utilities;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Build
|
||||
{
|
||||
internal class IncrementalPreconditionManager
|
||||
{
|
||||
private readonly bool _printPreconditions;
|
||||
private readonly bool _forceNonIncremental;
|
||||
private readonly bool _skipDependencies;
|
||||
private Dictionary<ProjectContextIdentity, IncrementalPreconditions> _preconditions;
|
||||
|
||||
public IncrementalPreconditionManager(bool printPreconditions, bool forceNonIncremental, bool skipDependencies)
|
||||
{
|
||||
_printPreconditions = printPreconditions;
|
||||
_forceNonIncremental = forceNonIncremental;
|
||||
_skipDependencies = skipDependencies;
|
||||
_preconditions = new Dictionary<ProjectContextIdentity, IncrementalPreconditions>();
|
||||
}
|
||||
|
||||
public static readonly string[] KnownCompilers = { "csc", "vbc", "fsc" };
|
||||
|
||||
public IncrementalPreconditions GetIncrementalPreconditions(ProjectGraphNode projectNode)
|
||||
{
|
||||
IncrementalPreconditions preconditions;
|
||||
if (_preconditions.TryGetValue(projectNode.ProjectContext.Identity, out preconditions))
|
||||
{
|
||||
return preconditions;
|
||||
}
|
||||
|
||||
preconditions = new IncrementalPreconditions(_printPreconditions);
|
||||
|
||||
if (_forceNonIncremental)
|
||||
{
|
||||
preconditions.AddForceUnsafePrecondition();
|
||||
}
|
||||
|
||||
var project = projectNode.ProjectContext;
|
||||
CollectScriptPreconditions(project, preconditions);
|
||||
CollectCompilerNamePreconditions(project, preconditions);
|
||||
CollectCheckPathProbingPreconditions(project, preconditions);
|
||||
_preconditions[projectNode.ProjectContext.Identity] = preconditions;
|
||||
return preconditions;
|
||||
}
|
||||
|
||||
private void CollectCheckPathProbingPreconditions(ProjectContext project, IncrementalPreconditions preconditions)
|
||||
{
|
||||
var pathCommands = CompilerUtil.GetCommandsInvokedByCompile(project)
|
||||
.Select(commandName => Command.CreateDotNet(commandName, Enumerable.Empty<string>(), project.TargetFramework))
|
||||
.Where(c => c.ResolutionStrategy.Equals(CommandResolutionStrategy.Path));
|
||||
|
||||
foreach (var pathCommand in pathCommands)
|
||||
{
|
||||
preconditions.AddPathProbingPrecondition(project.ProjectName(), pathCommand.CommandName);
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectCompilerNamePreconditions(ProjectContext project, IncrementalPreconditions preconditions)
|
||||
{
|
||||
if (project.ProjectFile != null)
|
||||
{
|
||||
var projectCompiler = project.ProjectFile.CompilerName;
|
||||
|
||||
if (!KnownCompilers.Any(knownCompiler => knownCompiler.Equals(projectCompiler, StringComparison.Ordinal)))
|
||||
{
|
||||
preconditions.AddUnknownCompilerPrecondition(project.ProjectName(), projectCompiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectScriptPreconditions(ProjectContext project, IncrementalPreconditions preconditions)
|
||||
{
|
||||
if (project.ProjectFile != null)
|
||||
{
|
||||
var preCompileScripts = project.ProjectFile.Scripts.GetOrEmpty(ScriptNames.PreCompile);
|
||||
var postCompileScripts = project.ProjectFile.Scripts.GetOrEmpty(ScriptNames.PostCompile);
|
||||
|
||||
if (preCompileScripts.Any())
|
||||
{
|
||||
preconditions.AddPrePostScriptPrecondition(project.ProjectName(), ScriptNames.PreCompile);
|
||||
}
|
||||
|
||||
if (postCompileScripts.Any())
|
||||
{
|
||||
preconditions.AddPrePostScriptPrecondition(project.ProjectName(), ScriptNames.PostCompile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.DotNet.Tools.Compiler;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Build
|
||||
{
|
||||
|
@ -36,7 +37,7 @@ namespace Microsoft.DotNet.Tools.Build
|
|||
|
||||
public void AddForceUnsafePrecondition()
|
||||
{
|
||||
_preconditions.Add($"[Forced Unsafe] The build was marked as unsafe. Remove the {BuilderCommandApp.NoIncrementalFlag} flag to enable incremental compilation");
|
||||
_preconditions.Add($"[Forced Unsafe] The build was marked as unsafe. Remove the {BuildCommandApp.NoIncrementalFlag} flag to enable incremental compilation");
|
||||
}
|
||||
|
||||
public bool PreconditionsDetected()
|
||||
|
@ -75,7 +76,7 @@ namespace Microsoft.DotNet.Tools.Build
|
|||
{
|
||||
if (PreconditionsDetected())
|
||||
{
|
||||
return _isProfile ? PreconditionsMessage().Yellow() : $"(The compilation time can be improved. Run \"dotnet build {BuilderCommandApp.BuildProfileFlag}\" for more information)";
|
||||
return _isProfile ? PreconditionsMessage().Yellow() : $"(The compilation time can be improved. Run \"dotnet build {BuildCommandApp.BuildProfileFlag}\" for more information)";
|
||||
}
|
||||
|
||||
return "";
|
||||
|
|
|
@ -5,10 +5,11 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
using Microsoft.DotNet.Tools.Compiler;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using NuGet.Frameworks;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Build
|
||||
{
|
||||
|
@ -20,7 +21,7 @@ namespace Microsoft.DotNet.Tools.Build
|
|||
|
||||
try
|
||||
{
|
||||
var app = new BuilderCommandApp("dotnet build", ".NET Builder", "Builder for the .NET Platform. It performs incremental compilation if it's safe to do so. Otherwise it delegates to dotnet-compile which performs non-incremental compilation");
|
||||
var app = new BuildCommandApp("dotnet build", ".NET Builder", "Builder for the .NET Platform. It performs incremental compilation if it's safe to do so. Otherwise it delegates to dotnet-compile which performs non-incremental compilation");
|
||||
return app.Execute(OnExecute, args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -34,13 +35,70 @@ namespace Microsoft.DotNet.Tools.Build
|
|||
}
|
||||
}
|
||||
|
||||
private static bool OnExecute(List<ProjectContext> contexts, CompilerCommandApp args)
|
||||
private static bool OnExecute(IEnumerable<string> files, IEnumerable<NuGetFramework> frameworks, BuildCommandApp args)
|
||||
{
|
||||
var compileContexts = contexts.Select(context => new CompileContext(context, (BuilderCommandApp)args)).ToList();
|
||||
var builderCommandApp = args;
|
||||
var graphCollector = new ProjectGraphCollector(
|
||||
!builderCommandApp.ShouldSkipDependencies,
|
||||
(project, target) => ProjectContext.Create(project, target));
|
||||
|
||||
var incrementalSafe = compileContexts.All(c => c.IsSafeForIncrementalCompilation);
|
||||
var contexts = ResolveRootContexts(files, frameworks, args);
|
||||
var graph = graphCollector.Collect(contexts).ToArray();
|
||||
var builder = new DotNetProjectBuilder(builderCommandApp);
|
||||
return builder.Build(graph).ToArray().All(r => r != CompilationResult.Failure);
|
||||
}
|
||||
|
||||
return compileContexts.All(c => c.Compile(incrementalSafe));
|
||||
private static IEnumerable<ProjectContext> ResolveRootContexts(
|
||||
IEnumerable<string> files,
|
||||
IEnumerable<NuGetFramework> frameworks,
|
||||
BuildCommandApp args)
|
||||
{
|
||||
|
||||
List<Task<ProjectContext>> tasks = new List<Task<ProjectContext>>();
|
||||
// Set defaults based on the environment
|
||||
var settings = ProjectReaderSettings.ReadFromEnvironment();
|
||||
|
||||
if (!string.IsNullOrEmpty(args.VersionSuffixValue))
|
||||
{
|
||||
settings.VersionSuffix = args.VersionSuffixValue;
|
||||
}
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var project = ProjectReader.GetProject(file);
|
||||
var projectFrameworks = project.GetTargetFrameworks().Select(f => f.FrameworkName);
|
||||
if (!projectFrameworks.Any())
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Project '{file}' does not have any frameworks listed in the 'frameworks' section.");
|
||||
}
|
||||
IEnumerable<NuGetFramework> selectedFrameworks;
|
||||
if (frameworks != null)
|
||||
{
|
||||
var unsupportedByProject = frameworks.Where(f => !projectFrameworks.Contains(f));
|
||||
if (unsupportedByProject.Any())
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Project \'{file}\' does not support framework: {string.Join(", ", unsupportedByProject.Select(fx => fx.DotNetFrameworkName))}.");
|
||||
}
|
||||
|
||||
selectedFrameworks = frameworks;
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedFrameworks = projectFrameworks;
|
||||
}
|
||||
|
||||
foreach (var framework in selectedFrameworks)
|
||||
{
|
||||
tasks.Add(Task.Run(() => new ProjectContextBuilder()
|
||||
.WithProjectDirectory(Path.GetDirectoryName(file))
|
||||
.WithTargetFramework(framework)
|
||||
.WithReaderSettings(settings)
|
||||
.Build()));
|
||||
}
|
||||
}
|
||||
return Task.WhenAll(tasks).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
86
src/dotnet/commands/dotnet-build/ProjectBuilder.cs
Normal file
86
src/dotnet/commands/dotnet-build/ProjectBuilder.cs
Normal file
|
@ -0,0 +1,86 @@
|
|||
// 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.Linq;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
using NuGet.Frameworks;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Build
|
||||
{
|
||||
internal abstract class ProjectBuilder
|
||||
{
|
||||
private Dictionary<ProjectContextIdentity, CompilationResult> _compilationResults = new Dictionary<ProjectContextIdentity, CompilationResult>();
|
||||
|
||||
public IEnumerable<CompilationResult> Build(IEnumerable<ProjectGraphNode> roots)
|
||||
{
|
||||
foreach (var projectNode in roots)
|
||||
{
|
||||
yield return Build(projectNode);
|
||||
}
|
||||
}
|
||||
|
||||
protected CompilationResult? GetCompilationResult(ProjectGraphNode projectNode)
|
||||
{
|
||||
CompilationResult result;
|
||||
if (_compilationResults.TryGetValue(projectNode.ProjectContext.Identity, out result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected virtual bool NeedsRebuilding(ProjectGraphNode projectNode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual void ProjectSkiped(ProjectGraphNode projectNode)
|
||||
{
|
||||
}
|
||||
protected abstract CompilationResult RunCompile(ProjectGraphNode projectNode);
|
||||
|
||||
private CompilationResult Build(ProjectGraphNode projectNode)
|
||||
{
|
||||
CompilationResult result;
|
||||
if (_compilationResults.TryGetValue(projectNode.ProjectContext.Identity, out result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
result = CompileWithDependencies(projectNode);
|
||||
|
||||
_compilationResults[projectNode.ProjectContext.Identity] = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private CompilationResult CompileWithDependencies(ProjectGraphNode projectNode)
|
||||
{
|
||||
foreach (var dependency in projectNode.Dependencies)
|
||||
{
|
||||
var result = Build(dependency);
|
||||
if (result == CompilationResult.Failure)
|
||||
{
|
||||
return CompilationResult.Failure;
|
||||
}
|
||||
}
|
||||
|
||||
var context = projectNode.ProjectContext;
|
||||
if (!context.ProjectFile.Files.SourceFiles.Any())
|
||||
{
|
||||
return CompilationResult.IncrementalSkip;
|
||||
}
|
||||
|
||||
if (NeedsRebuilding(projectNode))
|
||||
{
|
||||
return RunCompile(projectNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProjectSkiped(projectNode);
|
||||
return CompilationResult.IncrementalSkip;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
// 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 Dictionary<string, 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.Value.Library as ProjectDescription;
|
||||
|
||||
if (projectDependency != null && projectDependency.Resolved && projectDependency.Project.Files.SourceFiles.Any())
|
||||
{
|
||||
ProjectDependenciesWithSources[projectDependency.Identity.Name] = projectDependency;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo make extension of ProjectContext?
|
||||
private static Dictionary<string, 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.ToDictionary(d => d.Library.Identity.Name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
95
src/dotnet/commands/dotnet-build/ProjectGraphCollector.cs
Normal file
95
src/dotnet/commands/dotnet-build/ProjectGraphCollector.cs
Normal file
|
@ -0,0 +1,95 @@
|
|||
// 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 NuGet.Frameworks;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
using Microsoft.DotNet.ProjectModel.Graph;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Build
|
||||
{
|
||||
internal class ProjectGraphCollector
|
||||
{
|
||||
private readonly bool _collectDependencies;
|
||||
private readonly Func<string, NuGetFramework, ProjectContext> _projectContextFactory;
|
||||
|
||||
public ProjectGraphCollector(bool collectDependencies,
|
||||
Func<string, NuGetFramework, ProjectContext> projectContextFactory)
|
||||
{
|
||||
_collectDependencies = collectDependencies;
|
||||
_projectContextFactory = projectContextFactory;
|
||||
}
|
||||
|
||||
public IEnumerable<ProjectGraphNode> Collect(IEnumerable<ProjectContext> contexts)
|
||||
{
|
||||
foreach (var context in contexts)
|
||||
{
|
||||
var libraries = context.LibraryManager.GetLibraries();
|
||||
var lookup = libraries.ToDictionary(l => l.Identity.Name);
|
||||
var root = lookup[context.ProjectFile.Name];
|
||||
yield return TraverseProject((ProjectDescription) root, lookup, context);
|
||||
}
|
||||
}
|
||||
|
||||
private ProjectGraphNode TraverseProject(ProjectDescription project, IDictionary<string, LibraryDescription> lookup, ProjectContext context = null)
|
||||
{
|
||||
var deps = new List<ProjectGraphNode>();
|
||||
if (_collectDependencies)
|
||||
{
|
||||
foreach (var dependency in project.Dependencies)
|
||||
{
|
||||
LibraryDescription libraryDescription;
|
||||
if (lookup.TryGetValue(dependency.Name, out libraryDescription))
|
||||
{
|
||||
if (libraryDescription.Identity.Type.Equals(LibraryType.Project))
|
||||
{
|
||||
deps.Add(TraverseProject((ProjectDescription)libraryDescription, lookup));
|
||||
}
|
||||
else
|
||||
{
|
||||
deps.AddRange(TraverseNonProject(libraryDescription, lookup));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var task = context != null ? Task.FromResult(context) : Task.Run(() => _projectContextFactory(project.Path, project.Framework));
|
||||
return new ProjectGraphNode(task, deps, context != null);
|
||||
}
|
||||
|
||||
private IEnumerable<ProjectGraphNode> TraverseNonProject(LibraryDescription root, IDictionary<string, LibraryDescription> lookup)
|
||||
{
|
||||
Stack<LibraryDescription> libraries = new Stack<LibraryDescription>();
|
||||
libraries.Push(root);
|
||||
while (libraries.Count > 0)
|
||||
{
|
||||
var current = libraries.Pop();
|
||||
bool foundProject = false;
|
||||
foreach (var dependency in current.Dependencies)
|
||||
{
|
||||
LibraryDescription libraryDescription;
|
||||
if (lookup.TryGetValue(dependency.Name, out libraryDescription))
|
||||
{
|
||||
if (libraryDescription.Identity.Type.Equals(LibraryType.Project))
|
||||
{
|
||||
foundProject = true;
|
||||
yield return TraverseProject((ProjectDescription) libraryDescription, lookup);
|
||||
}
|
||||
else
|
||||
{
|
||||
libraries.Push(libraryDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
// if package didn't have any project dependencies inside remove it from lookup
|
||||
// and do not traverse anymore
|
||||
if (!foundProject)
|
||||
{
|
||||
lookup.Remove(current.Identity.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
src/dotnet/commands/dotnet-build/ProjectGraphNode.cs
Normal file
29
src/dotnet/commands/dotnet-build/ProjectGraphNode.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
// 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 Microsoft.DotNet.ProjectModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Build
|
||||
{
|
||||
internal class ProjectGraphNode
|
||||
{
|
||||
private readonly Task<ProjectContext> _projectContextCreator;
|
||||
|
||||
public ProjectGraphNode(Task<ProjectContext> projectContext, IEnumerable<ProjectGraphNode> dependencies, bool isRoot = false)
|
||||
{
|
||||
_projectContextCreator = projectContext;
|
||||
Dependencies = dependencies.ToList();
|
||||
IsRoot = isRoot;
|
||||
}
|
||||
|
||||
public ProjectContext ProjectContext { get { return _projectContextCreator.GetAwaiter().GetResult(); } }
|
||||
|
||||
public IReadOnlyList<ProjectGraphNode> Dependencies { get; }
|
||||
|
||||
public bool IsRoot { get; }
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
|||
_nativeCompiler = nativeCompiler;
|
||||
}
|
||||
|
||||
public bool Compile(IEnumerable<ProjectContext> contexts, CompilerCommandApp args)
|
||||
public bool Compile(IEnumerable<ProjectContext> contexts, BuildCommandApp args)
|
||||
{
|
||||
var success = true;
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
|||
{
|
||||
public abstract class Compiler : ICompiler
|
||||
{
|
||||
public abstract bool Compile(ProjectContext context, CompilerCommandApp args);
|
||||
public abstract bool Compile(ProjectContext context, BuildCommandApp args);
|
||||
|
||||
protected static bool PrintSummary(List<DiagnosticMessage> diagnostics, Stopwatch sw, bool success = true)
|
||||
{
|
||||
|
|
|
@ -1,205 +0,0 @@
|
|||
// 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.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Dnx.Runtime.Common.CommandLine;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
using Microsoft.Extensions.PlatformAbstractions;
|
||||
using NuGet.Frameworks;
|
||||
|
||||
// This class is responsible with defining the arguments for the Compile verb.
|
||||
// It knows how to interpret them and set default values
|
||||
namespace Microsoft.DotNet.Tools.Compiler
|
||||
{
|
||||
public delegate bool OnExecute(List<ProjectContext> contexts, CompilerCommandApp compilerCommand);
|
||||
|
||||
public class CompilerCommandApp
|
||||
{
|
||||
private readonly CommandLineApplication _app;
|
||||
|
||||
// options and arguments for compilation
|
||||
private CommandOption _outputOption;
|
||||
private CommandOption _buildBasePath;
|
||||
private CommandOption _frameworkOption;
|
||||
private CommandOption _runtimeOption;
|
||||
private CommandOption _versionSuffixOption;
|
||||
private CommandOption _configurationOption;
|
||||
private CommandArgument _projectArgument;
|
||||
private CommandOption _nativeOption;
|
||||
private CommandOption _archOption;
|
||||
private CommandOption _ilcArgsOption;
|
||||
private CommandOption _ilcPathOption;
|
||||
private CommandOption _ilcSdkPathOption;
|
||||
private CommandOption _appDepSdkPathOption;
|
||||
private CommandOption _cppModeOption;
|
||||
private CommandOption _cppCompilerFlagsOption;
|
||||
|
||||
// resolved values for the options and arguments
|
||||
public string ProjectPathValue { get; set; }
|
||||
public string BuildBasePathValue { get; set; }
|
||||
public string RuntimeValue { get; set; }
|
||||
public string OutputValue { get; set; }
|
||||
public string VersionSuffixValue { get; set; }
|
||||
public string ConfigValue { get; set; }
|
||||
public bool IsNativeValue { get; set; }
|
||||
public string ArchValue { get; set; }
|
||||
public IEnumerable<string> IlcArgsValue { get; set; }
|
||||
public string IlcPathValue { get; set; }
|
||||
public string IlcSdkPathValue { get; set; }
|
||||
public bool IsCppModeValue { get; set; }
|
||||
public string AppDepSdkPathValue { get; set; }
|
||||
public string CppCompilerFlagsValue { get; set; }
|
||||
|
||||
// workaround: CommandLineApplication is internal therefore I cannot make _app protected so baseclasses can add their own params
|
||||
private readonly Dictionary<string, CommandOption> baseClassOptions;
|
||||
|
||||
public CompilerCommandApp(string name, string fullName, string description)
|
||||
{
|
||||
_app = new CommandLineApplication
|
||||
{
|
||||
Name = name,
|
||||
FullName = fullName,
|
||||
Description = description
|
||||
};
|
||||
|
||||
baseClassOptions = new Dictionary<string, CommandOption>();
|
||||
|
||||
AddCompileParameters();
|
||||
}
|
||||
|
||||
private void AddCompileParameters()
|
||||
{
|
||||
_app.HelpOption("-h|--help");
|
||||
|
||||
_outputOption = _app.Option("-o|--output <OUTPUT_DIR>", "Directory in which to place outputs", CommandOptionType.SingleValue);
|
||||
_buildBasePath = _app.Option("-b|--build-base-path <OUTPUT_DIR>", "Directory in which to place temporary outputs", CommandOptionType.SingleValue);
|
||||
_frameworkOption = _app.Option("-f|--framework <FRAMEWORK>", "Compile a specific framework", CommandOptionType.SingleValue);
|
||||
_runtimeOption = _app.Option("-r|--runtime <RUNTIME_IDENTIFIER>", "Produce runtime-specific assets for the specified runtime", CommandOptionType.SingleValue);
|
||||
_configurationOption = _app.Option("-c|--configuration <CONFIGURATION>", "Configuration under which to build", CommandOptionType.SingleValue);
|
||||
_versionSuffixOption = _app.Option("--version-suffix <VERSION_SUFFIX>", "Defines what `*` should be replaced with in version field in project.json", CommandOptionType.SingleValue);
|
||||
_projectArgument = _app.Argument("<PROJECT>", "The project to compile, defaults to the current directory. Can be a path to a project.json or a project directory");
|
||||
|
||||
// Native Args
|
||||
_nativeOption = _app.Option("-n|--native", "Compiles source to native machine code.", CommandOptionType.NoValue);
|
||||
_archOption = _app.Option("-a|--arch <ARCH>", "The architecture for which to compile. x64 only currently supported.", CommandOptionType.SingleValue);
|
||||
_ilcArgsOption = _app.Option("--ilcarg <ARG>", "Command line option to be passed directly to ILCompiler.", CommandOptionType.MultipleValue);
|
||||
_ilcPathOption = _app.Option("--ilcpath <PATH>", "Path to the folder containing custom built ILCompiler.", CommandOptionType.SingleValue);
|
||||
_ilcSdkPathOption = _app.Option("--ilcsdkpath <PATH>", "Path to the folder containing ILCompiler application dependencies.", CommandOptionType.SingleValue);
|
||||
_appDepSdkPathOption = _app.Option("--appdepsdkpath <PATH>", "Path to the folder containing ILCompiler application dependencies.", CommandOptionType.SingleValue);
|
||||
_cppModeOption = _app.Option("--cpp", "Flag to do native compilation with C++ code generator.", CommandOptionType.NoValue);
|
||||
_cppCompilerFlagsOption = _app.Option("--cppcompilerflags <flags>", "Additional flags to be passed to the native compiler.", CommandOptionType.SingleValue);
|
||||
}
|
||||
|
||||
public int Execute(OnExecute execute, string[] args)
|
||||
{
|
||||
_app.OnExecute(() =>
|
||||
{
|
||||
if (_outputOption.HasValue() && !_frameworkOption.HasValue())
|
||||
{
|
||||
Reporter.Error.WriteLine("When the '--output' option is provided, the '--framework' option must also be provided.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Locate the project and get the name and full path
|
||||
ProjectPathValue = _projectArgument.Value;
|
||||
if (string.IsNullOrEmpty(ProjectPathValue))
|
||||
{
|
||||
ProjectPathValue = Directory.GetCurrentDirectory();
|
||||
}
|
||||
|
||||
OutputValue = _outputOption.Value();
|
||||
BuildBasePathValue = _buildBasePath.Value();
|
||||
ConfigValue = _configurationOption.Value() ?? Constants.DefaultConfiguration;
|
||||
RuntimeValue = _runtimeOption.Value();
|
||||
VersionSuffixValue = _versionSuffixOption.Value();
|
||||
|
||||
IsNativeValue = _nativeOption.HasValue();
|
||||
ArchValue = _archOption.Value();
|
||||
IlcArgsValue = _ilcArgsOption.HasValue() ? _ilcArgsOption.Values : Enumerable.Empty<string>();
|
||||
IlcPathValue = _ilcPathOption.Value();
|
||||
IlcSdkPathValue = _ilcSdkPathOption.Value();
|
||||
AppDepSdkPathValue = _appDepSdkPathOption.Value();
|
||||
IsCppModeValue = _cppModeOption.HasValue();
|
||||
CppCompilerFlagsValue = _cppCompilerFlagsOption.Value();
|
||||
|
||||
// Set defaults based on the environment
|
||||
var settings = ProjectReaderSettings.ReadFromEnvironment();
|
||||
|
||||
if (!string.IsNullOrEmpty(VersionSuffixValue))
|
||||
{
|
||||
settings.VersionSuffix = VersionSuffixValue;
|
||||
}
|
||||
|
||||
// Load the project file and construct all the targets
|
||||
var targets = ProjectContext.CreateContextForEachFramework(ProjectPathValue, settings).ToList();
|
||||
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
// Project is missing 'frameworks' section
|
||||
Reporter.Error.WriteLine("Project does not have any frameworks listed in the 'frameworks' section.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Filter the targets down based on the inputs
|
||||
if (_frameworkOption.HasValue())
|
||||
{
|
||||
var fx = NuGetFramework.Parse(_frameworkOption.Value());
|
||||
targets = targets.Where(t => fx.Equals(t.TargetFramework)).ToList();
|
||||
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
// We filtered everything out
|
||||
Reporter.Error.WriteLine($"Project does not support framework: {fx.DotNetFrameworkName}.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
Debug.Assert(targets.Count == 1);
|
||||
}
|
||||
|
||||
Debug.Assert(targets.All(t => string.IsNullOrEmpty(t.RuntimeIdentifier)));
|
||||
|
||||
var success = execute(targets, this);
|
||||
|
||||
return success ? 0 : 1;
|
||||
});
|
||||
|
||||
return _app.Execute(args);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
baseClassOptions[optionTemplate] = _app.Option(optionTemplate, descriptino, CommandOptionType.NoValue);
|
||||
}
|
||||
|
||||
protected bool OptionHasValue(string optionTemplate)
|
||||
{
|
||||
CommandOption option;
|
||||
|
||||
return baseClassOptions.TryGetValue(optionTemplate, out option) && option.HasValue();
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetRuntimes()
|
||||
{
|
||||
var rids = new List<string>();
|
||||
if (string.IsNullOrEmpty(RuntimeValue))
|
||||
{
|
||||
return PlatformServices.Default.Runtime.GetAllCandidateRuntimeIdentifiers();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new[] { RuntimeValue };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,6 @@ namespace Microsoft.DotNet.Tools.Compiler
|
|||
{
|
||||
public interface ICompiler
|
||||
{
|
||||
bool Compile(ProjectContext context, CompilerCommandApp args);
|
||||
bool Compile(ProjectContext context, BuildCommandApp args);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
|||
_commandFactory = commandFactory;
|
||||
}
|
||||
|
||||
public override bool Compile(ProjectContext context, CompilerCommandApp args)
|
||||
public override bool Compile(ProjectContext context, BuildCommandApp args)
|
||||
{
|
||||
// Set up Output Paths
|
||||
var outputPaths = context.GetOutputPaths(args.ConfigValue, args.BuildBasePathValue);
|
||||
|
|
|
@ -1,126 +0,0 @@
|
|||
// 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.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Compiler
|
||||
{
|
||||
public class NativeCompiler : Compiler
|
||||
{
|
||||
public override bool Compile(ProjectContext context, CompilerCommandApp args)
|
||||
{
|
||||
var outputPaths = context.GetOutputPaths(args.ConfigValue, args.BuildBasePathValue, args.OutputValue);
|
||||
var outputPath = outputPaths.RuntimeOutputPath;
|
||||
var nativeOutputPath = Path.Combine(outputPath, "native");
|
||||
var intermediateOutputPath =
|
||||
outputPaths.IntermediateOutputDirectoryPath;
|
||||
var nativeTempOutput = Path.Combine(intermediateOutputPath, "native");
|
||||
Directory.CreateDirectory(nativeOutputPath);
|
||||
Directory.CreateDirectory(nativeTempOutput);
|
||||
|
||||
var managedOutput = outputPaths.CompilationFiles.Assembly;
|
||||
|
||||
// Create the library exporter
|
||||
var exporter = context.CreateExporter(args.ConfigValue);
|
||||
var exports = exporter.GetDependencies();
|
||||
|
||||
// Runtime assemblies.
|
||||
// TODO: native assets/resources.
|
||||
var references = exports
|
||||
.SelectMany(export => export.RuntimeAssemblyGroups.GetDefaultAssets())
|
||||
.Select(r => r.ResolvedPath)
|
||||
.ToList();
|
||||
|
||||
// Setup native args.
|
||||
var nativeArgs = new List<string>();
|
||||
|
||||
// Input Assembly
|
||||
nativeArgs.Add($"{managedOutput}");
|
||||
|
||||
// Add Resolved Assembly References
|
||||
foreach (var reference in references)
|
||||
{
|
||||
nativeArgs.Add("--reference");
|
||||
nativeArgs.Add(reference);
|
||||
}
|
||||
|
||||
// ILC Args
|
||||
foreach (var ilcArg in args.IlcArgsValue)
|
||||
{
|
||||
nativeArgs.Add("--ilcarg");
|
||||
nativeArgs.Add($"\"{ilcArg}\"");
|
||||
}
|
||||
|
||||
// ILC Path
|
||||
if (!string.IsNullOrWhiteSpace(args.IlcPathValue))
|
||||
{
|
||||
nativeArgs.Add("--ilcpath");
|
||||
nativeArgs.Add(args.IlcPathValue);
|
||||
}
|
||||
|
||||
// ILC SDK Path
|
||||
if (!string.IsNullOrWhiteSpace(args.IlcSdkPathValue))
|
||||
{
|
||||
nativeArgs.Add("--ilcsdkpath");
|
||||
nativeArgs.Add(args.IlcSdkPathValue);
|
||||
}
|
||||
|
||||
// AppDep SDK Path
|
||||
if (!string.IsNullOrWhiteSpace(args.AppDepSdkPathValue))
|
||||
{
|
||||
nativeArgs.Add("--appdepsdk");
|
||||
nativeArgs.Add(args.AppDepSdkPathValue);
|
||||
}
|
||||
|
||||
// CodeGen Mode
|
||||
if (args.IsCppModeValue)
|
||||
{
|
||||
nativeArgs.Add("--mode");
|
||||
nativeArgs.Add("cpp");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(args.CppCompilerFlagsValue))
|
||||
{
|
||||
nativeArgs.Add("--cppcompilerflags");
|
||||
nativeArgs.Add(args.CppCompilerFlagsValue);
|
||||
}
|
||||
|
||||
// Configuration
|
||||
if (args.ConfigValue != null)
|
||||
{
|
||||
nativeArgs.Add("--configuration");
|
||||
nativeArgs.Add(args.ConfigValue);
|
||||
}
|
||||
|
||||
// Architecture
|
||||
if (args.ArchValue != null)
|
||||
{
|
||||
nativeArgs.Add("--arch");
|
||||
nativeArgs.Add(args.ArchValue);
|
||||
}
|
||||
|
||||
// Intermediate Path
|
||||
nativeArgs.Add("--temp-output");
|
||||
nativeArgs.Add($"{nativeTempOutput}");
|
||||
|
||||
// Output Path
|
||||
nativeArgs.Add("--output");
|
||||
nativeArgs.Add($"{nativeOutputPath}");
|
||||
|
||||
// Write Response File
|
||||
var rsp = Path.Combine(nativeTempOutput, $"dotnet-compile-native.{context.ProjectFile.Name}.rsp");
|
||||
File.WriteAllLines(rsp, nativeArgs);
|
||||
|
||||
// TODO Add -r assembly.dll for all Nuget References
|
||||
// Need CoreRT Framework published to nuget
|
||||
|
||||
// Do Native Compilation
|
||||
var result = Native.CompileNativeCommand.Run(new string[] { "--rsp", $"{rsp}" });
|
||||
|
||||
return result == 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
// 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 Microsoft.DotNet.Cli;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Compiler
|
||||
{
|
||||
public class CompileCommand
|
||||
{
|
||||
|
||||
public static int Run(string[] args)
|
||||
{
|
||||
DebugHelper.HandleDebugSwitch(ref args);
|
||||
|
||||
try
|
||||
{
|
||||
var commandFactory = new DotNetCommandFactory();
|
||||
var scriptRunner = new ScriptRunner();
|
||||
var managedCompiler = new ManagedCompiler(scriptRunner, commandFactory);
|
||||
var nativeCompiler = new NativeCompiler();
|
||||
var compilationDriver = new CompilationDriver(managedCompiler, nativeCompiler);
|
||||
|
||||
var compilerCommandArgs = new CompilerCommandApp("dotnet compile", ".NET Compiler", "Compiler for the .NET Platform");
|
||||
|
||||
return compilerCommandArgs.Execute(compilationDriver.Compile, args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.Error.WriteLine(ex);
|
||||
#else
|
||||
Console.Error.WriteLine(ex.Message);
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,6 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.Dnx.Runtime.Common.CommandLine;
|
||||
using Microsoft.Dotnet.Cli.Compiler.Common;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
using Microsoft.DotNet.ProjectModel.Graph;
|
||||
using NuGet.Frameworks;
|
||||
|
|
|
@ -18,12 +18,14 @@ namespace Microsoft.DotNet.Cli.Utils.Tests
|
|||
.CreateTestInstance("DesktopAppWhichCallsDotnet")
|
||||
.WithLockFiles()
|
||||
.WithBuildArtifacts();
|
||||
// project was moved to another location and needs it's relative path to Utils project restored
|
||||
new RestoreCommand().Execute(testInstance.TestRoot).Should().Pass();
|
||||
|
||||
var testProjectAssetManager = GetTestGroupTestAssetsManager("TestProjects");
|
||||
var testInstanceToBuild = testProjectAssetManager
|
||||
.CreateTestInstance("TestAppSimple")
|
||||
.WithLockFiles();
|
||||
|
||||
|
||||
var testProject = Path.Combine(testInstance.TestRoot, "project.json");
|
||||
var testProjectToBuild = Path.Combine(testInstanceToBuild.TestRoot, "project.json");
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
|||
.ExecuteWithCapturedOutput();
|
||||
|
||||
result.Should().Fail();
|
||||
result.Should().HaveStdErrContaining("Project does not support framework: Silverlight,Version=v4.0.");
|
||||
result.Should().HaveStdErrMatching("Project '.*?' does not support framework: Silverlight,Version=v4\\.0\\.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
134
test/dotnet-build.Tests/ProjectNameArgumentTests.cs
Normal file
134
test/dotnet-build.Tests/ProjectNameArgumentTests.cs
Normal file
|
@ -0,0 +1,134 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.DotNet.TestFramework;
|
||||
using Microsoft.DotNet.Tools.Test.Utilities;
|
||||
using Xunit;
|
||||
using FluentAssertions;
|
||||
using NuGet.Frameworks;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Builder.Tests
|
||||
{
|
||||
public class ProjectNameArgumentTests : TestBase
|
||||
{
|
||||
private TestInstance _testInstance;
|
||||
|
||||
[Fact]
|
||||
public void TestProjectDirectoryPath()
|
||||
{
|
||||
Test(new[] { Path.Combine("src", "L21") }, new[] { "L21" });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestProjectFile()
|
||||
{
|
||||
Test(new[] { Path.Combine("src", "L21", "project.json") }, new[] { "L21" });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestMultipleProjectDirectories()
|
||||
{
|
||||
Test(new[]
|
||||
{
|
||||
Path.Combine("src", "L21"),
|
||||
Path.Combine("src", "L11")
|
||||
},
|
||||
new[] { "L21", "L11" });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestMultipleProjectFiles()
|
||||
{
|
||||
Test(new[]
|
||||
{
|
||||
Path.Combine("src", "L21", "project.json"),
|
||||
Path.Combine("src", "L11", "project.json"),
|
||||
},
|
||||
new[] { "L21", "L11" });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGlobbing()
|
||||
{
|
||||
Test(new[]
|
||||
{
|
||||
Path.Combine("src", "**", "project.json")
|
||||
},
|
||||
new[] { "L21", "L11", "L12" });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestMultipleGlobbing()
|
||||
{
|
||||
Test(new[]
|
||||
{
|
||||
Path.Combine("src", "L1*", "project.json"),
|
||||
Path.Combine("src", "L2*", "project.json")
|
||||
},
|
||||
new[] { "L11", "L12", "L21", "L22" });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestFailsWhenNoGlobbingNoMatch()
|
||||
{
|
||||
Test(new[]
|
||||
{
|
||||
Path.Combine("src", "L33*", "project.json")
|
||||
},
|
||||
null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestFailsFileDoedNotExist()
|
||||
{
|
||||
Test(new[]
|
||||
{
|
||||
Path.Combine("src", "L33", "project.json")
|
||||
},
|
||||
null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestFindsProjectJsonInCurrentDirectoryWithNoArguments()
|
||||
{
|
||||
Test(new string[] { }, new[] { "L21" }, workingDirectory: Path.Combine("src", "L21"));
|
||||
}
|
||||
[Fact]
|
||||
public void TestFailsIfNoProjectJsonInCurrentDirectoryWithNoArguments()
|
||||
{
|
||||
Test(new string[] { }, null, workingDirectory: "src");
|
||||
}
|
||||
|
||||
private void Test(IEnumerable<string> inputs, IEnumerable<string> expectedProjects, string workingDirectory = null, [CallerMemberName] string testName = null)
|
||||
{
|
||||
var instance = TestAssetsManager.CreateTestInstance("TestProjectToProjectDependencies", testName)
|
||||
.WithLockFiles()
|
||||
.WithBuildArtifacts();
|
||||
string args = string.Join(" ", inputs);
|
||||
|
||||
workingDirectory = workingDirectory != null
|
||||
? Path.Combine(instance.TestRoot, workingDirectory)
|
||||
: instance.TestRoot;
|
||||
|
||||
var result = new TestCommand("dotnet")
|
||||
{
|
||||
WorkingDirectory = Path.Combine(workingDirectory)
|
||||
}.ExecuteWithCapturedOutput("--verbose build --no-dependencies " + args);
|
||||
if (expectedProjects != null)
|
||||
{
|
||||
result.Should().Pass();
|
||||
foreach (var expectedProject in expectedProjects)
|
||||
{
|
||||
result.Should().HaveSkippedProjectCompilation(expectedProject, NuGetFramework.Parse("netstandard1.5").DotNetFrameworkName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Should().Fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Tests
|
|||
private Mock<ICompiler> _managedCompilerMock;
|
||||
private Mock<ICompiler> _nativeCompilerMock;
|
||||
private List<ProjectContext> _contexts;
|
||||
private CompilerCommandApp _args;
|
||||
private BuildCommandApp _args;
|
||||
|
||||
public GivenACompilationDriverController()
|
||||
{
|
||||
|
@ -27,11 +27,11 @@ namespace Microsoft.DotNet.Tools.Compiler.Tests
|
|||
Path.Combine(AppContext.BaseDirectory, "TestAssets", "TestProjects", "TestAppWithLibrary", "TestApp", "project.json");
|
||||
_managedCompilerMock = new Mock<ICompiler>();
|
||||
_managedCompilerMock.Setup(c => c
|
||||
.Compile(It.IsAny<ProjectContext>(), It.IsAny<CompilerCommandApp>()))
|
||||
.Compile(It.IsAny<ProjectContext>(), It.IsAny<BuildCommandApp>()))
|
||||
.Returns(true);
|
||||
_nativeCompilerMock = new Mock<ICompiler>();
|
||||
_nativeCompilerMock.Setup(c => c
|
||||
.Compile(It.IsAny<ProjectContext>(), It.IsAny<CompilerCommandApp>()))
|
||||
.Compile(It.IsAny<ProjectContext>(), It.IsAny<BuildCommandApp>()))
|
||||
.Returns(true);
|
||||
|
||||
_contexts = new List<ProjectContext>
|
||||
|
@ -39,7 +39,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Tests
|
|||
ProjectContext.Create(_projectJson, NuGetFramework.Parse("netcoreapp1.0"))
|
||||
};
|
||||
|
||||
_args = new CompilerCommandApp("dotnet compile", ".NET Compiler", "Compiler for the .NET Platform");
|
||||
_args = new BuildCommandApp("dotnet compile", ".NET Compiler", "Compiler for the .NET Platform");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -47,8 +47,8 @@ namespace Microsoft.DotNet.Tools.Compiler.Tests
|
|||
{
|
||||
var compiledProjectContexts = new List<ProjectContext>();
|
||||
_managedCompilerMock.Setup(c => c
|
||||
.Compile(It.IsAny<ProjectContext>(), It.IsAny<CompilerCommandApp>()))
|
||||
.Callback<ProjectContext, CompilerCommandApp>((p, c) => compiledProjectContexts.Add(p))
|
||||
.Compile(It.IsAny<ProjectContext>(), It.IsAny<BuildCommandApp>()))
|
||||
.Callback<ProjectContext, BuildCommandApp>((p, c) => compiledProjectContexts.Add(p))
|
||||
.Returns(true);
|
||||
|
||||
var compilerController = new CompilationDriver(_managedCompilerMock.Object, _nativeCompilerMock.Object);
|
||||
|
@ -65,7 +65,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Tests
|
|||
|
||||
compilerController.Compile(_contexts, _args);
|
||||
|
||||
_nativeCompilerMock.Verify(c => c.Compile(It.IsAny<ProjectContext>(), It.IsAny<CompilerCommandApp>()), Times.Never);
|
||||
_nativeCompilerMock.Verify(c => c.Compile(It.IsAny<ProjectContext>(), It.IsAny<BuildCommandApp>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -77,7 +77,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Tests
|
|||
|
||||
compilerController.Compile(_contexts, _args);
|
||||
|
||||
_nativeCompilerMock.Verify(c => c.Compile(It.IsAny<ProjectContext>(), It.IsAny<CompilerCommandApp>()), Times.Once);
|
||||
_nativeCompilerMock.Verify(c => c.Compile(It.IsAny<ProjectContext>(), It.IsAny<BuildCommandApp>()), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -188,7 +188,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Tests
|
|||
It.IsAny<string>()))
|
||||
.Returns(command.Object);
|
||||
|
||||
var _args = new CompilerCommandApp("dotnet compile", ".NET Compiler", "Compiler for the .NET Platform");
|
||||
var _args = new BuildCommandApp("dotnet compile", ".NET Compiler", "Compiler for the .NET Platform");
|
||||
_args.ConfigValue = ConfigValue;
|
||||
|
||||
PreCompileScriptVariables = new Dictionary<string, string>();
|
||||
|
|
Loading…
Reference in a new issue