Merge pull request #625 from cdmihai/buildCommandWithSafetyChecks

Introduce --build-profile flag and precondition checking in the Build command
This commit is contained in:
Piotr Puszkiewicz 2015-12-28 10:46:27 -08:00
commit d1a257bff7
22 changed files with 990 additions and 344 deletions

View file

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23107.0
VisualStudioVersion = 14.0.24720.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Cli", "src\Microsoft.DotNet.Cli\Microsoft.DotNet.Cli.xproj", "{60CF7E6C-D6C8-439D-B7B7-D8A27E29BE2C}"
EndProject
@ -64,6 +64,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.ProjectMod
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.ProjectModel.Server.Tests", "test\Microsoft.DotNet.ProjectModel.Server.Tests\Microsoft.DotNet.ProjectModel.Server.Tests.xproj", "{11C77123-E4DA-499F-8900-80C88C2C69F2}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Tools.Builder", "src\Microsoft.DotNet.Tools.Builder\Microsoft.DotNet.Tools.Builder.xproj", "{0A309227-A9D8-4DDF-88DD-326B57B04379}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Extensions.DependencyModel", "src\Microsoft.Extensions.DependencyModel\Microsoft.Extensions.DependencyModel.xproj", "{688870C8-9843-4F9E-8576-D39290AD0F25}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Tools.Compiler.Fsc", "src\Microsoft.DotNet.Tools.Compiler.Fsc\Microsoft.DotNet.Tools.Compiler.Fsc.xproj", "{74F25188-BF63-4BF3-879B-B6CDB11ED608}"
@ -448,6 +450,22 @@ Global
{08A68C6A-86F6-4ED2-89A7-B166D33E9F85}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{08A68C6A-86F6-4ED2-89A7-B166D33E9F85}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{08A68C6A-86F6-4ED2-89A7-B166D33E9F85}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{0A309227-A9D8-4DDF-88DD-326B57B04379}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0A309227-A9D8-4DDF-88DD-326B57B04379}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A309227-A9D8-4DDF-88DD-326B57B04379}.Debug|x64.ActiveCfg = Debug|Any CPU
{0A309227-A9D8-4DDF-88DD-326B57B04379}.Debug|x64.Build.0 = Debug|Any CPU
{0A309227-A9D8-4DDF-88DD-326B57B04379}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
{0A309227-A9D8-4DDF-88DD-326B57B04379}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
{0A309227-A9D8-4DDF-88DD-326B57B04379}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{0A309227-A9D8-4DDF-88DD-326B57B04379}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{0A309227-A9D8-4DDF-88DD-326B57B04379}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A309227-A9D8-4DDF-88DD-326B57B04379}.Release|Any CPU.Build.0 = Release|Any CPU
{0A309227-A9D8-4DDF-88DD-326B57B04379}.Release|x64.ActiveCfg = Release|Any CPU
{0A309227-A9D8-4DDF-88DD-326B57B04379}.Release|x64.Build.0 = Release|Any CPU
{0A309227-A9D8-4DDF-88DD-326B57B04379}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
{0A309227-A9D8-4DDF-88DD-326B57B04379}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{0A309227-A9D8-4DDF-88DD-326B57B04379}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{0A309227-A9D8-4DDF-88DD-326B57B04379}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.Debug|Any CPU.Build.0 = Debug|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -540,6 +558,7 @@ Global
{7A75ACC4-3C2F-44E1-B492-0EC08704E9FF} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
{BC765FBF-AD7A-4A99-9902-5540C5A74181} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
{08A68C6A-86F6-4ED2-89A7-B166D33E9F85} = {0722D325-24C8-4E83-B5AF-0A083E7F0749}
{0A309227-A9D8-4DDF-88DD-326B57B04379} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
{688870C8-9843-4F9E-8576-D39290AD0F25} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
{74F25188-BF63-4BF3-879B-B6CDB11ED608} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
{1EA9AF94-5494-40DD-A05B-9D564572CCFC} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}

View file

@ -13,6 +13,7 @@ chmod -R 755 $INSTALL_DESTINATION
ln -s $INSTALL_DESTINATION/bin/dotnet /usr/local/bin/
ln -s $INSTALL_DESTINATION/bin/dotnet-compile /usr/local/bin/
ln -s $INSTALL_DESTINATION/bin/dotnet-build /usr/local/bin/
ln -s $INSTALL_DESTINATION/bin/dotnet-compile-csc /usr/local/bin/
ln -s $INSTALL_DESTINATION/bin/dotnet-compile-fsc /usr/local/bin/
ln -s $INSTALL_DESTINATION/bin/dotnet-new /usr/local/bin/

View file

