2015-12-10 13:06:33 -08:00
// 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 ;
2016-01-29 18:21:37 -08:00
using Microsoft.Dotnet.Cli.Compiler.Common ;
2016-01-27 16:20:26 -08:00
using Microsoft.DotNet.Cli.Compiler.Common ;
2015-12-10 13:06:33 -08:00
using Microsoft.DotNet.Cli.Utils ;
using Microsoft.DotNet.ProjectModel ;
using Microsoft.DotNet.ProjectModel.Utilities ;
2016-01-27 16:20:26 -08:00
using Microsoft.DotNet.Tools.Compiler ;
2016-01-29 18:21:37 -08:00
using Microsoft.Extensions.PlatformAbstractions ;
2015-12-10 13:06:33 -08:00
namespace Microsoft.DotNet.Tools.Build
{
2015-12-21 10:42:41 -08:00
// todo: Convert CompileContext into a DAG of dependencies: if a node needs recompilation, the entire path up to root needs compilation
2015-12-10 13:06:33 -08:00
// 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 ;
2016-02-05 15:29:34 -08:00
private readonly ProjectDependenciesFacade _rootProjectDependencies ;
2015-12-10 13:06:33 -08:00
private readonly BuilderCommandApp _args ;
private readonly IncrementalPreconditions _preconditions ;
2015-12-21 10:42:41 -08:00
public bool IsSafeForIncrementalCompilation = > ! _preconditions . PreconditionsDetected ( ) ;
2015-12-10 13:06:33 -08:00
public CompileContext ( ProjectContext rootProject , BuilderCommandApp args )
{
_rootProject = rootProject ;
2015-12-21 10:42:41 -08:00
2016-03-09 11:36:16 -08:00
// Cleaner to clone the args and mutate the clone than have separate CompileContext fields for mutated args
2015-12-21 10:42:41 -08:00
// and then reasoning which ones to get from args and which ones from fields.
2016-01-26 06:39:13 -08:00
_args = ( BuilderCommandApp ) args . ShallowCopy ( ) ;
2015-12-10 13:06:33 -08:00
// Set up dependencies
2016-02-05 15:29:34 -08:00
_rootProjectDependencies = new ProjectDependenciesFacade ( _rootProject , _args . ConfigValue ) ;
2015-12-10 13:06:33 -08:00
2015-12-21 10:42:41 -08:00
// gather preconditions
2015-12-10 13:06:33 -08:00
_preconditions = GatherIncrementalPreconditions ( ) ;
}
public bool Compile ( bool incremental )
{
CreateOutputDirectories ( ) ;
2016-03-01 17:42:44 -08:00
return CompileDependencies ( incremental ) & & CompileRootProject ( incremental ) ;
2016-02-05 15:29:34 -08:00
}
2015-12-10 13:06:33 -08:00
2016-02-05 15:29:34 -08:00
private bool CompileRootProject ( bool incremental )
{
2016-03-03 22:57:43 -08:00
try
2015-12-21 10:42:41 -08:00
{
2016-03-03 22:57:43 -08:00
if ( incremental & & ! NeedsRebuilding ( _rootProject , _rootProjectDependencies ) )
{
return true ;
}
2015-12-21 10:42:41 -08:00
2016-03-03 22:57:43 -08:00
var success = InvokeCompileOnRootProject ( ) ;
2015-12-10 13:06:33 -08:00
2016-03-03 22:57:43 -08:00
PrintSummary ( success ) ;
2015-12-10 13:06:33 -08:00
2016-03-03 22:57:43 -08:00
return success ;
}
finally
{
StampProjectWithSDKVersion ( _rootProject ) ;
}
2015-12-10 13:06:33 -08:00
}
2016-03-01 17:42:44 -08:00
private bool CompileDependencies ( bool incremental )
2016-02-05 15:29:34 -08:00
{
if ( _args . ShouldSkipDependencies )
{
return true ;
}
foreach ( var dependency in Sort ( _rootProjectDependencies . ProjectDependenciesWithSources ) )
{
2016-03-03 22:57:43 -08:00
var dependencyProjectContext = ProjectContext . Create ( dependency . Path , dependency . Framework , new [ ] { _rootProject . RuntimeIdentifier } ) ;
try
2016-02-05 15:29:34 -08:00
{
2016-03-03 22:57:43 -08:00
if ( incremental & & ! NeedsRebuilding ( dependencyProjectContext , new ProjectDependenciesFacade ( dependencyProjectContext , _args . ConfigValue ) ) )
{
continue ;
}
if ( ! InvokeCompileOnDependency ( dependency ) )
{
return false ;
}
2016-02-05 15:29:34 -08:00
}
2016-03-03 22:57:43 -08:00
finally
2016-02-05 15:29:34 -08:00
{
2016-03-03 22:57:43 -08:00
StampProjectWithSDKVersion ( dependencyProjectContext ) ;
2016-02-05 15:29:34 -08:00
}
}
return true ;
}
private bool NeedsRebuilding ( ProjectContext project , ProjectDependenciesFacade dependencies )
2016-01-26 06:39:13 -08:00
{
2016-03-03 22:57:43 -08:00
if ( CLIChangedSinceLastCompilation ( project ) )
{
Reporter . Output . WriteLine ( $"Project {project.GetDisplayName()} will be compiled because the CLI changed" ) ;
return true ;
}
2016-02-16 08:42:00 -08:00
var compilerIO = GetCompileIO ( project , dependencies ) ;
2015-12-21 10:42:41 -08:00
// rebuild if empty inputs / outputs
if ( ! ( compilerIO . Outputs . Any ( ) & & compilerIO . Inputs . Any ( ) ) )
2015-12-10 13:06:33 -08:00
{
2016-01-26 06:39:13 -08:00
Reporter . Output . WriteLine ( $"Project {project.GetDisplayName()} will be compiled because it either has empty inputs or outputs" ) ;
2015-12-21 10:42:41 -08:00
return true ;
2015-12-10 13:06:33 -08:00
}
2015-12-21 10:42:41 -08:00
//rebuild if missing inputs / outputs
if ( AnyMissingIO ( project , compilerIO . Outputs , "outputs" ) | | AnyMissingIO ( project , compilerIO . Inputs , "inputs" ) )
{
return true ;
}
2015-12-10 13:06:33 -08:00
2015-12-21 10:42:41 -08:00
// find the output with the earliest write time
var minOutputPath = compilerIO . Outputs . First ( ) ;
2016-01-27 16:20:26 -08:00
var minDateUtc = File . GetLastWriteTimeUtc ( minOutputPath ) ;
2015-12-21 10:42:41 -08:00
foreach ( var outputPath in compilerIO . Outputs )
{
2016-01-27 16:20:26 -08:00
if ( File . GetLastWriteTimeUtc ( outputPath ) > = minDateUtc )
2015-12-21 10:42:41 -08:00
{
continue ;
}
2016-01-27 16:20:26 -08:00
minDateUtc = File . GetLastWriteTimeUtc ( outputPath ) ;
2015-12-21 10:42:41 -08:00
minOutputPath = outputPath ;
}
// find inputs that are older than the earliest output
2016-01-26 14:53:56 -08:00
var newInputs = compilerIO . Inputs . FindAll ( p = > File . GetLastWriteTimeUtc ( p ) > = minDateUtc ) ;
2015-12-21 10:42:41 -08:00
if ( ! newInputs . Any ( ) )
{
2016-01-26 06:39:13 -08:00
Reporter . Output . WriteLine ( $"Project {project.GetDisplayName()} was previously compiled. Skipping compilation." ) ;
2015-12-21 10:42:41 -08:00
return false ;
}
2016-01-26 06:39:13 -08:00
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:" ) ;
2016-01-27 16:20:26 -08:00
Reporter . Verbose . WriteLine ( $" {minDateUtc.ToLocalTime()}: {minOutputPath}" ) ;
2016-01-26 06:39:13 -08:00
Reporter . Verbose . WriteLine ( ) ;
Reporter . Verbose . WriteLine ( $" Inputs newer than the oldest output item:" ) ;
2015-12-21 10:42:41 -08:00
foreach ( var newInput in newInputs )
{
2016-01-26 06:39:13 -08:00
Reporter . Verbose . WriteLine ( $" {File.GetLastWriteTime(newInput)}: {newInput}" ) ;
2015-12-21 10:42:41 -08:00
}
2016-01-26 06:39:13 -08:00
Reporter . Verbose . WriteLine ( ) ;
2015-12-21 10:42:41 -08:00
return true ;
2015-12-10 13:06:33 -08:00
}
2015-12-21 10:42:41 -08:00
private static bool AnyMissingIO ( ProjectContext project , IEnumerable < string > items , string itemsType )
2015-12-10 13:06:33 -08:00
{
2015-12-21 10:42:41 -08:00
var missingItems = items . Where ( i = > ! File . Exists ( i ) ) . ToList ( ) ;
2015-12-10 13:06:33 -08:00
2015-12-21 10:42:41 -08:00
if ( ! missingItems . Any ( ) )
{
return false ;
}
2015-12-10 13:06:33 -08:00
2016-01-26 06:39:13 -08:00
Reporter . Verbose . WriteLine ( $"Project {project.GetDisplayName()} will be compiled because expected {itemsType} are missing." ) ;
2015-12-10 13:06:33 -08:00
2015-12-21 10:42:41 -08:00
foreach ( var missing in missingItems )
2015-12-10 13:06:33 -08:00
{
2016-01-26 06:39:13 -08:00
Reporter . Verbose . WriteLine ( $" {missing}" ) ;
2015-12-21 10:42:41 -08:00
}
2015-12-10 13:06:33 -08:00
2016-01-26 06:39:13 -08:00
Reporter . Verbose . WriteLine ( ) ; ;
2015-12-21 10:42:41 -08:00
return true ;
}
2016-03-03 22:57:43 -08:00
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 versionsAreEqual = string . Equals ( File . ReadAllText ( currentVersionFile ) , 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 ) ;
}
File . Copy ( DotnetFiles . VersionFile , projectVersionFile , true ) ;
}
else
{
Reporter . Verbose . WriteLine ( $"Project {project.GetDisplayName()} was not stamped with a CLI version because the version file does not exist: {DotnetFiles.VersionFile}" ) ;
}
}
2015-12-21 10:42:41 -08:00
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 ( ) ;
2015-12-10 13:06:33 -08:00
}
2015-12-21 10:42:41 -08:00
Reporter . Output . WriteLine ( ) ;
}
private void CreateOutputDirectories ( )
{
2016-02-03 10:57:25 -08:00
if ( ! string . IsNullOrEmpty ( _args . OutputValue ) )
{
Directory . CreateDirectory ( _args . OutputValue ) ;
}
if ( ! string . IsNullOrEmpty ( _args . BuildBasePathValue ) )
{
Directory . CreateDirectory ( _args . BuildBasePathValue ) ;
}
2015-12-10 13:06:33 -08:00
}
private IncrementalPreconditions GatherIncrementalPreconditions ( )
{
2016-02-05 15:29:34 -08:00
var preconditions = new IncrementalPreconditions ( _args . ShouldPrintIncrementalPreconditions ) ;
2015-12-10 13:06:33 -08:00
2016-02-05 15:29:34 -08:00
if ( _args . ShouldNotUseIncrementality )
2015-12-21 10:42:41 -08:00
{
preconditions . AddForceUnsafePrecondition ( ) ;
}
2015-12-10 13:06:33 -08:00
var projectsToCheck = GetProjectsToCheck ( ) ;
foreach ( var project in projectsToCheck )
{
CollectScriptPreconditions ( project , preconditions ) ;
CollectCompilerNamePreconditions ( project , preconditions ) ;
2015-12-21 10:42:41 -08:00
CollectCheckPathProbingPreconditions ( project , preconditions ) ;
2015-12-10 13:06:33 -08:00
}
return preconditions ;
}
2015-12-21 10:42:41 -08:00
// check the entire project tree that needs to be compiled, duplicated for each framework
2015-12-10 13:06:33 -08:00
private List < ProjectContext > GetProjectsToCheck ( )
{
2016-02-05 15:29:34 -08:00
if ( _args . ShouldSkipDependencies )
{
return new List < ProjectContext > ( 1 ) { _rootProject } ;
}
2015-12-21 10:42:41 -08:00
// include initial root project
2016-02-05 15:29:34 -08:00
var contextsToCheck = new List < ProjectContext > ( 1 + _rootProjectDependencies . ProjectDependenciesWithSources . Count ) { _rootProject } ;
2015-12-10 13:06:33 -08:00
2015-12-21 10:42:41 -08:00
// convert ProjectDescription to ProjectContext
2016-02-05 15:29:34 -08:00
var dependencyContexts = _rootProjectDependencies . ProjectDependenciesWithSources . Select
2015-12-21 10:42:41 -08:00
( keyValuePair = > ProjectContext . Create ( keyValuePair . Value . Path , keyValuePair . Value . Framework ) ) ;
2015-12-10 13:06:33 -08:00
contextsToCheck . AddRange ( dependencyContexts ) ;
return contextsToCheck ;
}
2015-12-21 10:42:41 -08:00
private void CollectCheckPathProbingPreconditions ( ProjectContext project , IncrementalPreconditions preconditions )
2015-12-10 13:06:33 -08:00
{
var pathCommands = CompilerUtil . GetCommandsInvokedByCompile ( project )
2016-01-30 21:47:50 -08:00
. Select ( commandName = > Command . CreateDotNet ( commandName , Enumerable . Empty < string > ( ) , project . TargetFramework ) )
2016-01-06 02:27:16 -08:00
. Where ( c = > c . ResolutionStrategy . Equals ( CommandResolutionStrategy . Path ) ) ;
2015-12-10 13:06:33 -08:00
foreach ( var pathCommand in pathCommands )
{
preconditions . AddPathProbingPrecondition ( project . ProjectName ( ) , pathCommand . CommandName ) ;
}
}
private void CollectCompilerNamePreconditions ( ProjectContext project , IncrementalPreconditions preconditions )
{
2016-03-17 11:45:13 -07:00
if ( project . ProjectFile ! = null )
2015-12-10 13:06:33 -08:00
{
2016-03-17 11:45:13 -07:00
var projectCompiler = project . ProjectFile . CompilerName ;
if ( ! KnownCompilers . Any ( knownCompiler = > knownCompiler . Equals ( projectCompiler , StringComparison . Ordinal ) ) )
{
preconditions . AddUnknownCompilerPrecondition ( project . ProjectName ( ) , projectCompiler ) ;
}
2015-12-10 13:06:33 -08:00
}
}
private void CollectScriptPreconditions ( ProjectContext project , IncrementalPreconditions preconditions )
{
2016-03-17 11:45:13 -07:00
if ( project . ProjectFile ! = null )
2015-12-10 13:06:33 -08:00
{
2016-03-17 11:45:13 -07:00
var preCompileScripts = project . ProjectFile . Scripts . GetOrEmpty ( ScriptNames . PreCompile ) ;
var postCompileScripts = project . ProjectFile . Scripts . GetOrEmpty ( ScriptNames . PostCompile ) ;
2015-12-10 13:06:33 -08:00
2016-03-17 11:45:13 -07:00
if ( preCompileScripts . Any ( ) )
{
preconditions . AddPrePostScriptPrecondition ( project . ProjectName ( ) , ScriptNames . PreCompile ) ;
}
if ( postCompileScripts . Any ( ) )
{
preconditions . AddPrePostScriptPrecondition ( project . ProjectName ( ) , ScriptNames . PostCompile ) ;
}
2015-12-10 13:06:33 -08:00
}
}
2015-12-21 10:42:41 -08:00
private bool InvokeCompileOnDependency ( ProjectDescription projectDependency )
2015-12-10 13:06:33 -08:00
{
2016-01-22 13:55:13 -08:00
var args = new List < string > ( ) ;
args . Add ( "--framework" ) ;
2016-02-18 01:09:23 -08:00
args . Add ( $"{projectDependency.Framework}" ) ;
2016-01-22 13:55:13 -08:00
args . Add ( "--configuration" ) ;
2016-01-26 06:39:13 -08:00
args . Add ( _args . ConfigValue ) ;
args . Add ( projectDependency . Project . ProjectDirectory ) ;
2015-12-10 13:06:33 -08:00
2016-02-16 08:42:00 -08:00
if ( ! string . IsNullOrWhiteSpace ( _args . RuntimeValue ) )
{
args . Add ( "--runtime" ) ;
args . Add ( _args . RuntimeValue ) ;
}
2016-02-18 01:09:23 -08:00
if ( ! string . IsNullOrEmpty ( _args . VersionSuffixValue ) )
{
args . Add ( "--version-suffix" ) ;
args . Add ( _args . VersionSuffixValue ) ;
}
2016-02-03 10:57:25 -08:00
if ( ! string . IsNullOrWhiteSpace ( _args . BuildBasePathValue ) )
{
args . Add ( "--build-base-path" ) ;
args . Add ( _args . BuildBasePathValue ) ;
}
2016-03-17 23:46:01 -04:00
var compileResult = CompileCommand . Run ( args . ToArray ( ) ) ;
2016-02-03 12:04:09 -08:00
return compileResult = = 0 ;
2015-12-10 13:06:33 -08:00
}
2015-12-21 10:42:41 -08:00
private bool InvokeCompileOnRootProject ( )
2015-12-10 13:06:33 -08:00
{
2015-12-21 10:42:41 -08:00
// todo: add methods to CompilerCommandApp to generate the arg string?
2016-01-22 13:55:13 -08:00
var args = new List < string > ( ) ;
2016-01-22 14:04:04 -08:00
args . Add ( "--framework" ) ;
2016-02-18 01:09:23 -08:00
args . Add ( _rootProject . TargetFramework . ToString ( ) ) ;
2016-01-22 14:04:04 -08:00
args . Add ( "--configuration" ) ;
args . Add ( _args . ConfigValue ) ;
2016-02-03 10:57:25 -08:00
2016-02-16 08:42:00 -08:00
if ( ! string . IsNullOrWhiteSpace ( _args . RuntimeValue ) )
{
args . Add ( "--runtime" ) ;
args . Add ( _args . RuntimeValue ) ;
}
2016-02-03 10:57:25 -08:00
if ( ! string . IsNullOrEmpty ( _args . OutputValue ) )
{
args . Add ( "--output" ) ;
args . Add ( _args . OutputValue ) ;
}
2016-02-18 01:09:23 -08:00
if ( ! string . IsNullOrEmpty ( _args . VersionSuffixValue ) )
{
args . Add ( "--version-suffix" ) ;
args . Add ( _args . VersionSuffixValue ) ;
}
2016-02-03 10:57:25 -08:00
if ( ! string . IsNullOrEmpty ( _args . BuildBasePathValue ) )
{
args . Add ( "--build-base-path" ) ;
args . Add ( _args . BuildBasePathValue ) ;
}
2016-01-22 14:04:04 -08:00
//native args
2016-01-26 06:39:13 -08:00
if ( _args . IsNativeValue )
{
args . Add ( "--native" ) ;
2016-01-22 13:55:13 -08:00
}
2016-01-26 06:39:13 -08:00
if ( _args . IsCppModeValue )
{
args . Add ( "--cpp" ) ;
2016-01-22 13:55:13 -08:00
}
2016-02-25 18:12:00 -08:00
if ( ! string . IsNullOrWhiteSpace ( _args . CppCompilerFlagsValue ) )
{
args . Add ( "--cppcompilerflags" ) ;
args . Add ( _args . CppCompilerFlagsValue ) ;
}
2016-01-22 14:04:04 -08:00
if ( ! string . IsNullOrWhiteSpace ( _args . ArchValue ) )
{
args . Add ( "--arch" ) ;
args . Add ( _args . ArchValue ) ;
}
2016-01-22 13:55:13 -08:00
2016-02-03 16:34:03 -08:00
foreach ( var ilcArg in _args . IlcArgsValue )
2016-01-22 14:04:04 -08:00
{
2016-02-03 16:34:03 -08:00
args . Add ( "--ilcarg" ) ;
args . Add ( ilcArg ) ;
2016-01-22 14:04:04 -08:00
}
2016-01-22 13:55:13 -08:00
2016-01-22 14:04:04 -08:00
if ( ! string . IsNullOrWhiteSpace ( _args . IlcPathValue ) )
{
args . Add ( "--ilcpath" ) ;
args . Add ( _args . IlcPathValue ) ;
}
2016-01-22 13:55:13 -08:00
2016-01-22 14:04:04 -08:00
if ( ! string . IsNullOrWhiteSpace ( _args . IlcSdkPathValue ) )
{
args . Add ( "--ilcsdkpath" ) ;
args . Add ( _args . IlcSdkPathValue ) ;
}
args . Add ( _rootProject . ProjectDirectory ) ;
2016-03-17 23:46:01 -04:00
var compileResult = CompileCommand . Run ( args . ToArray ( ) ) ;
2015-12-10 13:06:33 -08:00
2016-02-03 12:04:09 -08:00
var succeeded = compileResult = = 0 ;
2016-01-29 18:21:37 -08:00
if ( succeeded )
{
2016-03-01 17:42:44 -08:00
MakeRunnable ( ) ;
2016-02-03 10:57:25 -08:00
}
2016-01-29 18:21:37 -08:00
return succeeded ;
}
2016-02-16 15:30:39 -08:00
private void CopyCompilationOutput ( OutputPaths outputPaths )
2016-01-29 18:21:37 -08:00
{
2016-02-16 15:30:39 -08:00
var dest = outputPaths . RuntimeOutputPath ;
var source = outputPaths . CompilationOutputPath ;
2016-03-15 11:50:14 -07:00
// No need to copy if dest and source are the same
if ( string . Equals ( dest , source , StringComparison . OrdinalIgnoreCase ) )
{
return ;
}
2016-02-16 15:30:39 -08:00
foreach ( var file in outputPaths . CompilationFiles . All ( ) )
2016-01-29 18:21:37 -08:00
{
2016-02-16 15:30:39 -08:00
var destFileName = file . Replace ( source , dest ) ;
var directoryName = Path . GetDirectoryName ( destFileName ) ;
if ( ! Directory . Exists ( directoryName ) )
2016-01-29 18:21:37 -08:00
{
2016-02-16 15:30:39 -08:00
Directory . CreateDirectory ( directoryName ) ;
2016-01-29 18:21:37 -08:00
}
2016-02-16 15:30:39 -08:00
File . Copy ( file , destFileName , true ) ;
2016-01-29 18:21:37 -08:00
}
2015-12-10 13:06:33 -08:00
}
2016-02-03 10:57:25 -08:00
private void MakeRunnable ( )
{
2016-02-16 08:42:00 -08:00
var runtimeContext = _rootProject . CreateRuntimeContext ( _args . GetRuntimes ( ) ) ;
var outputPaths = runtimeContext . GetOutputPaths ( _args . ConfigValue , _args . BuildBasePathValue , _args . OutputValue ) ;
var libraryExporter = runtimeContext . CreateExporter ( _args . ConfigValue , _args . BuildBasePathValue ) ;
2016-03-01 17:42:44 -08:00
2016-03-15 11:50:14 -07:00
CopyCompilationOutput ( outputPaths ) ;
2016-03-01 17:42:44 -08:00
2016-03-08 16:46:50 -08:00
var executable = new Executable ( runtimeContext , outputPaths , libraryExporter , _args . ConfigValue ) ;
2016-02-03 10:57:25 -08:00
executable . MakeCompilationOutputRunnable ( ) ;
}
2015-12-10 13:06:33 -08:00
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 ) ;
}
2015-12-21 10:42:41 -08:00
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
2016-02-16 08:42:00 -08:00
public CompilerIO GetCompileIO ( ProjectContext project , ProjectDependenciesFacade dependencies )
2015-12-21 10:42:41 -08:00
{
2016-02-16 08:42:00 -08:00
var buildConfiguration = _args . ConfigValue ;
var buildBasePath = _args . BuildBasePathValue ;
var outputPath = _args . OutputValue ;
var isRootProject = project = = _rootProject ;
2015-12-21 10:42:41 -08:00
var compilerIO = new CompilerIO ( new List < string > ( ) , new List < string > ( ) ) ;
2016-02-03 10:57:25 -08:00
var calculator = project . GetOutputPaths ( buildConfiguration , buildBasePath , outputPath ) ;
var binariesOutputPath = calculator . CompilationOutputPath ;
2015-12-21 10:42:41 -08:00
// input: project.json
compilerIO . Inputs . Add ( project . ProjectFile . ProjectFilePath ) ;
2016-01-14 13:32:39 -08:00
// input: lock file; find when dependencies change
AddLockFile ( project , compilerIO ) ;
2015-12-21 10:42:41 -08:00
// 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 ) ;
2016-03-09 11:36:16 -08:00
var allOutputPath = new HashSet < string > ( calculator . CompilationFiles . All ( ) ) ;
2016-02-10 08:14:06 -08:00
if ( isRootProject & & project . ProjectFile . HasRuntimeOutput ( buildConfiguration ) )
2016-02-03 10:57:25 -08:00
{
2016-02-16 08:42:00 -08:00
var runtimeContext = project . CreateRuntimeContext ( _args . GetRuntimes ( ) ) ;
2016-03-09 11:36:16 -08:00
foreach ( var path in runtimeContext . GetOutputPaths ( buildConfiguration , buildBasePath , outputPath ) . RuntimeFiles . All ( ) )
{
allOutputPath . Add ( path ) ;
}
2016-02-03 10:57:25 -08:00
}
2016-03-09 11:36:16 -08:00
2016-01-27 13:07:26 -08:00
// output: compiler outputs
2016-02-03 10:57:25 -08:00
foreach ( var path in allOutputPath )
2016-01-27 13:07:26 -08:00
{
compilerIO . Outputs . Add ( path ) ;
}
2016-02-03 10:57:25 -08:00
2016-01-27 13:07:26 -08:00
// input compilation options files
AddCompilationOptions ( project , buildConfiguration , compilerIO ) ;
2015-12-21 10:42:41 -08:00
2016-02-08 16:06:13 -08:00
// input / output: resources with culture
AddNonCultureResources ( project , calculator . IntermediateOutputDirectoryPath , compilerIO ) ;
2015-12-21 10:42:41 -08:00
// input / output: resources without culture
2016-02-03 10:57:25 -08:00
AddCultureResources ( project , binariesOutputPath , compilerIO ) ;
2015-12-21 10:42:41 -08:00
return compilerIO ;
}
2016-01-14 13:32:39 -08:00
private static void AddLockFile ( ProjectContext project , CompilerIO compilerIO )
{
2016-01-26 06:39:13 -08:00
if ( project . LockFile = = null )
2016-01-14 13:32:39 -08:00
{
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 ) ;
}
2015-12-21 10:42:41 -08:00
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 ) ) ;
2016-01-14 13:32:39 -08:00
// non project dependencies get captured by changes in the lock file
2015-12-21 10:42:41 -08:00
}
2016-01-27 13:07:26 -08:00
private static void AddCompilationOptions ( ProjectContext project , string config , CompilerIO compilerIO )
2015-12-21 10:42:41 -08:00
{
2016-03-08 16:46:50 -08:00
var compilerOptions = project . ResolveCompilationOptions ( config ) ;
2016-01-14 17:12:59 -08:00
// input: key file
if ( compilerOptions . KeyFile ! = null )
2015-12-21 10:42:41 -08:00
{
2016-01-14 17:12:59 -08:00
compilerIO . Inputs . Add ( compilerOptions . KeyFile ) ;
2015-12-21 10:42:41 -08:00
}
}
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 ) ;
}
}
}
2015-12-10 13:06:33 -08:00
}
}