2015-12-17 14:28:11 +01: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.CommandLine ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Runtime.InteropServices ;
using System.Text ;
using Microsoft.DotNet.Cli.Compiler.Common ;
using Microsoft.DotNet.Cli.Utils ;
using Microsoft.DotNet.ProjectModel ;
2016-02-08 11:03:27 +01:00
using NuGet.Frameworks ;
2015-12-17 14:28:11 +01:00
namespace Microsoft.DotNet.Tools.Compiler.Fsc
{
2016-01-30 21:47:50 -08:00
public class CompileFscCommand
2015-12-17 14:28:11 +01:00
{
private const int ExitFailed = 1 ;
2016-01-30 21:47:50 -08:00
public static int Run ( string [ ] args )
2015-12-17 14:28:11 +01:00
{
DebugHelper . HandleDebugSwitch ( ref args ) ;
CommonCompilerOptions commonOptions = null ;
AssemblyInfoOptions assemblyInfoOptions = null ;
string tempOutDir = null ;
IReadOnlyList < string > references = Array . Empty < string > ( ) ;
IReadOnlyList < string > resources = Array . Empty < string > ( ) ;
IReadOnlyList < string > sources = Array . Empty < string > ( ) ;
string outputName = null ;
var help = false ;
var returnCode = 0 ;
string helpText = null ;
try
{
ArgumentSyntax . Parse ( args , syntax = >
{
syntax . HandleHelp = false ;
syntax . HandleErrors = false ;
commonOptions = CommonCompilerOptionsExtensions . Parse ( syntax ) ;
assemblyInfoOptions = AssemblyInfoOptions . Parse ( syntax ) ;
syntax . DefineOption ( "temp-output" , ref tempOutDir , "Compilation temporary directory" ) ;
syntax . DefineOption ( "out" , ref outputName , "Name of the output assembly" ) ;
syntax . DefineOptionList ( "reference" , ref references , "Path to a compiler metadata reference" ) ;
syntax . DefineOptionList ( "resource" , ref resources , "Resources to embed" ) ;
syntax . DefineOption ( "h|help" , ref help , "Help for compile native." ) ;
syntax . DefineParameterList ( "source-files" , ref sources , "Compilation sources" ) ;
helpText = syntax . GetHelpText ( ) ;
if ( tempOutDir = = null )
{
syntax . ReportError ( "Option '--temp-output' is required" ) ;
}
} ) ;
}
catch ( ArgumentSyntaxException exception )
{
Console . Error . WriteLine ( exception . Message ) ;
help = true ;
returnCode = ExitFailed ;
}
if ( help )
{
Console . WriteLine ( helpText ) ;
return returnCode ;
}
2016-03-16 16:29:23 +01:00
// TODO less hacky
2016-03-16 02:28:47 +01:00
bool targetNetCore =
commonOptions . Defines . Contains ( "DNXCORE50" ) | |
commonOptions . Defines . Where ( d = > d . StartsWith ( "NETSTANDARDAPP1_" ) ) . Any ( ) | |
commonOptions . Defines . Where ( d = > d . StartsWith ( "NETSTANDARD1_" ) ) . Any ( ) ;
2016-02-11 15:42:20 +01:00
2016-03-16 16:29:23 +01:00
// FSC arguments
var allArgs = new List < string > ( ) ;
2015-12-17 14:28:11 +01:00
//HACK fsc raise error FS0208 if target exe doesnt have extension .exe
2016-02-11 15:42:20 +01:00
bool hackFS0208 = targetNetCore & & commonOptions . EmitEntryPoint = = true ;
2015-12-17 14:28:11 +01:00
string originalOutputName = outputName ;
if ( outputName ! = null )
{
if ( hackFS0208 )
{
outputName = Path . ChangeExtension ( outputName , ".exe" ) ;
}
2016-02-08 11:04:53 +01:00
allArgs . Add ( $"--out:{outputName}" ) ;
2015-12-17 14:28:11 +01:00
}
2016-03-16 16:29:23 +01:00
//debug info (only windows pdb supported, not portablepdb)
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
2016-02-08 11:03:27 +01:00
{
2016-03-16 16:29:23 +01:00
allArgs . Add ( "--debug" ) ;
//TODO check if full or pdbonly
allArgs . Add ( "--debug:pdbonly" ) ;
2016-02-08 11:03:27 +01:00
}
2016-03-16 16:29:23 +01:00
else
allArgs . Add ( "--debug-" ) ;
2016-02-08 11:03:27 +01:00
2016-03-16 16:29:23 +01:00
// Default options
allArgs . Add ( "--noframework" ) ;
allArgs . Add ( "--nologo" ) ;
allArgs . Add ( "--simpleresolution" ) ;
2016-02-11 15:42:20 +01:00
2016-03-16 16:29:23 +01:00
// project.json compilationOptions
if ( commonOptions . Defines ! = null )
2015-12-17 14:28:11 +01:00
{
2016-03-16 16:29:23 +01:00
allArgs . AddRange ( commonOptions . Defines . Select ( def = > $"--define:{def}" ) ) ;
2015-12-17 14:28:11 +01:00
}
2016-03-16 16:29:23 +01:00
if ( commonOptions . GenerateXmlDocumentation = = true )
2016-02-08 11:12:30 +01:00
{
2016-03-16 16:29:23 +01:00
allArgs . Add ( $"--doc:{Path.ChangeExtension(outputName, " xml ")}" ) ;
2016-02-08 11:12:30 +01:00
}
2016-03-16 16:29:23 +01:00
if ( commonOptions . KeyFile ! = null )
2015-12-17 14:28:11 +01:00
{
2016-03-16 16:29:23 +01:00
allArgs . Add ( $"--keyfile:{commonOptions.KeyFile}" ) ;
}
2015-12-17 14:28:11 +01:00
2016-03-16 16:29:23 +01:00
if ( commonOptions . Optimize = = true )
{
allArgs . Add ( "--optimize+" ) ;
}
2015-12-17 14:28:11 +01:00
2016-03-16 16:29:23 +01:00
//--resource doesnt expect "
//bad: --resource:"path/to/file",name
//ok: --resource:path/to/file,name
allArgs . AddRange ( resources . Select ( resource = > $"--resource:{resource.Replace(" \ "" , "" ) } "));
2015-12-17 14:28:11 +01:00
2016-03-16 16:29:23 +01:00
allArgs . AddRange ( references . Select ( r = > $"-r:{r}" ) ) ;
2015-12-17 14:28:11 +01:00
2016-03-16 16:29:23 +01:00
if ( commonOptions . EmitEntryPoint ! = true )
{
allArgs . Add ( "--target:library" ) ;
}
else
2015-12-17 14:28:11 +01:00
{
2016-03-16 16:29:23 +01:00
allArgs . Add ( "--target:exe" ) ;
//HACK we need default.win32manifest for exe
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
{
var win32manifestPath = Path . Combine ( AppContext . BaseDirectory , "default.win32manifest" ) ;
allArgs . Add ( $"--win32manifest:{win32manifestPath}" ) ;
}
2015-12-17 14:28:11 +01:00
}
2016-03-16 16:29:23 +01:00
if ( commonOptions . SuppressWarnings ! = null )
2016-02-08 11:04:53 +01:00
{
2016-03-16 16:29:23 +01:00
allArgs . Add ( "--nowarn:" + string . Join ( "," , commonOptions . SuppressWarnings . ToArray ( ) ) ) ;
2016-02-08 11:04:53 +01:00
}
2016-03-16 16:29:23 +01:00
if ( commonOptions . LanguageVersion ! = null )
2016-02-09 15:34:04 +01:00
{
2016-03-16 16:29:23 +01:00
// Not used in fsc
2016-02-09 15:34:04 +01:00
}
2016-03-16 16:29:23 +01:00
if ( commonOptions . Platform ! = null )
2016-02-08 11:04:53 +01:00
{
2016-03-16 16:29:23 +01:00
allArgs . Add ( $"--platform:{commonOptions.Platform}" ) ;
2016-02-08 11:04:53 +01:00
}
2016-03-16 16:29:23 +01:00
if ( commonOptions . AllowUnsafe = = true )
2015-12-17 14:28:11 +01:00
{
}
2016-03-16 16:29:23 +01:00
if ( commonOptions . WarningsAsErrors = = true )
2016-02-08 11:04:53 +01:00
{
2016-03-16 16:29:23 +01:00
allArgs . Add ( "--warnaserror" ) ;
2016-02-08 11:04:53 +01:00
}
2016-03-16 16:29:23 +01:00
//set target framework
if ( targetNetCore )
2015-12-17 14:28:11 +01:00
{
2016-03-16 16:29:23 +01:00
allArgs . Add ( "--targetprofile:netcore" ) ;
2015-12-17 14:28:11 +01:00
}
2016-03-16 16:29:23 +01:00
if ( commonOptions . DelaySign = = true )
2015-12-17 14:28:11 +01:00
{
2016-03-16 16:29:23 +01:00
allArgs . Add ( "--delaysign+" ) ;
2015-12-17 14:28:11 +01:00
}
2016-03-16 16:29:23 +01:00
if ( commonOptions . PublicSign = = true )
2016-02-08 11:04:53 +01:00
{
}
2016-03-16 16:29:23 +01:00
if ( commonOptions . AdditionalArguments ! = null )
2016-02-08 11:04:53 +01:00
{
2016-03-16 16:29:23 +01:00
// Additional arguments are added verbatim
allArgs . AddRange ( commonOptions . AdditionalArguments ) ;
2016-02-08 11:04:53 +01:00
}
2016-03-16 16:29:23 +01:00
// Generate assembly info
var assemblyInfo = Path . Combine ( tempOutDir , $"dotnet-compile.assemblyinfo.fs" ) ;
File . WriteAllText ( assemblyInfo , AssemblyInfoFileGenerator . GenerateFSharp ( assemblyInfoOptions ) ) ;
//source files + assemblyInfo
allArgs . AddRange ( GetSourceFiles ( sources , assemblyInfo ) . ToArray ( ) ) ;
//TODO check the switch enabled in fsproj in RELEASE and DEBUG configuration
var rsp = Path . Combine ( tempOutDir , "dotnet-compile-fsc.rsp" ) ;
File . WriteAllLines ( rsp , allArgs , Encoding . UTF8 ) ;
// Execute FSC!
2016-03-16 16:32:42 +01:00
var result = RunFsc ( new List < string > { $"@{rsp}" } )
2016-03-16 16:29:23 +01:00
. ForwardStdErr ( )
. ForwardStdOut ( )
. Execute ( ) ;
bool successFsc = result . ExitCode = = 0 ;
if ( hackFS0208 & & File . Exists ( outputName ) )
2016-01-08 11:03:14 -08:00
{
2016-03-16 16:29:23 +01:00
if ( File . Exists ( originalOutputName ) )
File . Delete ( originalOutputName ) ;
File . Move ( outputName , originalOutputName ) ;
2016-01-08 11:03:14 -08:00
}
2016-03-16 16:29:23 +01:00
//HACK dotnet build require a pdb (crash without), fsc atm cant generate a portable pdb, so an empty pdb is created
string pdbPath = Path . ChangeExtension ( outputName , ".pdb" ) ;
if ( successFsc & & ! File . Exists ( pdbPath ) )
2015-12-17 14:28:11 +01:00
{
2016-03-16 16:29:23 +01:00
File . WriteAllBytes ( pdbPath , Array . Empty < byte > ( ) ) ;
2015-12-17 14:28:11 +01:00
}
2016-03-16 16:29:23 +01:00
return result . ExitCode ;
}
private static Command RunFsc ( List < string > fscArgs )
{
var fscExe = Environment . GetEnvironmentVariable ( "DOTNET_FSC_PATH" )
? ? Path . Combine ( AppContext . BaseDirectory , "fsc.exe" ) ;
var exec = Environment . GetEnvironmentVariable ( "DOTNET_FSC_EXEC" ) ? . ToUpper ( ) ? ? "COREHOST" ;
switch ( exec )
2015-12-17 14:28:11 +01:00
{
2016-03-16 16:29:23 +01:00
case "RUN" :
return Command . Create ( fscExe , fscArgs . ToArray ( ) ) ;
2015-12-17 14:28:11 +01:00
2016-03-16 16:29:23 +01:00
case "COREHOST" :
default :
var corehost = Path . Combine ( AppContext . BaseDirectory , Constants . HostExecutableName ) ;
return Command . Create ( corehost , new [ ] { fscExe } . Concat ( fscArgs ) . ToArray ( ) ) ;
2015-12-17 14:28:11 +01:00
}
2016-01-08 11:03:14 -08:00
2015-12-17 14:28:11 +01:00
}
2016-03-16 16:29:23 +01:00
// The assembly info must be in the last minus 1 position because:
// - assemblyInfo should be in the end to override attributes
// - assemblyInfo cannot be in the last position, because last file contains the main
private static IEnumerable < string > GetSourceFiles ( IReadOnlyList < string > sourceFiles , string assemblyInfo )
2015-12-17 14:28:11 +01:00
{
2016-03-16 16:29:23 +01:00
if ( ! sourceFiles . Any ( ) )
{
yield return assemblyInfo ;
yield break ;
}
foreach ( var s in sourceFiles . Take ( sourceFiles . Count ( ) - 1 ) )
yield return s ;
yield return assemblyInfo ;
yield return sourceFiles . Last ( ) ;
2015-12-17 14:28:11 +01:00
}
}
}