@ -14,6 +14,7 @@ param(
$Projects = @(
"Microsoft.DotNet.Cli",
"Microsoft.DotNet.Tools.Compiler",
"Microsoft.DotNet.Tools.Builder",
"Microsoft.DotNet.Tools.Compiler.Csc",
"Microsoft.DotNet.Tools.Compiler.Fsc",
"Microsoft.DotNet.Tools.Compiler.Native",

View file

@ -26,6 +26,7 @@ source "$DIR/../_common.sh"
PROJECTS=( \
Microsoft.DotNet.Cli \
Microsoft.DotNet.Tools.Compiler \
Microsoft.DotNet.Tools.Builder \
Microsoft.DotNet.Tools.Compiler.Csc \
Microsoft.DotNet.Tools.Compiler.Fsc \
Microsoft.DotNet.Tools.Compiler.Native \

View file

@ -49,7 +49,7 @@ try {
# Check prereqs
if (!(Get-Command -ErrorAction SilentlyContinue cmake)) {
throw @"
cmake is required to build the native host 'corehost'"
cmake is required to build the native host 'corehost'
Download it from https://www.cmake.org
"@
}

View file

@ -21,9 +21,24 @@ namespace Microsoft.DotNet.Cli.Utils
private readonly StreamForwarder _stdOut;
private readonly StreamForwarder _stdErr;
public enum CommandResolutionStrategy
{
//command loaded from a nuget package
NugetPackage,
//command loaded from the same directory as the executing assembly
BaseDirectory,
//command loaded from path
Path,
//command not found
None
}
private bool _running = false;
private Command(string executable, string args)
private Command(string executable, string args, CommandResolutionStrategy resolutionStrategy)
{
// Set the things we need
var psi = new ProcessStartInfo()
@ -41,6 +56,8 @@ namespace Microsoft.DotNet.Cli.Utils
_stdOut = new StreamForwarder();
_stdErr = new StreamForwarder();
ResolutionStrategy = resolutionStrategy;
}
public static Command Create(string executable, IEnumerable<string> args, NuGetFramework framework = null)
@ -50,20 +67,25 @@ namespace Microsoft.DotNet.Cli.Utils
public static Command Create(string executable, string args, NuGetFramework framework = null)
{
ResolveExecutablePath(ref executable, ref args, framework);
return new Command(executable, args);
var resolutionStrategy = CommandResolutionStrategy.None;
ResolveExecutablePath(ref executable, ref args, ref resolutionStrategy, framework);
return new Command(executable, args, resolutionStrategy);
}
private static void ResolveExecutablePath(ref string executable, ref string args, NuGetFramework framework = null)
private static void ResolveExecutablePath(ref string executable, ref string args, ref CommandResolutionStrategy resolutionStrategy, NuGetFramework framework = null)
{
executable =
ResolveExecutablePathFromProject(executable, framework) ??
ResolveExecutableFromPath(executable, ref args);
ResolveExecutablePathFromProject(executable, framework, ref resolutionStrategy) ??
ResolveExecutableFromPath(executable, ref args, ref resolutionStrategy);
}
private static string ResolveExecutableFromPath(string executable, ref string args)
private static string ResolveExecutableFromPath(string executable, ref string args, ref CommandResolutionStrategy resolutionStrategy)
{
resolutionStrategy = CommandResolutionStrategy.Path;
foreach (string suffix in Constants.RunnableSuffixes)
{
var fullExecutable = Path.GetFullPath(Path.Combine(
@ -72,6 +94,8 @@ namespace Microsoft.DotNet.Cli.Utils
if (File.Exists(fullExecutable))
{
executable = fullExecutable;
resolutionStrategy = CommandResolutionStrategy.BaseDirectory;
// In priority order we've found the best runnable extension, so break.
break;
@ -93,7 +117,7 @@ namespace Microsoft.DotNet.Cli.Utils
return executable;
}
private static string ResolveExecutablePathFromProject(string executable, NuGetFramework framework)
private static string ResolveExecutablePathFromProject(string executable, NuGetFramework framework, ref CommandResolutionStrategy resolutionStrategy)
{
if (framework == null) return null;
@ -125,6 +149,8 @@ namespace Microsoft.DotNet.Cli.Utils
var commandPath = commandPackage.Library.Files
.First(f => Path.GetFileName(f) == commandName + FileNameSuffixes.DotNet.Exe);
resolutionStrategy = CommandResolutionStrategy.NugetPackage;
return Path.Combine(projectContext.PackagesDirectory, commandPackage.Path, commandPath);
}
@ -286,6 +312,10 @@ namespace Microsoft.DotNet.Cli.Utils
return this;
}
public CommandResolutionStrategy ResolutionStrategy { get; }
public string CommandName => _process.StartInfo.FileName;
private string FormatProcessInfo(ProcessStartInfo info)
{
if (string.IsNullOrWhiteSpace(info.Arguments))

View file

@ -0,0 +1,67 @@
// 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.IO;
using Microsoft.DotNet.ProjectModel;
using NuGet.Frameworks;
namespace Microsoft.DotNet.Cli.Utils
{
internal static class ProjectContextExtensions
{
public static string ProjectName(this ProjectContext context) => context.RootProject.Identity.Name;
public static string GetOutputPath(this ProjectContext context, string configuration, string currentOutputPath)
{
var outputPath = string.Empty;
if (string.IsNullOrEmpty(currentOutputPath))
{
outputPath = Path.Combine(
GetDefaultRootOutputPath(context, currentOutputPath),
Constants.BinDirectoryName,
configuration,
context.TargetFramework.GetTwoDigitShortFolderName());
}
else
{
outputPath = currentOutputPath;
}
return outputPath;
}
public static string GetIntermediateOutputPath(this ProjectContext context, string configuration, string intermediateOutputValue, string currentOutputPath)
{
var intermediateOutputPath = string.Empty;
if (string.IsNullOrEmpty(intermediateOutputValue))
{
intermediateOutputPath = Path.Combine(
GetDefaultRootOutputPath(context, currentOutputPath),
Constants.ObjDirectoryName,
configuration,
context.TargetFramework.GetTwoDigitShortFolderName());
}
else
{
intermediateOutputPath = intermediateOutputValue;
}
return intermediateOutputPath;
}
public static string GetDefaultRootOutputPath(ProjectContext context, string currentOutputPath)
{
string rootOutputPath = string.Empty;
if (string.IsNullOrEmpty(currentOutputPath))
{
rootOutputPath = context.ProjectFile.ProjectDirectory;
}
return rootOutputPath;
}
}
}

View 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 Microsoft.DotNet.Tools.Compiler;
namespace Microsoft.DotNet.Tools.Build
{
internal class BuilderCommandApp : CompilerCommandApp
{
private const string BuildProfileFlag = "--build-profile";
public bool BuildProfileValue => OptionHasValue(BuildProfileFlag);
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");
}
}
}

View file

@ -0,0 +1,253 @@
// 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.Utils;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Tools.Compiler;
using Microsoft.DotNet.ProjectModel.Utilities;
namespace Microsoft.DotNet.Tools.Build
{
// 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 BuilderCommandApp _args;
private readonly Dictionary<string, ProjectDescription> _dependencies;
private readonly string _outputPath;
private readonly string _intermediateOutputPath;
private readonly IncrementalPreconditions _preconditions;
public bool IsSafeForIncrementalCompilation => _preconditions.PreconditionsDetected();
public CompileContext(ProjectContext rootProject, BuilderCommandApp args)
{
_rootProject = rootProject;
_args = args;
// Set up Output Paths. They are unique per each CompileContext
// Todo: clone args and mutate the clone so the rest of this class does not have special treatment for output paths
_outputPath = _rootProject.GetOutputPath(_args.ConfigValue, _args.OutputValue);
_intermediateOutputPath = _rootProject.GetIntermediateOutputPath(_args.ConfigValue, _args.IntermediateValue, _args.OutputValue);
// Set up dependencies
_dependencies = GetProjectDependenciesWithSources(_rootProject, _args.ConfigValue);
//gather preconditions
_preconditions = GatherIncrementalPreconditions();
}
public bool Compile(bool incremental)
{
CreateOutputDirectories();
//compile dependencies
foreach (var dependency in Sort(_dependencies))
{
if (!InvokeCompileOnDependency(dependency, _outputPath, _intermediateOutputPath))
{
return false;
}
}
//compile project
var success = InvokeCompileOnRootProject(_outputPath, _intermediateOutputPath);
PrintSummary(success);
return success;
}
private void PrintSummary(bool success)
{
//todo: Ideally it's the builder's responsibility for adding the time elapsed. That way we avoid cross cutting display concerns between compile and build for printing time elapsed
if (success)
{
Reporter.Output.Write(" " + _preconditions.LogMessage());
Reporter.Output.WriteLine();
}
Reporter.Output.WriteLine();
}
private void CreateOutputDirectories()
{
Directory.CreateDirectory(_outputPath);
Directory.CreateDirectory(_intermediateOutputPath);
}
//todo make extension of ProjectContext?
//returns map with dependencies: string projectName -> ProjectDescription
private static Dictionary<string, ProjectDescription> GetProjectDependenciesWithSources(ProjectContext projectContext, string configuration)
{
var projects = new Dictionary<string, ProjectDescription>();
// Create the library exporter
var exporter = projectContext.CreateExporter(configuration);
// Gather exports for the project
var dependencies = exporter.GetDependencies().ToList();
// Build project references
foreach (var dependency in dependencies)
{
var projectDependency = dependency.Library as ProjectDescription;
if (projectDependency != null && projectDependency.Project.Files.SourceFiles.Any())
{
projects[projectDependency.Identity.Name] = projectDependency;
}
}
return projects;
}
private IncrementalPreconditions GatherIncrementalPreconditions()
{
var preconditions = new IncrementalPreconditions(_args.BuildProfileValue);
var projectsToCheck = GetProjectsToCheck();
foreach (var project in projectsToCheck)
{
CollectScriptPreconditions(project, preconditions);
CollectCompilerNamePreconditions(project, preconditions);
CheckPathProbing(project, preconditions);
}
return preconditions;
}
//check the entire project tree that needs to be compiled, duplicated for each framework
private List<ProjectContext> GetProjectsToCheck()
{
//include initial root project
var contextsToCheck = new List<ProjectContext>(1 + _dependencies.Count) {_rootProject};
//convert ProjectDescription to ProjectContext
var dependencyContexts = _dependencies.Select
(keyValuePair => ProjectContext.Create(keyValuePair.Value.Path, keyValuePair.Value.TargetFrameworkInfo.FrameworkName));
contextsToCheck.AddRange(dependencyContexts);
return contextsToCheck;
}
private void CheckPathProbing(ProjectContext project, IncrementalPreconditions preconditions)
{
var pathCommands = CompilerUtil.GetCommandsInvokedByCompile(project)
.Select(commandName => Command.Create(commandName, "", project.TargetFramework))
.Where(c => Command.CommandResolutionStrategy.Path.Equals(c.ResolutionStrategy));
foreach (var pathCommand in pathCommands)
{
preconditions.AddPathProbingPrecondition(project.ProjectName(), pathCommand.CommandName);
}
}
private void CollectCompilerNamePreconditions(ProjectContext project, IncrementalPreconditions preconditions)
{
var projectCompiler = CompilerUtil.ResolveCompilerName(project);
if (KnownCompilers.Any(knownCompiler => knownCompiler.Equals(projectCompiler, StringComparison.Ordinal)))
{
preconditions.AddUnknownCompilerPrecondition(project.ProjectName(), projectCompiler);
}
}
private void CollectScriptPreconditions(ProjectContext project, IncrementalPreconditions preconditions)
{
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, string outputPath, string intermediateOutputPath)
{
var compileResult = Command.Create("dotnet-compile",
$"--framework {projectDependency.Framework} " +
$"--configuration {_args.ConfigValue} " +
$"--output \"{outputPath}\" " +
$"--temp-output \"{intermediateOutputPath}\" " +
(_args.NoHostValue ? "--no-host " : string.Empty) +
$"\"{projectDependency.Project.ProjectDirectory}\"")
.ForwardStdOut()
.ForwardStdErr()
.Execute();
return compileResult.ExitCode == 0;
}
private bool InvokeCompileOnRootProject(string outputPath, string intermediateOutputPath)
{
//todo: add methods to CompilerCommandApp to generate the arg string
var compileResult = Command.Create("dotnet-compile",
$"--framework {_rootProject.TargetFramework} " +
$"--configuration {_args.ConfigValue} " +
$"--output \"{outputPath}\" " +
$"--temp-output \"{intermediateOutputPath}\" " +
(_args.NoHostValue ? "--no-host " : string.Empty) +
//nativeArgs
(_args.IsNativeValue ? "--native " : string.Empty) +
(_args.IsCppModeValue ? "--cpp " : string.Empty) +
(!string.IsNullOrWhiteSpace(_args.ArchValue) ? $"--arch {_args.ArchValue} " : string.Empty) +
(!string.IsNullOrWhiteSpace(_args.IlcArgsValue) ? $"--ilcargs \"{_args.IlcArgsValue}\" " : string.Empty) +
(!string.IsNullOrWhiteSpace(_args.IlcPathValue) ? $"--ilcpath \"{_args.IlcPathValue}\" " : string.Empty) +
(!string.IsNullOrWhiteSpace(_args.IlcSdkPathValue) ? $"--ilcsdkpath \"{_args.IlcSdkPathValue}\" " : string.Empty) +
$"\"{_rootProject.ProjectDirectory}\"")
.ForwardStdOut()
.ForwardStdErr()
.Execute();
return compileResult.ExitCode == 0;
}
private static ISet<ProjectDescription> Sort(Dictionary<string, ProjectDescription> projects)
{
var outputs = new HashSet<ProjectDescription>();
foreach (var pair in projects)
{
Sort(pair.Value, projects, outputs);
}
return outputs;
}
private static void Sort(ProjectDescription project, Dictionary<string, ProjectDescription> projects, ISet<ProjectDescription> outputs)
{
// Sorts projects in dependency order so that we only build them once per chain
foreach (var dependency in project.Dependencies)
{
ProjectDescription projectDependency;
if (projects.TryGetValue(dependency.Name, out projectDependency))
{
Sort(projectDependency, projects, outputs);
}
}
outputs.Add(project);
}
}
}

View file

@ -0,0 +1,79 @@
// 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 System.Text;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Tools.Build
{
internal class IncrementalPreconditions
{
private readonly List<string> _preconditions;
private readonly bool _isProfile;
public IncrementalPreconditions(bool isProfile)
{
_isProfile = isProfile;
_preconditions = new List<string>();
}
public void AddPrePostScriptPrecondition(string projectName, string scriptType)
{
_preconditions.Add($"[Pre / Post Scripts] Project {projectName} is using {scriptType} scripts.");
}
public void AddUnknownCompilerPrecondition(string projectName, string compilerName)
{
_preconditions.Add($"[Unknown Compiler] Project {projectName} is using unknown compiler {compilerName}.");
}
public void AddPathProbingPrecondition(string projectName, string commandName)
{
_preconditions.Add($"[PATH Probing] Project {projectName} is loading tool \"{commandName}\" from PATH");
}
public bool PreconditionsDetected()
{
return _preconditions.Any();
}
private string PreconditionsMessage()
{
var log = new StringBuilder();
log.AppendLine();
log.Append("Incremental compilation has been disabled due to the following project properties:");
foreach (var precondition in _preconditions)
{
log.AppendLine();
log.Append("\t" + precondition);
}
log.AppendLine();
log.AppendLine();
log.Append(
"Incremental compilation will be automatically enabled if the above mentioned project properties are not used. " +
"For more information on the properties and how to address them, please consult:\n" +
@"https://github.com/cdmihai/cli/wiki/Addressing-Incremental-Compilation-Warnings");
log.AppendLine();
log.AppendLine();
return log.ToString();
}
public string LogMessage()
{
if (PreconditionsDetected())
{
return _isProfile ? PreconditionsMessage().Yellow() : "(The compilation time can be improved. Run \"dotnet build --profile\" for more information)";
}
return "";
}
}
}

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>0a309227-a9d8-4ddf-88dd-326b57b04379</ProjectGuid>
<RootNamespace>Microsoft.DotNet.Tools.Build</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View file

@ -0,0 +1,45 @@
// 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.Cli.Utils;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Tools.Compiler;
namespace Microsoft.DotNet.Tools.Build
{
public class Program
{
public static int Main(string[] args)
{
DebugHelper.HandleDebugSwitch(ref args);
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");
return app.Execute(OnExecute, args);
}
catch (Exception ex)
{
#if DEBUG
Console.Error.WriteLine(ex);
#else
Console.Error.WriteLine(ex.Message);
#endif
return 1;
}
}
private static bool OnExecute(List<ProjectContext> contexts, CompilerCommandApp args)
{
var compileContexts = contexts.Select(context => new CompileContext(context, (BuilderCommandApp)args)).ToList();
var incrementalSafe = compileContexts.All(c => c.IsSafeForIncrementalCompilation);
return compileContexts.All(c => c.Compile(incrementalSafe));
}
}
}

View file

@ -0,0 +1,32 @@
dotnet-build
===========
**NAME**
dotnet-build -- Orchestrates the compilation of a project and all its dependencies.
**SYNOPSIS**
dotnet build[options]
**DESCRIPTION**
The build verb orchestrates the compilation of a project: it gathers the dependencies of a project and decides which to compile.
Users should invoke the Build verb when they want the entire dependency graph compiled, and Compile when they want only a specific project compiled.
Before any compilation begins, the build verb analyzes the project and its dependencies for incremental safety checks. If all checks clear out, then build proceeds with incremental compilation of the project and its dependencies. Otherwise it falls back to non-incremental compilation. Via a profile flag users can choose to receive additional information on how they can improve their build times.
All the projects in the dependency graph that need compilation must pass the following safety checks in order for the compilation process to be incremental:
- not use pre / post compile scripts
- not load compilation tools from PATH (e.g., resgen, compilers)
- use only known compilers (csc, vbc, fsc)
Please read the [documentation](https://github.com/dotnet/cli/blob/master/src/Microsoft.DotNet.Tools.Compiler/README.md) on Compile for details on compilation and project structure:
**Options**
Build inherits all the [Compile command line parameters](https://github.com/dotnet/cli/blob/master/src/Microsoft.DotNet.Tools.Compiler/README.md).
In addition Compile's parameters, Build adds the following flag:
--build-profile [exe | dynlib | lib]
Prints out the incremental safety checks that users need to address in order for incremental compilation to be automatically turned on.

View file

@ -0,0 +1,33 @@
{
"name": "dotnet-build",
"version": "1.0.0-*",
"compilationOptions": {
"emitEntryPoint": true
},
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23608",
"System.Linq": "4.0.1-rc2-23608",
"System.Reflection.Metadata": "1.1.0",
"Microsoft.DotNet.ProjectModel": "1.0.0-*",
"Microsoft.DotNet.Compiler.Common": "1.0.0-*",
"Microsoft.DotNet.Tools.Compiler": "1.0.0-*",
"Microsoft.DotNet.Cli.Utils": {
"type": "build",
"version": "1.0.0-*"
},
"Microsoft.Extensions.CommandLineUtils.Sources": {
"type": "build",
"version": "1.0.0-*"
}
},
"frameworks": {
"dnxcore50": { }
},
"scripts": {
"postcompile": [
"../../scripts/build/place-binary \"%compile:OutputDir%/%project:Name%.dll\"",
"../../scripts/build/place-binary \"%compile:OutputDir%/%project:Name%.pdb\""
]
}
}

View file

@ -0,0 +1,144 @@
// 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 Microsoft.Dnx.Runtime.Common.CommandLine;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectModel;
using NuGet.Frameworks;
using System.Linq;
//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 _intermediateOutputOption;
private CommandOption _frameworkOption;
private CommandOption _configurationOption;
private CommandOption _noHostOption;
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 OutputValue { get; set; }
public string IntermediateValue { get; set; }
public string ConfigValue { get; set; }
public bool NoHostValue { get; set; }
public bool IsNativeValue { get; set; }
public string ArchValue { get; set; }
public 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);
_intermediateOutputOption = _app.Option("-t|--temp-output <OUTPUT_DIR>", "Directory in which to place temporary outputs", CommandOptionType.SingleValue);
_frameworkOption = _app.Option("-f|--framework <FRAMEWORK>", "Compile a specific framework", CommandOptionType.MultipleValue);
_configurationOption = _app.Option("-c|--configuration <CONFIGURATION>", "Configuration under which to build", CommandOptionType.SingleValue);
_noHostOption = _app.Option("--no-host", "Set this to skip publishing a runtime host when building for CoreCLR", CommandOptionType.NoValue);
_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("--ilcargs <ARGS>", "Command line arguments to be passed directly to ILCompiler.", CommandOptionType.SingleValue);
_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(() =>
{
// Locate the project and get the name and full path
ProjectPathValue = _projectArgument.Value;
if (string.IsNullOrEmpty(ProjectPathValue))
{
ProjectPathValue = Directory.GetCurrentDirectory();
}
OutputValue = _outputOption.Value();
IntermediateValue = _intermediateOutputOption.Value();
ConfigValue = _configurationOption.Value() ?? Constants.DefaultConfiguration;
NoHostValue = _noHostOption.HasValue();
IsNativeValue = _nativeOption.HasValue();
ArchValue = _archOption.Value();
IlcArgsValue = _ilcArgsOption.Value();
IlcPathValue = _ilcPathOption.Value();
IlcSdkPathValue = _ilcSdkPathOption.Value();
AppDepSdkPathValue = _appDepSdkPathOption.Value();
IsCppModeValue = _cppModeOption.HasValue();
CppCompilerFlagsValue = _cppCompilerFlagsOption.Value();
// Load project contexts for each framework
var contexts = _frameworkOption.HasValue() ?
_frameworkOption.Values.Select(f => ProjectContext.Create(ProjectPathValue, NuGetFramework.Parse(f))) :
ProjectContext.CreateContextForEachFramework(ProjectPathValue);
var success = execute(contexts.ToList(), this);
return success ? 0 : 1;
});
return _app.Execute(args);
}
//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();
}
}
}

View file

@ -0,0 +1,121 @@
// 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.Cli.Utils;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli.Compiler.Common;
using Microsoft.DotNet.ProjectModel.Compilation;
using Microsoft.DotNet.Tools.Common;
//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 static class CompilerUtil
{
public static string ResolveCompilerName(ProjectContext context)
{
var compilerName = context.ProjectFile.CompilerName;
compilerName = compilerName ?? "csc";
return compilerName;
}
public struct NonCultureResgenIO
{
public readonly string InputFile;
public readonly string MetadataName;
//is non-null only when resgen needs to be invoked (inputFile is .resx)
public readonly string OutputFile;
public NonCultureResgenIO(string inputFile, string outputFile, string metadataName)
{
InputFile = inputFile;
OutputFile = outputFile;
MetadataName = metadataName;
}
}
//used in incremental compilation
public static List<NonCultureResgenIO> GetNonCultureResources(Project project, string intermediateOutputPath)
{
return
(from resourceFile in project.Files.ResourceFiles
let inputFile = resourceFile.Key
where string.IsNullOrEmpty(ResourceUtility.GetResourceCultureName(inputFile))
let metadataName = GetResourceFileMetadataName(project, resourceFile)
let outputFile = ResourceUtility.IsResxFile(inputFile) ? Path.Combine(intermediateOutputPath, metadataName) : null
select new NonCultureResgenIO(inputFile, outputFile, metadataName)
).ToList();
}
public struct CultureResgenIO
{
public readonly string Culture;
public readonly Dictionary<string, string> InputFileToMetadata;
public readonly string OutputFile;
public CultureResgenIO(string culture, Dictionary<string, string> inputFileToMetadata, string outputFile)
{
Culture = culture;
InputFileToMetadata = inputFileToMetadata;
OutputFile = outputFile;
}
}
//used in incremental compilation
public static List<CultureResgenIO> GetCultureResources(Project project, string outputPath)
{
return
(from resourceFileGroup in project.Files.ResourceFiles.GroupBy(resourceFile => ResourceUtility.GetResourceCultureName(resourceFile.Key))
let culture = resourceFileGroup.Key
where !string.IsNullOrEmpty(culture)
let inputFileToMetadata = resourceFileGroup.ToDictionary(r => r.Key, r => GetResourceFileMetadataName(project, r))
let resourceOutputPath = Path.Combine(outputPath, culture)
let outputFile = Path.Combine(resourceOutputPath, project.Name + ".resources.dll")
select new CultureResgenIO(culture, inputFileToMetadata, outputFile)
).ToList();
}
//used in incremental compilation
public static IList<string> GetReferencePathsForCultureResgen(List<LibraryExport> dependencies)
{
return dependencies.SelectMany(libraryExport => libraryExport.CompilationAssemblies).Select(r => r.ResolvedPath).ToList();
}
public static string GetResourceFileMetadataName(Project project, KeyValuePair<string, string> resourceFile)
{
string resourceName = null;
string rootNamespace = null;
string root = PathUtility.EnsureTrailingSlash(project.ProjectDirectory);
string resourcePath = resourceFile.Key;
if (string.IsNullOrEmpty(resourceFile.Value))
{
// No logical name, so use the file name
resourceName = ResourceUtility.GetResourceName(root, resourcePath);
rootNamespace = project.Name;
}
else
{
resourceName = ResourceManifestName.EnsureResourceExtension(resourceFile.Value, resourcePath);
rootNamespace = null;
}
var name = ResourceManifestName.CreateManifestName(resourceName, rootNamespace);
return name;
}
public static IEnumerable<string> GetCommandsInvokedByCompile(ProjectContext project)
{
return new List<string> {ResolveCompilerName(project)};
}
}
}

