2015-10-23 15:21:49 -07:00
using System ;
2015-10-13 14:31:29 -07:00
using System.Collections.Generic ;
2015-10-06 10:46:43 -07:00
using System.IO ;
using System.Linq ;
2015-10-18 07:10:22 -07:00
using System.Runtime.InteropServices ;
2015-10-06 10:46:43 -07:00
using Microsoft.Dnx.Runtime.Common.CommandLine ;
using Microsoft.DotNet.Cli.Utils ;
2015-10-18 19:02:09 -07:00
using Microsoft.DotNet.Tools.Common ;
2015-10-13 14:31:29 -07:00
using Microsoft.Extensions.ProjectModel ;
using Microsoft.Extensions.ProjectModel.Compilation ;
using NuGet.Frameworks ;
2015-10-06 10:46:43 -07:00
namespace Microsoft.DotNet.Tools.Compiler
{
public class Program
{
public static int Main ( string [ ] args )
{
2015-10-13 14:31:29 -07:00
DebugHelper . HandleDebugSwitch ( ref args ) ;
2015-10-06 10:46:43 -07:00
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 ) ;
2015-10-13 14:31:29 -07:00
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 ) ;
2015-10-17 23:43:28 -07:00
var noProjectDependencies = app . Option ( "--no-project-dependencies" , "Skips building project references." , CommandOptionType . NoValue ) ;
2015-10-06 10:46:43 -07:00
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" ) ;
app . OnExecute ( ( ) = >
{
// Locate the project and get the name and full path
var path = project . Value ;
if ( string . IsNullOrEmpty ( path ) )
{
path = Directory . GetCurrentDirectory ( ) ;
}
2015-10-13 14:31:29 -07:00
2015-10-18 07:41:59 -07:00
var buildProjectReferences = ! noProjectDependencies . HasValue ( ) ;
2015-10-17 23:43:28 -07:00
2015-10-13 14:31:29 -07:00
// Load project contexts for each framework and compile them
bool success = true ;
if ( framework . HasValue ( ) )
2015-10-06 10:46:43 -07:00
{
2015-10-13 14:31:29 -07:00
foreach ( var context in framework . Values . Select ( f = > ProjectContext . Create ( path , NuGetFramework . Parse ( f ) ) ) )
{
2015-10-18 07:41:59 -07:00
success & = Compile ( context , configuration . Value ( ) ? ? Constants . DefaultConfiguration , output . Value ( ) , buildProjectReferences ) ;
2015-10-13 14:31:29 -07:00
}
2015-10-06 10:46:43 -07:00
}
2015-10-13 14:31:29 -07:00
else
2015-10-06 10:46:43 -07:00
{
2015-10-13 14:31:29 -07:00
foreach ( var context in ProjectContext . CreateContextForEachFramework ( path ) )
{
2015-10-18 07:41:59 -07:00
success & = Compile ( context , configuration . Value ( ) ? ? Constants . DefaultConfiguration , output . Value ( ) , buildProjectReferences ) ;
2015-10-13 14:31:29 -07:00
}
2015-10-06 10:46:43 -07:00
}
2015-10-13 14:31:29 -07:00
return success ? 0 : 1 ;
2015-10-06 10:46:43 -07:00
} ) ;
try
{
return app . Execute ( args ) ;
}
2015-10-18 02:06:15 -07:00
catch ( Exception ex )
2015-10-06 10:46:43 -07:00
{
2015-10-20 01:43:37 -07:00
#if DEBUG
Console . Error . WriteLine ( ex ) ;
#else
2015-10-06 10:46:43 -07:00
Console . Error . WriteLine ( ex . Message ) ;
2015-10-20 01:43:37 -07:00
#endif
2015-10-06 10:46:43 -07:00
return 1 ;
}
}
2015-10-18 07:41:59 -07:00
private static bool Compile ( ProjectContext context , string configuration , string outputPath , bool buildProjectReferences )
2015-10-06 10:46:43 -07:00
{
2015-10-21 00:26:57 -07:00
Reporter . Output . WriteLine ( $"Compiling {context.RootProject.Identity.Name.Yellow()} for {context.TargetFramework.DotNetFrameworkName.Yellow()}" ) ;
2015-10-13 14:31:29 -07:00
// Create the library exporter
var exporter = context . CreateExporter ( configuration ) ;
2015-10-21 00:26:57 -07:00
var diagnostics = new List < DiagnosticMessage > ( ) ;
2015-10-17 03:49:49 -07:00
2015-10-21 00:26:57 -07:00
bool success = true ;
2015-10-17 03:49:49 -07:00
2015-10-21 00:26:57 -07:00
// Collect dependency diagnostics
diagnostics . AddRange ( context . LibraryManager . GetAllDiagnostics ( ) ) ;
2015-10-17 03:49:49 -07:00
2015-10-13 14:31:29 -07:00
// Gather exports for the project
var dependencies = exporter . GetCompilationDependencies ( ) . ToList ( ) ;
2015-10-18 07:41:59 -07:00
if ( buildProjectReferences )
2015-10-06 10:46:43 -07:00
{
2015-10-17 23:43:28 -07:00
var projects = new Dictionary < string , ProjectDescription > ( ) ;
// Build project references
foreach ( var dependency in dependencies . Where ( d = > d . CompilationAssemblies . Any ( ) ) )
2015-10-13 14:31:29 -07:00
{
2015-10-17 23:43:28 -07:00
var projectDependency = dependency . Library as ProjectDescription ;
if ( projectDependency ! = null )
{
projects [ projectDependency . Identity . Name ] = projectDependency ;
}
}
foreach ( var projectDependency in Sort ( projects ) )
{
// Skip compiling project dependencies since we've already figured out the build order
2015-10-22 03:32:32 -07:00
var compileResult = Command . Create ( "dotnet-compile" , $"--framework {projectDependency.Framework} --configuration {configuration} --no-project-dependencies \" { projectDependency . Project . ProjectDirectory } \ "" )
2015-10-17 23:43:28 -07:00
. ForwardStdOut ( )
. ForwardStdErr ( )
2015-10-21 03:11:27 -07:00
. Execute ( ) ;
2015-10-17 23:43:28 -07:00
2015-10-13 14:31:29 -07:00
if ( compileResult . ExitCode ! = 0 )
{
return false ;
}
}
2015-10-18 07:41:59 -07:00
projects . Clear ( ) ;
2015-10-06 10:46:43 -07:00
}
2015-10-13 14:31:29 -07:00
// Dump dependency data
2015-10-17 03:49:49 -07:00
// TODO: Turn on only if verbose, we can look at the response
// file anyways
// ShowDependencyInfo(dependencies);
2015-10-13 14:31:29 -07:00
// Hackily generate the output path
2015-10-06 10:46:43 -07:00
if ( string . IsNullOrEmpty ( outputPath ) )
{
2015-10-13 14:31:29 -07:00
outputPath = Path . Combine (
2015-10-15 12:56:07 -07:00
context . ProjectFile . ProjectDirectory ,
Constants . BinDirectoryName ,
2015-10-13 14:31:29 -07:00
configuration ,
context . TargetFramework . GetTwoDigitShortFolderName ( ) ) ;
2015-10-06 10:46:43 -07:00
}
2015-10-17 23:43:28 -07:00
2015-10-18 21:07:48 -07:00
string intermediateOutputPath = Path . Combine (
context . ProjectFile . ProjectDirectory ,
Constants . ObjDirectoryName ,
configuration ,
context . TargetFramework . GetTwoDigitShortFolderName ( ) ) ;
2015-10-06 10:46:43 -07:00
if ( ! Directory . Exists ( outputPath ) )
{
Directory . CreateDirectory ( outputPath ) ;
}
2015-10-18 21:07:48 -07:00
if ( ! Directory . Exists ( intermediateOutputPath ) )
{
Directory . CreateDirectory ( intermediateOutputPath ) ;
}
2015-10-13 14:31:29 -07:00
// Get compilation options
2015-10-15 12:56:07 -07:00
var compilationOptions = context . ProjectFile . GetCompilerOptions ( context . TargetFramework , configuration ) ;
2015-10-20 04:35:40 -07:00
var outputName = Path . Combine ( outputPath , context . ProjectFile . Name + ( compilationOptions . EmitEntryPoint . GetValueOrDefault ( ) ? ".exe" : ".dll" ) ) ;
2015-10-21 01:18:14 -07:00
2015-10-18 01:17:13 -07:00
// Assemble args
var compilerArgs = new List < string > ( )
2015-10-06 10:46:43 -07:00
{
2015-10-13 14:31:29 -07:00
"-nostdlib" ,
"-nologo" ,
2015-10-15 12:56:07 -07:00
$"-out:\" { outputName } \ ""
2015-10-13 14:31:29 -07:00
} ;
2015-10-18 19:02:09 -07:00
2015-10-21 01:18:14 -07:00
// Default suppressions, some versions of mono don't support these
compilerArgs . Add ( "-nowarn:CS1701" ) ;
compilerArgs . Add ( "-nowarn:CS1702" ) ;
compilerArgs . Add ( "-nowarn:CS1705" ) ;
2015-10-13 14:31:29 -07:00
// Add compilation options to the args
2015-10-21 01:18:14 -07:00
ApplyCompilationOptions ( compilationOptions , compilerArgs ) ;
2015-10-13 14:31:29 -07:00
foreach ( var dependency in dependencies )
{
2015-10-18 01:17:13 -07:00
compilerArgs . AddRange ( dependency . CompilationAssemblies . Select ( r = > $"-r:\" { r } \ "" ) ) ;
2015-10-22 03:32:32 -07:00
compilerArgs . AddRange ( dependency . SourceReferences . Select ( s = > $"\" { s } \ "" ) ) ;
2015-10-06 10:46:43 -07:00
}
2015-10-13 14:31:29 -07:00
// Add project source files
2015-10-23 15:21:49 -07:00
var sourceFiles = context . ProjectFile . Files . SourceFiles ;
compilerArgs . AddRange ( sourceFiles . Select ( s = > $"\" { s } \ "" ) ) ;
2015-10-18 01:17:13 -07:00
2015-10-18 21:07:48 -07:00
if ( ! AddResources ( context . ProjectFile , compilerArgs , intermediateOutputPath ) )
{
return false ;
}
2015-10-18 19:02:09 -07:00
2015-10-23 15:21:49 -07:00
var compilerName = context . ProjectFile . CompilerName ;
2015-10-25 23:37:41 -07:00
if ( compilerName = = null )
2015-10-23 15:21:49 -07:00
{
2015-10-25 23:37:41 -07:00
Console . Error . WriteLine ( "Could not find the compiler name. Please specify it in the project.json file." ) ;
2015-10-23 15:21:49 -07:00
return false ;
}
2015-10-13 14:31:29 -07:00
// Write RSP file
2015-10-23 15:21:49 -07:00
var rsp = Path . Combine ( intermediateOutputPath , $"dotnet-compile.{compilerName}.rsp" ) ;
2015-10-18 01:17:13 -07:00
File . WriteAllLines ( rsp , compilerArgs ) ;
2015-10-23 15:21:49 -07:00
var result = Command . Create ( $"dotnet-compile-{compilerName}" , $"\" { rsp } \ "" )
2015-10-21 03:11:27 -07:00
. OnErrorLine ( line = >
2015-10-21 00:26:57 -07:00
{
var diagnostic = ParseDiagnostic ( context . ProjectDirectory , line ) ;
if ( diagnostic ! = null )
{
diagnostics . Add ( diagnostic ) ;
}
else
{
Console . Error . WriteLine ( line ) ;
}
} )
. OnOutputLine ( line = >
{
var diagnostic = ParseDiagnostic ( context . ProjectDirectory , line ) ;
if ( diagnostic ! = null )
{
diagnostics . Add ( diagnostic ) ;
}
else
{
Console . Out . WriteLine ( line ) ;
}
} )
2015-10-21 03:11:27 -07:00
. Execute ( ) ;
2015-10-18 01:17:13 -07:00
2015-10-21 00:26:57 -07:00
foreach ( var diag in diagnostics )
{
success & = diag . Severity ! = DiagnosticMessageSeverity . Error ;
PrintDiagnostic ( diag ) ;
}
success & = result . ExitCode = = 0 ;
PrintSummary ( diagnostics ) ;
return success ;
}
private static void PrintSummary ( List < DiagnosticMessage > diagnostics )
{
Reporter . Output . Writer . WriteLine ( ) ;
var errorCount = diagnostics . Count ( d = > d . Severity = = DiagnosticMessageSeverity . Error ) ;
var warningCount = diagnostics . Count ( d = > d . Severity = = DiagnosticMessageSeverity . Warning ) ;
if ( errorCount > 0 )
{
Reporter . Output . WriteLine ( "Compilation failed." . Red ( ) ) ;
}
else
2015-10-18 07:32:42 -07:00
{
2015-10-21 00:26:57 -07:00
Reporter . Output . WriteLine ( "Compilation succeeded." . Green ( ) ) ;
2015-10-18 07:32:42 -07:00
}
2015-10-21 00:26:57 -07:00
Reporter . Output . WriteLine ( $" {warningCount} Warning(s)" ) ;
Reporter . Output . WriteLine ( $" {errorCount} Error(s)" ) ;
Reporter . Output . Writer . WriteLine ( ) ;
2015-10-13 14:31:29 -07:00
}
2015-10-18 21:07:48 -07:00
private static bool AddResources ( Project project , List < string > compilerArgs , string intermediateOutputPath )
2015-10-18 19:02:09 -07:00
{
string root = PathUtility . EnsureTrailingSlash ( project . ProjectDirectory ) ;
foreach ( var resourceFile in project . Files . ResourceFiles )
{
string resourceName = null ;
string rootNamespace = null ;
var resourcePath = resourceFile . Key ;
if ( string . IsNullOrEmpty ( resourceFile . Value ) )
{
// No logical name, so use the file name
resourceName = ResourcePathUtility . GetResourceName ( root , resourcePath ) ;
rootNamespace = project . Name ;
}
else
{
resourceName = CreateCSharpManifestResourceName . EnsureResourceExtension ( resourceFile . Value , resourcePath ) ;
rootNamespace = null ;
}
var name = CreateCSharpManifestResourceName . CreateManifestName ( resourceName , rootNamespace ) ;
var fileName = resourcePath ;
2015-10-18 21:07:48 -07:00
if ( ResourcePathUtility . IsResxResourceFile ( fileName ) )
{
var ext = Path . GetExtension ( fileName ) ;
if ( string . Equals ( ext , ".resx" , StringComparison . OrdinalIgnoreCase ) )
{
// {file}.resx -> {file}.resources
var resourcesFile = Path . Combine ( intermediateOutputPath , name ) ;
2015-10-22 03:32:32 -07:00
var result = Command . Create ( "resgen" , $"\" { fileName } \ " \"{resourcesFile}\"" )
2015-10-18 21:07:48 -07:00
. ForwardStdErr ( )
. ForwardStdOut ( )
2015-10-21 03:11:27 -07:00
. Execute ( ) ;
2015-10-18 21:07:48 -07:00
if ( result . ExitCode ! = 0 )
{
return false ;
}
// Use this as the resource name instead
fileName = resourcesFile ;
}
}
2015-10-18 19:02:09 -07:00
compilerArgs . Add ( $"-resource:\" { fileName } \ ",{name}" ) ;
}
2015-10-18 21:07:48 -07:00
return true ;
2015-10-18 19:02:09 -07:00
}
2015-10-17 23:43:28 -07: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-10-21 00:26:57 -07:00
private static DiagnosticMessage ParseDiagnostic ( string projectRootPath , string line )
{
var error = CanonicalError . Parse ( line ) ;
if ( error ! = null )
{
var severity = error . category = = CanonicalError . Parts . Category . Error ?
DiagnosticMessageSeverity . Error : DiagnosticMessageSeverity . Warning ;
return new DiagnosticMessage (
error . code ,
error . text ,
Path . IsPathRooted ( error . origin ) ? line : projectRootPath + Path . DirectorySeparatorChar + line ,
Path . Combine ( projectRootPath , error . origin ) ,
severity ,
error . line ,
error . column ,
error . endColumn ,
error . endLine ,
source : null ) ;
}
return null ;
}
private static void PrintDiagnostic ( DiagnosticMessage diag )
{
switch ( diag . Severity )
{
case DiagnosticMessageSeverity . Info :
Reporter . Error . WriteLine ( diag . FormattedMessage ) ;
break ;
case DiagnosticMessageSeverity . Warning :
Reporter . Error . WriteLine ( diag . FormattedMessage . Yellow ( ) . Bold ( ) ) ;
break ;
case DiagnosticMessageSeverity . Error :
Reporter . Error . WriteLine ( diag . FormattedMessage . Red ( ) . Bold ( ) ) ;
break ;
}
}
2015-10-21 01:18:14 -07:00
private static void ApplyCompilationOptions ( CompilerOptions compilationOptions , List < string > compilerArgs )
2015-10-13 14:31:29 -07:00
{
2015-10-15 12:56:07 -07:00
var targetType = compilationOptions . EmitEntryPoint . GetValueOrDefault ( ) ? "exe" : "library" ;
2015-10-17 03:32:58 -07:00
2015-10-21 01:18:14 -07:00
compilerArgs . Add ( $"-target:{targetType}" ) ;
2015-10-17 03:32:58 -07:00
2015-10-15 12:56:07 -07:00
if ( compilationOptions . AllowUnsafe . GetValueOrDefault ( ) )
2015-10-13 14:31:29 -07:00
{
2015-10-21 01:18:14 -07:00
compilerArgs . Add ( "-unsafe+" ) ;
2015-10-13 14:31:29 -07:00
}
2015-10-17 03:32:58 -07:00
2015-10-21 01:18:14 -07:00
compilerArgs . AddRange ( compilationOptions . Defines . Select ( d = > $"-d:{d}" ) ) ;
2015-10-17 03:32:58 -07:00
2015-10-15 12:56:07 -07:00
if ( compilationOptions . Optimize . GetValueOrDefault ( ) )
2015-10-06 10:46:43 -07:00
{
2015-10-21 01:18:14 -07:00
compilerArgs . Add ( "-optimize" ) ;
2015-10-06 10:46:43 -07:00
}
2015-10-17 03:32:58 -07:00
2015-10-13 14:31:29 -07:00
if ( ! string . IsNullOrEmpty ( compilationOptions . Platform ) )
{
2015-10-21 01:18:14 -07:00
compilerArgs . Add ( $"-platform:{compilationOptions.Platform}" ) ;
2015-10-13 14:31:29 -07:00
}
2015-10-17 03:32:58 -07:00
2015-10-15 12:56:07 -07:00
if ( compilationOptions . WarningsAsErrors . GetValueOrDefault ( ) )
2015-10-13 14:31:29 -07:00
{
2015-10-21 01:18:14 -07:00
compilerArgs . Add ( "-warnaserror" ) ;
2015-10-13 14:31:29 -07:00
}
2015-10-17 03:32:58 -07:00
if ( compilationOptions . DelaySign . GetValueOrDefault ( ) )
{
2015-10-21 01:18:14 -07:00
compilerArgs . Add ( "-delaysign+" ) ;
2015-10-17 03:32:58 -07:00
}
if ( ! string . IsNullOrEmpty ( compilationOptions . KeyFile ) )
{
2015-10-21 01:18:14 -07:00
compilerArgs . Add ( $"-keyFile:\" { compilationOptions . KeyFile } \ "" ) ;
2015-10-17 03:32:58 -07:00
}
2015-10-21 01:18:14 -07:00
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
2015-10-18 07:10:22 -07:00
{
2015-10-21 01:18:14 -07:00
compilerArgs . Add ( "-debug:full" ) ;
2015-10-18 07:10:22 -07:00
}
else
{
2015-10-21 01:18:14 -07:00
compilerArgs . Add ( "-debug:portable" ) ;
2015-10-18 07:10:22 -07:00
}
2015-10-18 19:02:09 -07:00
2015-10-17 03:32:58 -07:00
// TODO: OSS signing
2015-10-13 14:31:29 -07:00
}
2015-10-06 10:46:43 -07:00
2015-10-13 14:31:29 -07:00
private static void ShowDependencyInfo ( IEnumerable < LibraryExport > dependencies )
{
foreach ( var dependency in dependencies )
{
if ( ! dependency . Library . Resolved )
2015-10-06 10:46:43 -07:00
{
2015-10-13 14:31:29 -07:00
Reporter . Error . WriteLine ( $" Unable to resolve dependency {dependency.Library.Identity.ToString().Red().Bold()}" ) ;
Reporter . Error . WriteLine ( "" ) ;
2015-10-06 10:46:43 -07:00
}
2015-10-13 14:31:29 -07:00
else
{
Reporter . Output . WriteLine ( $" Using {dependency.Library.Identity.Type.Value.Cyan().Bold()} dependency {dependency.Library.Identity.ToString().Cyan().Bold()}" ) ;
Reporter . Output . WriteLine ( $" Path: {dependency.Library.Path}" ) ;
2015-10-06 10:46:43 -07:00
2015-10-13 14:31:29 -07:00
foreach ( var metadataReference in dependency . CompilationAssemblies )
{
Reporter . Output . WriteLine ( $" Assembly: {metadataReference}" ) ;
}
foreach ( var sourceReference in dependency . SourceReferences )
{
Reporter . Output . WriteLine ( $" Source: {sourceReference}" ) ;
}
Reporter . Output . WriteLine ( "" ) ;
}
}
2015-10-06 10:46:43 -07:00
}
}
}