// 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; using System.Runtime.InteropServices; using System.Text; using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Compiler.Common; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.ProjectModel; namespace Microsoft.DotNet.Tools.Compiler.Csc { public class CompileCscCommand { private const int ExitFailed = 1; public static int Run(string[] args) { DebugHelper.HandleDebugSwitch(ref args); CommandLineApplication app = new CommandLineApplication(); app.Name = "dotnet compile-csc"; app.FullName = ".NET C# Compiler"; app.Description = "C# Compiler for the .NET Platform"; app.HandleResponseFiles = true; app.HelpOption("-h|--help"); CommonCompilerOptionsCommandLine commonCompilerCommandLine = CommonCompilerOptionsCommandLine.AddOptions(app); AssemblyInfoOptionsCommandLine assemblyInfoCommandLine = AssemblyInfoOptionsCommandLine.AddOptions(app); CommandOption tempOutput = app.Option("--temp-output ", "Compilation temporary directory", CommandOptionType.SingleValue); CommandOption outputName = app.Option("--out ", "Name of the output assembly", CommandOptionType.SingleValue); CommandOption references = app.Option("--reference ...", "Path to a compiler metadata reference", CommandOptionType.MultipleValue); CommandOption analyzers = app.Option("--analyzer ...", "Path to an analyzer assembly", CommandOptionType.MultipleValue); CommandOption resources = app.Option("--resource ...", "Resources to embed", CommandOptionType.MultipleValue); CommandArgument sources = app.Argument("...", "Compilation sources", multipleValues: true); app.OnExecute(() => { if (!tempOutput.HasValue()) { Reporter.Error.WriteLine("Option '--temp-output' is required"); return ExitFailed; } CommonCompilerOptions commonOptions = commonCompilerCommandLine.GetOptionValues(); AssemblyInfoOptions assemblyInfoOptions = assemblyInfoCommandLine.GetOptionValues(); var translated = TranslateCommonOptions(commonOptions, outputName.Value()); var allArgs = new List(translated); allArgs.AddRange(GetDefaultOptions()); // Generate assembly info var assemblyInfo = Path.Combine(tempOutput.Value(), $"dotnet-compile.assemblyinfo.cs"); File.WriteAllText(assemblyInfo, AssemblyInfoFileGenerator.GenerateCSharp(assemblyInfoOptions, sources.Values)); allArgs.Add($"\"{assemblyInfo}\""); if (outputName.HasValue()) { allArgs.Add($"-out:\"{outputName.Value()}\""); } allArgs.AddRange(analyzers.Values.Select(a => $"-a:\"{a}\"")); allArgs.AddRange(references.Values.Select(r => $"-r:\"{r}\"")); // Resource has two parts separated by a comma // Only the first should be quoted. This is handled // in dotnet-compile where the information is present. allArgs.AddRange(resources.Values.Select(resource => $"-resource:{resource}")); allArgs.AddRange(sources.Values.Select(s => $"\"{s}\"")); var rsp = Path.Combine(tempOutput.Value(), "dotnet-compile-csc.rsp"); File.WriteAllLines(rsp, allArgs, Encoding.UTF8); // Execute CSC! var result = RunCsc(new string[] { $"-noconfig", "@" + $"{rsp}" }) .WorkingDirectory(Directory.GetCurrentDirectory()) .ForwardStdErr() .ForwardStdOut() .Execute(); return result.ExitCode; }); try { return app.Execute(args); } catch (Exception ex) { #if DEBUG Reporter.Error.WriteLine(ex.ToString()); #else Reporter.Error.WriteLine(ex.Message); #endif return ExitFailed; } } // TODO: Review if this is the place for default options private static IEnumerable GetDefaultOptions() { var args = new List() { "-nostdlib", "-nologo", }; return args; } private static IEnumerable TranslateCommonOptions(CommonCompilerOptions options, string outputName) { List commonArgs = new List(); if (options.Defines != null) { commonArgs.AddRange(options.Defines.Select(def => $"-d:{def}")); } if (options.SuppressWarnings != null) { commonArgs.AddRange(options.SuppressWarnings.Select(w => $"-nowarn:{w}")); } // Additional arguments are added verbatim if (options.AdditionalArguments != null) { commonArgs.AddRange(options.AdditionalArguments); } if (options.LanguageVersion != null) { commonArgs.Add($"-langversion:{GetLanguageVersion(options.LanguageVersion)}"); } if (options.Platform != null) { commonArgs.Add($"-platform:{options.Platform}"); } if (options.AllowUnsafe == true) { commonArgs.Add("-unsafe"); } if (options.WarningsAsErrors == true) { commonArgs.Add("-warnaserror"); } if (options.Optimize == true) { commonArgs.Add("-optimize"); } if (options.KeyFile != null) { commonArgs.Add($"-keyfile:\"{options.KeyFile}\""); // If we're not on Windows, full signing isn't supported, so we'll // public sign, unless the public sign switch has explicitly been // set to false if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && options.PublicSign == null) { commonArgs.Add("-publicsign"); } } if (options.DelaySign == true) { commonArgs.Add("-delaysign"); } if (options.PublicSign == true) { commonArgs.Add("-publicsign"); } if (options.GenerateXmlDocumentation == true) { commonArgs.Add($"-doc:\"{Path.ChangeExtension(outputName, "xml")}\""); } if (options.EmitEntryPoint != true) { commonArgs.Add("-t:library"); } if (string.IsNullOrEmpty(options.DebugType)) { commonArgs.Add(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "-debug:full" : "-debug:portable"); } else { commonArgs.Add(options.DebugType == "portable" ? "-debug:portable" : "-debug:full"); } return commonArgs; } private static string GetLanguageVersion(string languageVersion) { // project.json supports the enum that the roslyn APIs expose if (languageVersion?.StartsWith("csharp", StringComparison.OrdinalIgnoreCase) == true) { // We'll be left with the number csharp6 = 6 return languageVersion.Substring("csharp".Length); } return languageVersion; } private static Command RunCsc(string[] cscArgs) { // Locate CoreRun return Command.Create("csc.dll", cscArgs); } } }