2015-12-17 13:28:11 +00: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 10:03:27 +00:00
|
|
|
|
using NuGet.Frameworks;
|
2015-12-17 13:28:11 +00:00
|
|
|
|
|
|
|
|
|
namespace Microsoft.DotNet.Tools.Compiler.Fsc
|
|
|
|
|
{
|
2016-01-31 05:47:50 +00:00
|
|
|
|
public class CompileFscCommand
|
2015-12-17 13:28:11 +00:00
|
|
|
|
{
|
|
|
|
|
private const int ExitFailed = 1;
|
|
|
|
|
|
2016-01-31 05:47:50 +00:00
|
|
|
|
public static int Run(string[] args)
|
2015-12-17 13:28:11 +00: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 15:29:23 +00:00
|
|
|
|
// TODO less hacky
|
2016-03-16 01:28:47 +00: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 14:42:20 +00:00
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
// FSC arguments
|
|
|
|
|
var allArgs = new List<string>();
|
|
|
|
|
|
2015-12-17 13:28:11 +00:00
|
|
|
|
//HACK fsc raise error FS0208 if target exe doesnt have extension .exe
|
2016-02-11 14:42:20 +00:00
|
|
|
|
bool hackFS0208 = targetNetCore && commonOptions.EmitEntryPoint == true;
|
2015-12-17 13:28:11 +00:00
|
|
|
|
string originalOutputName = outputName;
|
|
|
|
|
|
|
|
|
|
if (outputName != null)
|
|
|
|
|
{
|
|
|
|
|
if (hackFS0208)
|
|
|
|
|
{
|
|
|
|
|
outputName = Path.ChangeExtension(outputName, ".exe");
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-08 10:04:53 +00:00
|
|
|
|
allArgs.Add($"--out:{outputName}");
|
2015-12-17 13:28:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
//debug info (only windows pdb supported, not portablepdb)
|
|
|
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
2016-02-08 10:03:27 +00:00
|
|
|
|
{
|
2016-03-16 15:29:23 +00:00
|
|
|
|
allArgs.Add("--debug");
|
|
|
|
|
//TODO check if full or pdbonly
|
|
|
|
|
allArgs.Add("--debug:pdbonly");
|
2016-02-08 10:03:27 +00:00
|
|
|
|
}
|
2016-03-16 15:29:23 +00:00
|
|
|
|
else
|
|
|
|
|
allArgs.Add("--debug-");
|
2016-02-08 10:03:27 +00:00
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
// Default options
|
|
|
|
|
allArgs.Add("--noframework");
|
|
|
|
|
allArgs.Add("--nologo");
|
|
|
|
|
allArgs.Add("--simpleresolution");
|
2016-02-11 14:42:20 +00:00
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
// project.json compilationOptions
|
|
|
|
|
if (commonOptions.Defines != null)
|
2015-12-17 13:28:11 +00:00
|
|
|
|
{
|
2016-03-16 15:29:23 +00:00
|
|
|
|
allArgs.AddRange(commonOptions.Defines.Select(def => $"--define:{def}"));
|
2015-12-17 13:28:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
if (commonOptions.GenerateXmlDocumentation == true)
|
2016-02-08 10:12:30 +00:00
|
|
|
|
{
|
2016-03-16 15:29:23 +00:00
|
|
|
|
allArgs.Add($"--doc:{Path.ChangeExtension(outputName, "xml")}");
|
2016-02-08 10:12:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
if (commonOptions.KeyFile != null)
|
2015-12-17 13:28:11 +00:00
|
|
|
|
{
|
2016-03-16 15:29:23 +00:00
|
|
|
|
allArgs.Add($"--keyfile:{commonOptions.KeyFile}");
|
|
|
|
|
}
|
2015-12-17 13:28:11 +00:00
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
if (commonOptions.Optimize == true)
|
|
|
|
|
{
|
|
|
|
|
allArgs.Add("--optimize+");
|
|
|
|
|
}
|
2015-12-17 13:28:11 +00:00
|
|
|
|
|
2016-03-16 15:29:23 +00: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 13:28:11 +00:00
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
allArgs.AddRange(references.Select(r => $"-r:{r}"));
|
2015-12-17 13:28:11 +00:00
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
if (commonOptions.EmitEntryPoint != true)
|
|
|
|
|
{
|
|
|
|
|
allArgs.Add("--target:library");
|
|
|
|
|
}
|
|
|
|
|
else
|
2015-12-17 13:28:11 +00:00
|
|
|
|
{
|
2016-03-16 15:29:23 +00: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 13:28:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
if (commonOptions.SuppressWarnings != null)
|
2016-02-08 10:04:53 +00:00
|
|
|
|
{
|
2016-03-16 15:29:23 +00:00
|
|
|
|
allArgs.Add("--nowarn:" + string.Join(",", commonOptions.SuppressWarnings.ToArray()));
|
2016-02-08 10:04:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
if (commonOptions.LanguageVersion != null)
|
2016-02-09 14:34:04 +00:00
|
|
|
|
{
|
2016-03-16 15:29:23 +00:00
|
|
|
|
// Not used in fsc
|
2016-02-09 14:34:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
if (commonOptions.Platform != null)
|
2016-02-08 10:04:53 +00:00
|
|
|
|
{
|
2016-03-16 15:29:23 +00:00
|
|
|
|
allArgs.Add($"--platform:{commonOptions.Platform}");
|
2016-02-08 10:04:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
if (commonOptions.AllowUnsafe == true)
|
2015-12-17 13:28:11 +00:00
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
if (commonOptions.WarningsAsErrors == true)
|
2016-02-08 10:04:53 +00:00
|
|
|
|
{
|
2016-03-16 15:29:23 +00:00
|
|
|
|
allArgs.Add("--warnaserror");
|
2016-02-08 10:04:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
//set target framework
|
|
|
|
|
if (targetNetCore)
|
2015-12-17 13:28:11 +00:00
|
|
|
|
{
|
2016-03-16 15:29:23 +00:00
|
|
|
|
allArgs.Add("--targetprofile:netcore");
|
2015-12-17 13:28:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
if (commonOptions.DelaySign == true)
|
2015-12-17 13:28:11 +00:00
|
|
|
|
{
|
2016-03-16 15:29:23 +00:00
|
|
|
|
allArgs.Add("--delaysign+");
|
2015-12-17 13:28:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
if (commonOptions.PublicSign == true)
|
2016-02-08 10:04:53 +00:00
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
if (commonOptions.AdditionalArguments != null)
|
2016-02-08 10:04:53 +00:00
|
|
|
|
{
|
2016-03-16 15:29:23 +00:00
|
|
|
|
// Additional arguments are added verbatim
|
|
|
|
|
allArgs.AddRange(commonOptions.AdditionalArguments);
|
2016-02-08 10:04:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 15:29:23 +00: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);
|
|
|
|
|
|
|
|
|
|
//with env var DOTNET_COMPILEFSC_USE_RESPONSE_FILE, use the response file instead of
|
|
|
|
|
//call fsc with all arguments
|
|
|
|
|
if (Environment.GetEnvironmentVariable("DOTNET_COMPILEFSC_USE_RESPONSE_FILE") == "1")
|
2016-02-08 10:04:53 +00:00
|
|
|
|
{
|
2016-03-16 15:29:23 +00:00
|
|
|
|
allArgs = new List<string> { $"@{rsp}" };
|
2016-02-08 10:04:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
// Execute FSC!
|
|
|
|
|
var result = RunFsc(allArgs)
|
|
|
|
|
.ForwardStdErr()
|
|
|
|
|
.ForwardStdOut()
|
|
|
|
|
.Execute();
|
|
|
|
|
|
|
|
|
|
bool successFsc = result.ExitCode == 0;
|
|
|
|
|
|
|
|
|
|
if (hackFS0208 && File.Exists(outputName))
|
2016-01-08 19:03:14 +00:00
|
|
|
|
{
|
2016-03-16 15:29:23 +00:00
|
|
|
|
if (File.Exists(originalOutputName))
|
|
|
|
|
File.Delete(originalOutputName);
|
|
|
|
|
File.Move(outputName, originalOutputName);
|
2016-01-08 19:03:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 15:29:23 +00: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 13:28:11 +00:00
|
|
|
|
{
|
2016-03-16 15:29:23 +00:00
|
|
|
|
File.WriteAllBytes(pdbPath, Array.Empty<byte>());
|
2015-12-17 13:28:11 +00:00
|
|
|
|
}
|
2016-03-16 15:29:23 +00: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 13:28:11 +00:00
|
|
|
|
{
|
2016-03-16 15:29:23 +00:00
|
|
|
|
case "RUN":
|
|
|
|
|
return Command.Create(fscExe, fscArgs.ToArray());
|
2015-12-17 13:28:11 +00:00
|
|
|
|
|
2016-03-16 15:29:23 +00:00
|
|
|
|
case "COREHOST":
|
|
|
|
|
default:
|
|
|
|
|
var corehost = Path.Combine(AppContext.BaseDirectory, Constants.HostExecutableName);
|
|
|
|
|
return Command.Create(corehost, new[] { fscExe }.Concat(fscArgs).ToArray());
|
2015-12-17 13:28:11 +00:00
|
|
|
|
}
|
2016-01-08 19:03:14 +00:00
|
|
|
|
|
2015-12-17 13:28:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 15:29:23 +00: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 13:28:11 +00:00
|
|
|
|
{
|
2016-03-16 15:29:23 +00: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 13:28:11 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|