View file

@ -22,76 +22,15 @@ namespace Microsoft.DotNet.Tools.Compiler
{
public class Program
{
public static int Main(string[] args)
{
DebugHelper.HandleDebugSwitch(ref args);
var app = new CommandLineApplication();
app.Name = "dotnet compile";
app.FullName = ".NET Compiler";
app.Description = "Compiler for the .NET Platform";
app.HelpOption("-h|--help");
var output = app.Option("-o|--output <OUTPUT_DIR>", "Directory in which to place outputs", CommandOptionType.SingleValue);
var intermediateOutput = app.Option("-t|--temp-output <OUTPUT_DIR>", "Directory in which to place temporary outputs", CommandOptionType.SingleValue);
var framework = app.Option("-f|--framework <FRAMEWORK>", "Compile a specific framework", CommandOptionType.MultipleValue);
var configuration = app.Option("-c|--configuration <CONFIGURATION>", "Configuration under which to build", CommandOptionType.SingleValue);
var noProjectDependencies = app.Option("--no-project-dependencies", "Skips building project references.", CommandOptionType.NoValue);
var noHost = app.Option("--no-host", "Set this to skip publishing a runtime host when building for CoreCLR", CommandOptionType.NoValue);
var project = 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
var native = app.Option("-n|--native", "Compiles source to native machine code.", CommandOptionType.NoValue);
var arch = app.Option("-a|--arch <ARCH>", "The architecture for which to compile. x64 only currently supported.", CommandOptionType.SingleValue);
var ilcArgs = app.Option("--ilcargs <ARGS>", "Command line arguments to be passed directly to ILCompiler.", CommandOptionType.SingleValue);
var ilcPath = app.Option("--ilcpath <PATH>", "Path to the folder containing custom built ILCompiler.", CommandOptionType.SingleValue);
var ilcSdkPath = app.Option("--ilcsdkpath <PATH>", "Path to the folder containing custom built ILCompiler SDK.", CommandOptionType.SingleValue);
var appDepSdkPath = app.Option("--appdepsdkpath <PATH>", "Path to the folder containing ILCompiler application dependencies.", CommandOptionType.SingleValue);
var cppMode = app.Option("--cpp", "Flag to do native compilation with C++ code generator.", CommandOptionType.NoValue);
var cppCompilerFlags = app.Option("--cppcompilerflags <flags>", "Additional flags to be passed to the native compiler.", CommandOptionType.SingleValue);
app.OnExecute(() =>
{
// Locate the project and get the name and full path
var path = project.Value;
if (string.IsNullOrEmpty(path))
{
path = Directory.GetCurrentDirectory();
}
var buildProjectReferences = !noProjectDependencies.HasValue();
var isNative = native.HasValue();
var isCppMode = cppMode.HasValue();
var archValue = arch.Value();
var ilcArgsValue = ilcArgs.Value();
var ilcPathValue = ilcPath.Value();
var ilcSdkPathValue = ilcSdkPath.Value();
var appDepSdkPathValue = appDepSdkPath.Value();
var configValue = configuration.Value() ?? Constants.DefaultConfiguration;
var outputValue = output.Value();
var intermediateValue = intermediateOutput.Value();
var cppCompilerFlagsValue = cppCompilerFlags.Value();
// Load project contexts for each framework and compile them
bool success = true;
var contexts = framework.HasValue() ?
framework.Values.Select(f => ProjectContext.Create(path, NuGetFramework.Parse(f))) :
ProjectContext.CreateContextForEachFramework(path);
foreach (var context in contexts)
{
success &= Compile(context, configValue, outputValue, intermediateValue, buildProjectReferences, noHost.HasValue());
if (isNative && success)
{
success &= CompileNative(context, configValue, outputValue, buildProjectReferences, intermediateValue, archValue, ilcArgsValue, ilcPathValue, ilcSdkPathValue, appDepSdkPathValue, isCppMode, cppCompilerFlagsValue);
}
}
return success ? 0 : 1;
});
try
{
return app.Execute(args);
var compilerCommandArgs = new CompilerCommandApp("dotnet compile", ".NET Compiler", "Compiler for the .NET Platform");
return compilerCommandArgs.Execute(OnExecute, args);
}
catch (Exception ex)
{
@ -104,31 +43,36 @@ namespace Microsoft.DotNet.Tools.Compiler
}
}
private static bool OnExecute(List<ProjectContext> contexts, CompilerCommandApp args)
{
var success = true;
foreach (var context in contexts)
{
success &= CompileProject(context, args);
if (args.IsNativeValue && success)
{
success &= CompileNative(context, args);
}
}
return success;
}
private static bool CompileNative(
ProjectContext context,
string configuration,
string outputOptionValue,
bool buildProjectReferences,
string intermediateOutputValue,
string archValue,
string ilcArgsValue,
string ilcPathValue,
string ilcSdkPathValue,
string appDepSdkPathValue,
bool isCppMode,
string cppCompilerFlagsValue)
CompilerCommandApp args)
{
var outputPath = GetOutputPath(context, configuration, outputOptionValue);
var nativeOutputPath = Path.Combine(GetOutputPath(context, configuration, outputOptionValue), "native");
var outputPath = context.GetOutputPath(args.ConfigValue, args.OutputValue);
var nativeOutputPath = Path.Combine(outputPath, "native");
var intermediateOutputPath =
GetIntermediateOutputPath(context, configuration, intermediateOutputValue, outputOptionValue);
context.GetIntermediateOutputPath(args.ConfigValue, args.IntermediateValue, outputPath);
Directory.CreateDirectory(nativeOutputPath);
Directory.CreateDirectory(intermediateOutputPath);
var compilationOptions = context.ProjectFile.GetCompilerOptions(context.TargetFramework, configuration);
var compilationOptions = context.ProjectFile.GetCompilerOptions(context.TargetFramework, args.ConfigValue);
var managedOutput =
GetProjectOutput(context.ProjectFile, context.TargetFramework, configuration, outputPath);
GetProjectOutput(context.ProjectFile, context.TargetFramework, args.ConfigValue, outputPath);
var nativeArgs = new List<string>();
@ -136,58 +80,58 @@ namespace Microsoft.DotNet.Tools.Compiler
nativeArgs.Add($"{managedOutput}");
// ILC Args
if (!string.IsNullOrWhiteSpace(ilcArgsValue))
if (!string.IsNullOrWhiteSpace(args.IlcArgsValue))
{
nativeArgs.Add("--ilcargs");
nativeArgs.Add($"{ilcArgsValue}");
nativeArgs.Add($"{args.IlcArgsValue}");
}
// ILC Path
if (!string.IsNullOrWhiteSpace(ilcPathValue))
if (!string.IsNullOrWhiteSpace(args.IlcPathValue))
{
nativeArgs.Add("--ilcpath");
nativeArgs.Add(ilcPathValue);
nativeArgs.Add(args.IlcPathValue);
}
// ILC SDK Path
if (!string.IsNullOrWhiteSpace(ilcSdkPathValue))
if (!string.IsNullOrWhiteSpace(args.IlcSdkPathValue))
{
nativeArgs.Add("--ilcsdkpath");
nativeArgs.Add(ilcSdkPathValue);
nativeArgs.Add(args.IlcSdkPathValue);
}
// AppDep SDK Path
if (!string.IsNullOrWhiteSpace(appDepSdkPathValue))
if (!string.IsNullOrWhiteSpace(args.AppDepSdkPathValue))
{
nativeArgs.Add("--appdepsdk");
nativeArgs.Add(appDepSdkPathValue);
nativeArgs.Add(args.AppDepSdkPathValue);
}
// CodeGen Mode
if(isCppMode)
if(args.IsCppModeValue)
{
nativeArgs.Add("--mode");
nativeArgs.Add("cpp");
}
if (!string.IsNullOrWhiteSpace(cppCompilerFlagsValue))
if (!string.IsNullOrWhiteSpace(args.CppCompilerFlagsValue))
{
nativeArgs.Add("--cppcompilerflags");
nativeArgs.Add(cppCompilerFlagsValue);
nativeArgs.Add(args.CppCompilerFlagsValue);
}
// Configuration
if (configuration != null)
if (args.ConfigValue != null)
{
nativeArgs.Add("--configuration");
nativeArgs.Add(configuration);
nativeArgs.Add(args.ConfigValue);
}
// Architecture
if (archValue != null)
if (args.ArchValue != null)
{
nativeArgs.Add("--arch");
nativeArgs.Add(archValue);
nativeArgs.Add(args.ArchValue);
}
// Intermediate Path
@ -214,65 +158,21 @@ namespace Microsoft.DotNet.Tools.Compiler
return result.ExitCode == 0;
}
private static bool Compile(ProjectContext context, string configuration, string outputOptionValue, string intermediateOutputValue, bool buildProjectReferences, bool noHost)
private static bool CompileProject(ProjectContext context, CompilerCommandApp args)
{
// Set up Output Paths
string outputPath = GetOutputPath(context, configuration, outputOptionValue);
string intermediateOutputPath = GetIntermediateOutputPath(context, configuration, intermediateOutputValue, outputOptionValue);
string outputPath = context.GetOutputPath(args.ConfigValue, args.OutputValue);
string intermediateOutputPath = context.GetIntermediateOutputPath(args.ConfigValue, args.IntermediateValue, outputPath);
Directory.CreateDirectory(outputPath);
Directory.CreateDirectory(intermediateOutputPath);
// Create the library exporter
var exporter = context.CreateExporter(configuration);
var exporter = context.CreateExporter(args.ConfigValue);
// Gather exports for the project
var dependencies = exporter.GetDependencies().ToList();
if (buildProjectReferences)
{
var projects = new Dictionary<string, ProjectDescription>();
// Build project references
foreach (var dependency in dependencies)
{
var projectDependency = dependency.Library as ProjectDescription;
if (projectDependency != null && projectDependency.Project.Files.SourceFiles.Any())
{
projects[projectDependency.Identity.Name] = projectDependency;
}
}
foreach (var projectDependency in Sort(projects))
{
// Skip compiling project dependencies since we've already figured out the build order
var compileResult = Command.Create("dotnet-compile",
$"--framework {projectDependency.Framework} " +
$"--configuration {configuration} " +
$"--output \"{outputPath}\" " +
$"--temp-output \"{intermediateOutputPath}\" " +
"--no-project-dependencies " +
(noHost ? "--no-host " : string.Empty) +
$"\"{projectDependency.Project.ProjectDirectory}\"")
.ForwardStdOut()
.ForwardStdErr()
.Execute();
if (compileResult.ExitCode != 0)
{
return false;
}
}
projects.Clear();
}
return CompileProject(context, configuration, outputPath, intermediateOutputPath, dependencies, noHost);
}
private static bool CompileProject(ProjectContext context, string configuration, string outputPath, string intermediateOutputPath, List<LibraryExport> dependencies, bool noHost)
{
Reporter.Output.WriteLine($"Compiling {context.RootProject.Identity.Name.Yellow()} for {context.TargetFramework.DotNetFrameworkName.Yellow()}");
var sw = Stopwatch.StartNew();
@ -300,7 +200,7 @@ namespace Microsoft.DotNet.Tools.Compiler
}
// Get compilation options
var outputName = GetProjectOutput(context.ProjectFile, context.TargetFramework, configuration, outputPath);
var outputName = GetProjectOutput(context.ProjectFile, context.TargetFramework, args.ConfigValue, outputPath);
// Assemble args
var compilerArgs = new List<string>()
@ -309,7 +209,7 @@ namespace Microsoft.DotNet.Tools.Compiler
$"--out:{outputName}"
};
var compilationOptions = context.ProjectFile.GetCompilerOptions(context.TargetFramework, configuration);
var compilationOptions = context.ProjectFile.GetCompilerOptions(context.TargetFramework, args.ConfigValue);
// Path to strong naming key in environment variable overrides path in project.json
var environmentKeyFile = Environment.GetEnvironmentVariable(EnvironmentNames.StrongNameKeyFile);
@ -340,7 +240,7 @@ namespace Microsoft.DotNet.Tools.Compiler
{
if (projectDependency.Project.Files.SourceFiles.Any())
{
var projectOutputPath = GetProjectOutput(projectDependency.Project, projectDependency.Framework, configuration, outputPath);
var projectOutputPath = GetProjectOutput(projectDependency.Project, projectDependency.Framework, args.ConfigValue, outputPath);
references.Add(projectOutputPath);
}
}
@ -355,7 +255,7 @@ namespace Microsoft.DotNet.Tools.Compiler
compilerArgs.AddRange(references.Select(r => $"--reference:{r}"));
var runtimeContext = ProjectContext.Create(context.ProjectDirectory, context.TargetFramework, new[] { RuntimeIdentifier.Current });
var libraryExporter = runtimeContext.CreateExporter(configuration);
var libraryExporter = runtimeContext.CreateExporter(args.ConfigValue);
if (compilationOptions.PreserveCompilationContext == true)
{
@ -384,7 +284,7 @@ namespace Microsoft.DotNet.Tools.Compiler
}
}
if (!AddResources(context.ProjectFile, compilerArgs, intermediateOutputPath))
if (!AddNonCultureResources(context.ProjectFile, compilerArgs, intermediateOutputPath))
{
return false;
}
@ -392,8 +292,7 @@ namespace Microsoft.DotNet.Tools.Compiler
var sourceFiles = context.ProjectFile.Files.SourceFiles;
compilerArgs.AddRange(sourceFiles);
var compilerName = context.ProjectFile.CompilerName;
compilerName = compilerName ?? "csc";
var compilerName = CompilerUtil.ResolveCompilerName(context);
// Write RSP file
var rsp = Path.Combine(intermediateOutputPath, $"dotnet-compile.{context.ProjectFile.Name}.rsp");
@ -403,7 +302,7 @@ namespace Microsoft.DotNet.Tools.Compiler
var contextVariables = new Dictionary<string, string>()
{
{ "compile:TargetFramework", context.TargetFramework.DotNetFrameworkName },
{ "compile:Configuration", configuration },
{ "compile:Configuration", args.ConfigValue },
{ "compile:OutputFile", outputName },
{ "compile:OutputDir", outputPath },
{ "compile:ResponseFile", rsp }
@ -444,10 +343,10 @@ namespace Microsoft.DotNet.Tools.Compiler
if (success)
{
success &= GenerateResourceAssemblies(context.ProjectFile, dependencies, outputPath, configuration);
success &= GenerateCultureResourceAssemblies(context.ProjectFile, dependencies, outputPath);
}
if (success && !noHost && compilationOptions.EmitEntryPoint.GetValueOrDefault())
if (success && !args.NoHostValue && compilationOptions.EmitEntryPoint.GetValueOrDefault())
{
MakeRunnable(runtimeContext,
outputPath,
@ -481,58 +380,6 @@ namespace Microsoft.DotNet.Tools.Compiler
return Path.Combine(outputPath, project.Name + outputExtension);
}
private static string GetOutputPath(ProjectContext context, string configuration, string outputOptionValue)
{
var outputPath = string.Empty;
if (string.IsNullOrEmpty(outputOptionValue))
{
outputPath = Path.Combine(
GetDefaultRootOutputPath(context, outputOptionValue),
Constants.BinDirectoryName,
configuration,
context.TargetFramework.GetTwoDigitShortFolderName());
}
else
{
outputPath = outputOptionValue;
}
return outputPath;
}
private static string GetIntermediateOutputPath(ProjectContext context, string configuration, string intermediateOutputValue, string outputOptionValue)
{
var intermediateOutputPath = string.Empty;
if (string.IsNullOrEmpty(intermediateOutputValue))
{
intermediateOutputPath = Path.Combine(
GetDefaultRootOutputPath(context, outputOptionValue),
Constants.ObjDirectoryName,
configuration,
context.TargetFramework.GetTwoDigitShortFolderName());
}
else
{
intermediateOutputPath = intermediateOutputValue;
}
return intermediateOutputPath;
}
private static string GetDefaultRootOutputPath(ProjectContext context, string outputOptionValue)
{
string rootOutputPath = string.Empty;
if (string.IsNullOrEmpty(outputOptionValue))
{
rootOutputPath = context.ProjectFile.ProjectDirectory;
}
return rootOutputPath;
}
private static void CleanOrCreateDirectory(string path)
{
if (Directory.Exists(path))
@ -686,147 +533,79 @@ namespace Microsoft.DotNet.Tools.Compiler
Reporter.Output.WriteLine();
Reporter.Output.WriteLine($"Time elapsed {sw.Elapsed}");
Reporter.Output.WriteLine();
Reporter.Output.Write($"Time elapsed {sw.Elapsed}");
return success;
}
private static bool AddResources(Project project, List<string> compilerArgs, string intermediateOutputPath)
private static bool AddNonCultureResources(Project project, List<string> compilerArgs, string intermediateOutputPath)
{
var resgenFiles = CompilerUtil.GetNonCultureResources(project, intermediateOutputPath);
foreach (var resourceFile in project.Files.ResourceFiles)
foreach (var resgenFile in resgenFiles)
{
var resourcePath = resourceFile.Key;
if (!string.IsNullOrEmpty(ResourceUtility.GetResourceCultureName(resourcePath)))
if (ResourceUtility.IsResxFile(resgenFile.InputFile))
{
// Include only "neutral" resources into main assembly
continue;
var result =
Command.Create("dotnet-resgen",
$"\"{resgenFile.InputFile}\" -o \"{resgenFile.OutputFile}\" -v \"{project.Version.Version}\"")
.ForwardStdErr()
.ForwardStdOut()
.Execute();
if (result.ExitCode != 0)
{
return false;
}
compilerArgs.Add($"--resource:\"{resgenFile.OutputFile}\",{Path.GetFileName(resgenFile.MetadataName)}");
}
else
{
compilerArgs.Add($"--resource:\"{resgenFile.InputFile}\",{Path.GetFileName(resgenFile.MetadataName)}");
}
}
return true;
}
private static bool GenerateCultureResourceAssemblies(Project project, List<LibraryExport> dependencies, string outputPath)
{
var referencePaths = CompilerUtil.GetReferencePathsForCultureResgen(dependencies);
var resgenReferenceArgs = referencePaths.Select(referencePath => $"-r \"{referencePath}\"").ToList();
var cultureResgenFiles = CompilerUtil.GetCultureResources(project, outputPath);
foreach (var resgenFile in cultureResgenFiles)
{
var resourceOutputPath = Path.GetDirectoryName(resgenFile.OutputFile);
if (!Directory.Exists(resourceOutputPath))
{
Directory.CreateDirectory(resourceOutputPath);
}
var name = GetResourceFileMetadataName(project, resourceFile);
var arguments = new List<string>();
var fileName = resourcePath;
arguments.AddRange(resgenReferenceArgs);
arguments.Add($"-o \"{resgenFile.OutputFile}\"");
arguments.Add($"-c {resgenFile.Culture}");
arguments.Add($"-v {project.Version.Version}");
arguments.AddRange(resgenFile.InputFileToMetadata.Select(fileToMetadata => $"\"{fileToMetadata.Key}\",{fileToMetadata.Value}"));
if (ResourceUtility.IsResxFile(fileName))
{
// {file}.resx -> {file}.resources
var resourcesFile = Path.Combine(intermediateOutputPath, name);
var result = Command.Create("dotnet-resgen", $"\"{fileName}\" -o \"{resourcesFile}\" -v \"{project.Version.Version}\"")
var result = Command.Create("dotnet-resgen", arguments)
.ForwardStdErr()
.ForwardStdOut()
.Execute();
if (result.ExitCode != 0)
{
return false;
}
// Use this as the resource name instead
fileName = resourcesFile;
if (result.ExitCode != 0)
{
return false;
}
compilerArgs.Add($"--resource:\"{fileName}\",{name}");
}
return true;
}
private static string GetResourceFileMetadataName(Project project, KeyValuePair<string, string> resourceFile)
{
string resourceName = null;
string rootNamespace = null;
string root = PathUtility.EnsureTrailingSlash(project.ProjectDirectory);
string resourcePath = resourceFile.Key;
if (string.IsNullOrEmpty(resourceFile.Value))
{
// No logical name, so use the file name
resourceName = ResourceUtility.GetResourceName(root, resourcePath);
rootNamespace = project.Name;
}
else
{
resourceName = ResourceManifestName.EnsureResourceExtension(resourceFile.Value, resourcePath);
rootNamespace = null;
}
var name = ResourceManifestName.CreateManifestName(resourceName, rootNamespace);
return name;
}
private static bool GenerateResourceAssemblies(Project project, List<LibraryExport> dependencies, string outputPath, string configuration)
{
var references = dependencies.SelectMany(libraryExport => libraryExport.CompilationAssemblies);
foreach (var resourceFileGroup in project.Files.ResourceFiles.GroupBy(file => ResourceUtility.GetResourceCultureName(file.Key)))
{
var culture = resourceFileGroup.Key;
if (!string.IsNullOrEmpty(culture))
{
var resourceOutputPath = Path.Combine(outputPath, culture);
if (!Directory.Exists(resourceOutputPath))
{
Directory.CreateDirectory(resourceOutputPath);
}
var resourceOuputFile = Path.Combine(resourceOutputPath, project.Name + ".resources.dll");
var arguments = new List<string>();
arguments.AddRange(references.Select(r => $"-r \"{r.ResolvedPath}\""));
arguments.Add($"-o \"{resourceOuputFile}\"");
arguments.Add($"-c {culture}");
arguments.Add($"-v {project.Version.Version}");
foreach (var resourceFile in resourceFileGroup)
{
var metadataName = GetResourceFileMetadataName(project, resourceFile);
arguments.Add($"\"{resourceFile.Key}\",{metadataName}");
}
var result = Command.Create("dotnet-resgen", arguments)
.ForwardStdErr()
.ForwardStdOut()
.Execute();
if (result.ExitCode != 0)
{
return false;
}
}
}
return true;
}
private static ISet<ProjectDescription> Sort(Dictionary<string, ProjectDescription> projects)
{
var outputs = new HashSet<ProjectDescription>();
foreach (var pair in projects)
{
Sort(pair.Value, projects, outputs);
}
return outputs;
}
private static void Sort(ProjectDescription project, Dictionary<string, ProjectDescription> projects, ISet<ProjectDescription> outputs)
{
// Sorts projects in dependency order so that we only build them once per chain
foreach (var dependency in project.Dependencies)
{
ProjectDescription projectDependency;
if (projects.TryGetValue(dependency.Name, out projectDependency))
{
Sort(projectDependency, projects, outputs);
}
}
outputs.Add(project);
}
private static DiagnosticMessage ParseDiagnostic(string projectRootPath, string line)
{
var error = CanonicalError.Parse(line);

View file

@ -2,13 +2,15 @@ dotnet-compile
===========
**NAME**
dotnet-compile -- Compiles source files to a binary format and saves to a target file.
dotnet-compile -- Compiles source files for a single project to a binary format and saves to a target file.
**SYNOPSIS**
dotnet compile [options]
**DESCRIPTION**
The compile command compiles source files to a binary file, either IL byte code or native machine code, depending on the options provided. The default option is compilation to IL byte code, but may change in the future.
The compile command compiles source files from a single project to a binary file, either IL byte code or native machine code, depending on the options provided. The default option is compilation to IL byte code, but may change in the future.
Users who want to benefit from incremental builds and who want to compile both the project and its dependencies should use the Build command.
The default IL [--il] output is a PE32 exe [exe], with the default extension of ".exe" on all OSes. The exe must include a public static void or public static int main entry point, or it is an error. The dll [dll] output option has the default extension of ".dll".
@ -48,4 +50,4 @@ Compiles source to IL byte code, which is (typically) portable across machine ty
Specifies the filename to be used. It is an error not to specify an output filename. If no extension is provided, the default one is provided for the output type.
-v, --verbose
Prints verbose logging information, to follow the flow of execution of the command.
Prints verbose logging information, to follow the flow of execution of the command.

View file

@ -101,7 +101,7 @@ namespace Microsoft.DotNet.Tools.Compiler
argsBuilder.Append($" \"{path}\"");
var result = Command.Create("dotnet-compile", argsBuilder.ToString())
var result = Command.Create("dotnet-build", argsBuilder.ToString())
.ForwardStdOut()
.ForwardStdErr()
.Execute();

View file

@ -119,7 +119,7 @@ namespace Microsoft.DotNet.Tools.Publish
}
// Compile the project (and transitively, all it's dependencies)
var result = Command.Create("dotnet-compile",
var result = Command.Create("dotnet-build",
$"--framework \"{context.TargetFramework.DotNetFrameworkName}\" " +
$"--output \"{outputPath}\" " +
$"--configuration \"{configuration}\" " +

View file

@ -90,7 +90,7 @@ namespace Microsoft.DotNet.Tools.Run
var tempDir = Path.Combine(_context.ProjectDirectory, "bin", ".dotnetrun", Guid.NewGuid().ToString("N"));
// Compile to that directory
var result = Command.Create($"dotnet-compile", $"--output \"{tempDir}\" --temp-output \"{tempDir}\" --framework \"{_context.TargetFramework}\" --configuration \"{Configuration}\" {_context.ProjectFile.ProjectDirectory}")
var result = Command.Create($"dotnet-build", $"--output \"{tempDir}\" --temp-output \"{tempDir}\" --framework \"{_context.TargetFramework}\" --configuration \"{Configuration}\" {_context.ProjectFile.ProjectDirectory}")
.ForwardStdOut(onlyIfVerbose: true)
.ForwardStdErr()
.Execute();

View file

@ -46,7 +46,7 @@ namespace ConsoleApplication
{
TestSetup();
TestRunCommand("dotnet", $"compile -o {OutputDirectory}");
TestRunCommand("dotnet", $"build -o {OutputDirectory}");
TestOutputExecutable(OutputDirectory);
}
@ -60,7 +60,7 @@ namespace ConsoleApplication
TestSetup();
TestRunCommand("dotnet", $"compile --native -o {OutputDirectory}");
TestRunCommand("dotnet", $"build --native -o {OutputDirectory}");
var nativeOut = Path.Combine(OutputDirectory, "native");
TestOutputExecutable(nativeOut);
@ -71,7 +71,7 @@ namespace ConsoleApplication
{
TestSetup();
TestRunCommand("dotnet", $"compile --native --cpp -o {OutputDirectory}");
TestRunCommand("dotnet", $"build --native --cpp -o {OutputDirectory}");
var nativeOut = Path.Combine(OutputDirectory, "native");
TestOutputExecutable(nativeOut);