compile-fsc fix and features

Support project.json compilationOptions:

- nowarn
- warningsAsErrors
- keyFile
- delaySign

Use env var to configure compile-fsc behaviour:

- DOTNET_FSC_PATH the fsc path. Default the bundled fsc.exe
- DOTNET_COMPILEFSC_USE_RESPONSE_FILE if '1' then pass a response file
  to fsc instead of all arguments. Default pass all arguments
- DOTNET_FSC_EXEC configure how to run the fsc. Values:
   - RUN run fsc passing fsc args
   - COREHOST run corehost passing fsc and fsc args. The default

Use the same order of fsproj msbuild task for fsc arguments to make it easier
compare fsproj build and .net cli build

Fix --resource with path and name

Enable --debug (-g) if pdb

The generated assembly info file must be in the last - 1 position
in source files list.

1. the generated assembly info file must be in the last possibile position to
   override the attributes
2. The last file is the source file with main
This commit is contained in:
Enrico Sada 2016-03-16 16:29:23 +01:00
parent c2afaa265f
commit b6fcbbdd86

View file

@ -80,21 +80,16 @@ namespace Microsoft.DotNet.Tools.Compiler.Fsc
return returnCode;
}
var translated = TranslateCommonOptions(commonOptions, outputName);
var allArgs = new List<string>(translated);
allArgs.AddRange(GetDefaultOptions());
// Generate assembly info
var assemblyInfo = Path.Combine(tempOutDir, $"dotnet-compile.assemblyinfo.fs");
File.WriteAllText(assemblyInfo, AssemblyInfoFileGenerator.GenerateFSharp(assemblyInfoOptions));
allArgs.Add($"{assemblyInfo}");
// TODO less hacky
bool targetNetCore =
commonOptions.Defines.Contains("DNXCORE50") ||
commonOptions.Defines.Where(d => d.StartsWith("NETSTANDARDAPP1_")).Any() ||
commonOptions.Defines.Where(d => d.StartsWith("NETSTANDARD1_")).Any();
// FSC arguments
var allArgs = new List<string>();
//HACK fsc raise error FS0208 if target exe doesnt have extension .exe
bool hackFS0208 = targetNetCore && commonOptions.EmitEntryPoint == true;
string originalOutputName = outputName;
@ -109,19 +104,129 @@ namespace Microsoft.DotNet.Tools.Compiler.Fsc
allArgs.Add($"--out:{outputName}");
}
//debug info (only windows pdb supported, not portablepdb)
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
allArgs.Add("--debug");
//TODO check if full or pdbonly
allArgs.Add("--debug:pdbonly");
}
else
allArgs.Add("--debug-");
// Default options
allArgs.Add("--noframework");
allArgs.Add("--nologo");
allArgs.Add("--simpleresolution");
// project.json compilationOptions
if (commonOptions.Defines != null)
{
allArgs.AddRange(commonOptions.Defines.Select(def => $"--define:{def}"));
}
if (commonOptions.GenerateXmlDocumentation == true)
{
allArgs.Add($"--doc:{Path.ChangeExtension(outputName, "xml")}");
}
if (commonOptions.KeyFile != null)
{
allArgs.Add($"--keyfile:{commonOptions.KeyFile}");
}
if (commonOptions.Optimize == true)
{
allArgs.Add("--optimize+");
}
//--resource doesnt expect "
//bad: --resource:"path/to/file",name
//ok: --resource:path/to/file,name
allArgs.AddRange(resources.Select(resource => $"--resource:{resource.Replace("\"", "")}"));
allArgs.AddRange(references.Select(r => $"-r:{r}"));
if (commonOptions.EmitEntryPoint != true)
{
allArgs.Add("--target:library");
}
else
{
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}");
}
}
if (commonOptions.SuppressWarnings != null)
{
allArgs.Add("--nowarn:" + string.Join(",", commonOptions.SuppressWarnings.ToArray()));
}
if (commonOptions.LanguageVersion != null)
{
// Not used in fsc
}
if (commonOptions.Platform != null)
{
allArgs.Add($"--platform:{commonOptions.Platform}");
}
if (commonOptions.AllowUnsafe == true)
{
}
if (commonOptions.WarningsAsErrors == true)
{
allArgs.Add("--warnaserror");
}
//set target framework
if (targetNetCore)
{
allArgs.Add("--targetprofile:netcore");
}
allArgs.AddRange(references.Select(r => $"-r:{r}"));
allArgs.AddRange(resources.Select(resource => $"--resource:{resource}"));
allArgs.AddRange(sources.Select(s => $"{s}"));
if (commonOptions.DelaySign == true)
{
allArgs.Add("--delaysign+");
}
if (commonOptions.PublicSign == true)
{
}
if (commonOptions.AdditionalArguments != null)
{
// Additional arguments are added verbatim
allArgs.AddRange(commonOptions.AdditionalArguments);
}
// 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")
{
allArgs = new List<string> { $"@{rsp}" };
}
// Execute FSC!
var result = RunFsc(allArgs)
.ForwardStdErr()
@ -147,112 +252,43 @@ namespace Microsoft.DotNet.Tools.Compiler.Fsc
return result.ExitCode;
}
// TODO: Review if this is the place for default options
private static IEnumerable<string> GetDefaultOptions()
{
var args = new List<string>()
{
"--noframework",
"--nologo",
"--simpleresolution"
};
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
args.Add("--debug:full");
else
args.Add("--debug-");
return args;
}
private static IEnumerable<string> TranslateCommonOptions(CommonCompilerOptions options, string outputName)
{
List<string> commonArgs = new List<string>();
if (options.Defines != null)
{
commonArgs.AddRange(options.Defines.Select(def => $"-d:{def}"));
}
if (options.SuppressWarnings != null)
{
}
// Additional arguments are added verbatim
if (options.AdditionalArguments != null)
{
commonArgs.AddRange(options.AdditionalArguments);
}
if (options.LanguageVersion != null)
{
}
if (options.Platform != null)
{
commonArgs.Add($"--platform:{options.Platform}");
}
if (options.AllowUnsafe == true)
{
}
if (options.WarningsAsErrors == true)
{
commonArgs.Add("--warnaserror");
}
if (options.Optimize == true)
{
commonArgs.Add("--optimize");
}
if (options.KeyFile != null)
{
}
if (options.DelaySign == true)
{
}
if (options.PublicSign == true)
{
}
if (options.GenerateXmlDocumentation == true)
{
commonArgs.Add($"--doc:{Path.ChangeExtension(outputName, "xml")}");
}
if (options.EmitEntryPoint != true)
{
commonArgs.Add("--target:library");
}
else
{
commonArgs.Add("--target:exe");
//HACK we need default.win32manifest for exe
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var win32manifestPath = Path.Combine(AppContext.BaseDirectory, "default.win32manifest");
commonArgs.Add($"--win32manifest:{win32manifestPath}");
}
}
return commonArgs;
}
private static Command RunFsc(List<string> fscArgs)
{
var corerun = Path.Combine(AppContext.BaseDirectory, Constants.HostExecutableName);
var fscExe = Path.Combine(AppContext.BaseDirectory, "fsc.exe");
var fscExe = Environment.GetEnvironmentVariable("DOTNET_FSC_PATH")
?? Path.Combine(AppContext.BaseDirectory, "fsc.exe");
List<string> args = new List<string>();
args.Add(fscExe);
args.AddRange(fscArgs);
return Command.Create(corerun, args.ToArray());
var exec = Environment.GetEnvironmentVariable("DOTNET_FSC_EXEC")?.ToUpper() ?? "COREHOST";
switch (exec)
{
case "RUN":
return Command.Create(fscExe, fscArgs.ToArray());
case "COREHOST":
default:
var corehost = Path.Combine(AppContext.BaseDirectory, Constants.HostExecutableName);
return Command.Create(corehost, new[] { fscExe }.Concat(fscArgs).ToArray());
}
}
// 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)
{
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();
}
}
}