From 81a26537500dacdc9a37904279ef3417310f7136 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 12 Oct 2015 15:31:50 -0700 Subject: [PATCH] Adding the Csi task to MSBuild. --- src/Compilers/Core/MSBuildTask/Csc.cs | 118 +++---- src/Compilers/Core/MSBuildTask/Csi.cs | 40 +++ .../Core/MSBuildTask/InteractiveCompiler.cs | 287 ++++++++++++++++++ .../Core/MSBuildTask/MSBuildTask.csproj | 2 + .../Core/MSBuildTask/ManagedCompiler.cs | 129 ++++---- .../Core/MSBuildTaskTests/CsiTests.cs | 49 +++ .../MSBuildTaskTests/MSBuildTaskTests.csproj | 3 +- 7 files changed, 504 insertions(+), 124 deletions(-) create mode 100644 src/Compilers/Core/MSBuildTask/Csi.cs create mode 100644 src/Compilers/Core/MSBuildTask/InteractiveCompiler.cs create mode 100644 src/Compilers/Core/MSBuildTaskTests/CsiTests.cs diff --git a/src/Compilers/Core/MSBuildTask/Csc.cs b/src/Compilers/Core/MSBuildTask/Csc.cs index bf594ad9c76..cd5d62471b9 100644 --- a/src/Compilers/Core/MSBuildTask/Csc.cs +++ b/src/Compilers/Core/MSBuildTask/Csc.cs @@ -208,44 +208,44 @@ protected override string GenerateFullPathToTool() /// protected internal override void AddResponseFileCommands(CommandLineBuilderExtension commandLine) { - commandLine.AppendSwitchIfNotNull("/lib:", this.AdditionalLibPaths, ","); - commandLine.AppendPlusOrMinusSwitch("/unsafe", this._store, "AllowUnsafeBlocks"); - commandLine.AppendPlusOrMinusSwitch("/checked", this._store, "CheckForOverflowUnderflow"); - commandLine.AppendSwitchWithSplitting("/nowarn:", this.DisabledWarnings, ",", ';', ','); - commandLine.AppendWhenTrue("/fullpaths", this._store, "GenerateFullPaths"); - commandLine.AppendSwitchIfNotNull("/langversion:", this.LangVersion); - commandLine.AppendSwitchIfNotNull("/moduleassemblyname:", this.ModuleAssemblyName); - commandLine.AppendSwitchIfNotNull("/pdb:", this.PdbFile); - commandLine.AppendPlusOrMinusSwitch("/nostdlib", this._store, "NoStandardLib"); - commandLine.AppendSwitchIfNotNull("/platform:", this.PlatformWith32BitPreference); - commandLine.AppendSwitchIfNotNull("/errorreport:", this.ErrorReport); - commandLine.AppendSwitchWithInteger("/warn:", this._store, "WarningLevel"); - commandLine.AppendSwitchIfNotNull("/doc:", this.DocumentationFile); - commandLine.AppendSwitchIfNotNull("/baseaddress:", this.BaseAddress); - commandLine.AppendSwitchUnquotedIfNotNull("/define:", this.GetDefineConstantsSwitch(this.DefineConstants)); - commandLine.AppendSwitchIfNotNull("/win32res:", this.Win32Resource); - commandLine.AppendSwitchIfNotNull("/main:", this.MainEntryPoint); - commandLine.AppendSwitchIfNotNull("/appconfig:", this.ApplicationConfiguration); - commandLine.AppendWhenTrue("/errorendlocation", this._store, "ErrorEndLocation"); - commandLine.AppendSwitchIfNotNull("/preferreduilang:", this.PreferredUILang); - commandLine.AppendPlusOrMinusSwitch("/highentropyva", this._store, "HighEntropyVA"); + commandLine.AppendSwitchIfNotNull("/lib:", AdditionalLibPaths, ","); + commandLine.AppendPlusOrMinusSwitch("/unsafe", _store, nameof(AllowUnsafeBlocks)); + commandLine.AppendPlusOrMinusSwitch("/checked", _store, nameof(CheckForOverflowUnderflow)); + commandLine.AppendSwitchWithSplitting("/nowarn:", DisabledWarnings, ",", ';', ','); + commandLine.AppendWhenTrue("/fullpaths", _store, nameof(GenerateFullPaths)); + commandLine.AppendSwitchIfNotNull("/langversion:", LangVersion); + commandLine.AppendSwitchIfNotNull("/moduleassemblyname:", ModuleAssemblyName); + commandLine.AppendSwitchIfNotNull("/pdb:", PdbFile); + commandLine.AppendPlusOrMinusSwitch("/nostdlib", _store, nameof(NoStandardLib)); + commandLine.AppendSwitchIfNotNull("/platform:", PlatformWith32BitPreference); + commandLine.AppendSwitchIfNotNull("/errorreport:", ErrorReport); + commandLine.AppendSwitchWithInteger("/warn:", _store, nameof(WarningLevel)); + commandLine.AppendSwitchIfNotNull("/doc:", DocumentationFile); + commandLine.AppendSwitchIfNotNull("/baseaddress:", BaseAddress); + commandLine.AppendSwitchUnquotedIfNotNull("/define:", GetDefineConstantsSwitch(DefineConstants, Log)); + commandLine.AppendSwitchIfNotNull("/win32res:", Win32Resource); + commandLine.AppendSwitchIfNotNull("/main:", MainEntryPoint); + commandLine.AppendSwitchIfNotNull("/appconfig:", ApplicationConfiguration); + commandLine.AppendWhenTrue("/errorendlocation", _store, nameof(ErrorEndLocation)); + commandLine.AppendSwitchIfNotNull("/preferreduilang:", PreferredUILang); + commandLine.AppendPlusOrMinusSwitch("/highentropyva", _store, nameof(HighEntropyVA)); // If not design time build and the globalSessionGuid property was set then add a -globalsessionguid: bool designTime = false; - if (this.HostObject != null) + if (HostObject != null) { - var csHost = this.HostObject as ICscHostObject; + var csHost = HostObject as ICscHostObject; designTime = csHost.IsDesignTime(); } if (!designTime) { - if (!string.IsNullOrWhiteSpace(this.VsSessionGuid)) + if (!string.IsNullOrWhiteSpace(VsSessionGuid)) { - commandLine.AppendSwitchIfNotNull("/sqmsessionguid:", this.VsSessionGuid); + commandLine.AppendSwitchIfNotNull("/sqmsessionguid:", VsSessionGuid); } } - this.AddReferencesToCommandLine(commandLine); + AddReferencesToCommandLine(commandLine, References); base.AddResponseFileCommands(commandLine); @@ -264,16 +264,16 @@ protected internal override void AddResponseFileCommands(CommandLineBuilderExten // /warnaserror- // is just shorthand for: // /warnaserror-: - commandLine.AppendSwitchWithSplitting("/warnaserror+:", this.WarningsAsErrors, ",", ';', ','); - commandLine.AppendSwitchWithSplitting("/warnaserror-:", this.WarningsNotAsErrors, ",", ';', ','); + commandLine.AppendSwitchWithSplitting("/warnaserror+:", WarningsAsErrors, ",", ';', ','); + commandLine.AppendSwitchWithSplitting("/warnaserror-:", WarningsNotAsErrors, ",", ';', ','); // It's a good idea for the response file to be the very last switch passed, just // from a predictability perspective. It also solves the problem that a dogfooder // ran into, which is described in an email thread attached to bug VSWhidbey 146883. // See also bugs 177762 and 118307 for additional bugs related to response file position. - if (this.ResponseFiles != null) + if (ResponseFiles != null) { - foreach (ITaskItem response in this.ResponseFiles) + foreach (ITaskItem response in ResponseFiles) { commandLine.AppendSwitchIfNotNull("@", response.ItemSpec); } @@ -294,14 +294,14 @@ protected internal override void AddResponseFileCommands(CommandLineBuilderExten /// list of aliases, and if any of the aliases specified is the string "global", /// then we add that reference to the command-line without an alias. /// - private void AddReferencesToCommandLine - ( - CommandLineBuilderExtension commandLine - ) + internal static void AddReferencesToCommandLine( + CommandLineBuilderExtension commandLine, + ITaskItem[] references, + bool isInteractive = false) { // If there were no references passed in, don't add any /reference: switches // on the command-line. - if ((this.References == null) || (this.References.Length == 0)) + if (references == null) { return; } @@ -309,22 +309,24 @@ CommandLineBuilderExtension commandLine // Loop through all the references passed in. We'll be adding separate // /reference: switches for each reference, and in some cases even multiple // /reference: switches per reference. - foreach (ITaskItem reference in this.References) + foreach (ITaskItem reference in references) { // See if there was an "Alias" attribute on the reference. string aliasString = reference.GetMetadata("Aliases"); string switchName = "/reference:"; - bool embed = Utilities.TryConvertItemMetadataToBool(reference, - "EmbedInteropTypes"); - - if (embed == true) + if (!isInteractive) { - switchName = "/link:"; - } + bool embed = Utilities.TryConvertItemMetadataToBool(reference, + "EmbedInteropTypes"); - if ((aliasString == null) || (aliasString.Length == 0)) + if (embed) + { + switchName = "/link:"; + } + } + if (string.IsNullOrEmpty(aliasString)) { // If there was no "Alias" attribute, just add this as a global reference. commandLine.AppendSwitchIfNotNull(switchName, reference.ItemSpec); @@ -365,7 +367,7 @@ CommandLineBuilderExtension commandLine // The alias called "global" is special. It means that we don't // give it an alias on the command-line. - if (String.Compare("global", trimmedAlias, StringComparison.OrdinalIgnoreCase) == 0) + if (string.Compare("global", trimmedAlias, StringComparison.OrdinalIgnoreCase) == 0) { commandLine.AppendSwitchIfNotNull(switchName, reference.ItemSpec); } @@ -395,7 +397,7 @@ CommandLineBuilderExtension commandLine /// other words, a constant is either defined or not defined ... it can't have /// an actual value. /// - internal string GetDefineConstantsSwitch(string originalDefineConstants) + internal static string GetDefineConstantsSwitch(string originalDefineConstants, TaskLoggingHelper log) { if (originalDefineConstants == null) { @@ -424,7 +426,7 @@ internal string GetDefineConstantsSwitch(string originalDefineConstants) } else if (singleIdentifier.Length > 0) { - Log.LogWarningWithCodeFromResources("Csc_InvalidParameterWarning", "/define:", singleIdentifier); + log.LogWarningWithCodeFromResources("Csc_InvalidParameterWarning", "/define:", singleIdentifier); } } @@ -465,7 +467,7 @@ internal string GetDefineConstantsSwitch(string originalDefineConstants) private bool InitializeHostCompiler(ICscHostObject cscHostObject) { bool success; - this.HostCompilerSupportsAllParameters = this.UseHostCompilerIfAvailable; + HostCompilerSupportsAllParameters = UseHostCompilerIfAvailable; string param = "Unknown"; try @@ -485,7 +487,7 @@ private bool InitializeHostCompiler(ICscHostObject cscHostObject) } catch (Exception e) when (!Utilities.IsCriticalException(e)) { - if (this.HostCompilerSupportsAllParameters) + if (HostCompilerSupportsAllParameters) { // If the host compiler doesn't support everything we need, we're going to end up // shelling out to the command-line compiler anyway. That means the command-line @@ -513,7 +515,7 @@ private bool InitializeHostCompiler(ICscHostObject cscHostObject) CheckHostObjectSupport(param = nameof(EmitDebugInformation), cscHostObject.SetEmitDebugInformation(EmitDebugInformation)); CheckHostObjectSupport(param = nameof(DebugType), cscHostObject.SetDebugType(DebugType)); - CheckHostObjectSupport(param = nameof(DefineConstants), cscHostObject.SetDefineConstants(GetDefineConstantsSwitch(DefineConstants))); + CheckHostObjectSupport(param = nameof(DefineConstants), cscHostObject.SetDefineConstants(GetDefineConstantsSwitch(DefineConstants, Log))); CheckHostObjectSupport(param = nameof(DelaySign), cscHostObject.SetDelaySign((_store["DelaySign"] != null), DelaySign)); CheckHostObjectSupport(param = nameof(DisabledWarnings), cscHostObject.SetDisabledWarnings(DisabledWarnings)); CheckHostObjectSupport(param = nameof(DocumentationFile), cscHostObject.SetDocumentationFile(DocumentationFile)); @@ -581,7 +583,7 @@ private bool InitializeHostCompiler(ICscHostObject cscHostObject) { // If we have been given a property that the host compiler doesn't support // then we need to state that we are falling back to the command line compiler - if (!String.IsNullOrEmpty(Win32Manifest)) + if (!string.IsNullOrEmpty(Win32Manifest)) { CheckHostObjectSupport(param = nameof(Win32Manifest), resultFromHostObjectSetOperation: false); } @@ -599,7 +601,7 @@ private bool InitializeHostCompiler(ICscHostObject cscHostObject) { // If we have been given a property that the host compiler doesn't support // then we need to state that we are falling back to the command line compiler - if (!String.IsNullOrEmpty(ApplicationConfiguration)) + if (!string.IsNullOrEmpty(ApplicationConfiguration)) { CheckHostObjectSupport(nameof(ApplicationConfiguration), resultFromHostObjectSetOperation: false); } @@ -610,14 +612,14 @@ private bool InitializeHostCompiler(ICscHostObject cscHostObject) // Null is supported because it means that option should be omitted, and compiler default used - obviously always valid. // Explicitly specified name of current locale is also supported, since it is effectively a no-op. // Other options are not supported since in-proc compiler always uses current locale. - if (!String.IsNullOrEmpty(PreferredUILang) && !String.Equals(PreferredUILang, System.Globalization.CultureInfo.CurrentUICulture.Name, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(PreferredUILang) && !string.Equals(PreferredUILang, System.Globalization.CultureInfo.CurrentUICulture.Name, StringComparison.OrdinalIgnoreCase)) { CheckHostObjectSupport(nameof(PreferredUILang), resultFromHostObjectSetOperation: false); } } catch (Exception e) when (!Utilities.IsCriticalException(e)) { - if (this.HostCompilerSupportsAllParameters) + if (HostCompilerSupportsAllParameters) { // If the host compiler doesn't support everything we need, we're going to end up // shelling out to the command-line compiler anyway. That means the command-line @@ -634,7 +636,7 @@ private bool InitializeHostCompiler(ICscHostObject cscHostObject) success = cscHostObject.EndInitialization(out errorMessage, out errorCode); - if (this.HostCompilerSupportsAllParameters) + if (HostCompilerSupportsAllParameters) { // If the host compiler doesn't support everything we need, we're going to end up // shelling out to the command-line compiler anyway. That means the command-line @@ -669,7 +671,7 @@ private bool InitializeHostCompiler(ICscHostObject cscHostObject) /// RGoel protected override HostObjectInitializationStatus InitializeHostObject() { - if (this.HostObject != null) + if (HostObject != null) { // When the host object was passed into the task, it was passed in as a generic // "Object" (because ITask interface obviously can't have any Csc-specific stuff @@ -680,7 +682,7 @@ protected override HostObjectInitializationStatus InitializeHostObject() // NOTE: For compat reasons this must remain ICscHostObject // we can dynamically test for smarter interfaces later.. - using (RCWForCurrentContext hostObject = new RCWForCurrentContext(this.HostObject as ICscHostObject)) + using (RCWForCurrentContext hostObject = new RCWForCurrentContext(HostObject as ICscHostObject)) { ICscHostObject cscHostObject = hostObject.RCW; @@ -738,9 +740,9 @@ protected override HostObjectInitializationStatus InitializeHostObject() /// RGoel protected override bool CallHostObjectToExecute() { - Debug.Assert(this.HostObject != null, "We should not be here if the host object has not been set."); + Debug.Assert(HostObject != null, "We should not be here if the host object has not been set."); - ICscHostObject cscHostObject = this.HostObject as ICscHostObject; + ICscHostObject cscHostObject = HostObject as ICscHostObject; Debug.Assert(cscHostObject != null, "Wrong kind of host object passed in!"); return cscHostObject.Compile(); } diff --git a/src/Compilers/Core/MSBuildTask/Csi.cs b/src/Compilers/Core/MSBuildTask/Csi.cs new file mode 100644 index 00000000000..47561b8c4f4 --- /dev/null +++ b/src/Compilers/Core/MSBuildTask/Csi.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text; + +using Microsoft.Build.Framework; +using Microsoft.CodeAnalysis.CSharp; + +namespace Microsoft.CodeAnalysis.BuildTasks +{ + public class Csi : InteractiveCompiler + { + #region Properties - Please keep these alphabetized. + + // These are the parameters specific to Csi. + // The ones shared between Csi and Vbi are defined in InteractiveCompiler.cs, which is the base class. + + #endregion + + #region Tool Members + /// + /// Return the name of the tool to execute. + /// + protected override string ToolName => "csi.exe"; + #endregion + + #region Interactive Compiler Members + protected override void AddResponseFileCommands(CommandLineBuilderExtension commandLine) + { + commandLine.AppendSwitchIfNotNull("/lib:", AdditionalLibPaths, ","); + commandLine.AppendSwitchIfNotNull("/loadpaths:", AdditionalLoadPaths, ","); + commandLine.AppendSwitchIfNotNull("/imports:", Imports, ";"); + + Csc.AddReferencesToCommandLine(commandLine, References, isInteractive: true); + + base.AddResponseFileCommands(commandLine); + } + #endregion + } +} diff --git a/src/Compilers/Core/MSBuildTask/InteractiveCompiler.cs b/src/Compilers/Core/MSBuildTask/InteractiveCompiler.cs new file mode 100644 index 00000000000..512a2ecc5bc --- /dev/null +++ b/src/Compilers/Core/MSBuildTask/InteractiveCompiler.cs @@ -0,0 +1,287 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Linq; +using Roslyn.Utilities; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.CodeAnalysis.BuildTasks +{ + /// + /// This class defines all of the common stuff that is shared between the Vbc and Csc tasks. + /// This class is not instantiatable as a Task just by itself. + /// + public abstract class InteractiveCompiler : ToolTask + { + internal readonly PropertyDictionary _store = new PropertyDictionary(); + + public InteractiveCompiler() + { + TaskResources = ErrorString.ResourceManager; + } + + #region Properties - Please keep these alphabetized. + public string[] AdditionalLibPaths + { + set + { + _store[nameof(AdditionalLibPaths)] = value; + } + + get + { + return (string[])_store[nameof(AdditionalLibPaths)]; + } + } + + public string[] AdditionalLoadPaths + { + set + { + _store[nameof(AdditionalLoadPaths)] = value; + } + + get + { + return (string[])_store[nameof(AdditionalLoadPaths)]; + } + } + + [Output] + public ITaskItem[] CommandLineArgs + { + set + { + _store[nameof(CommandLineArgs)] = value; + } + + get + { + return (ITaskItem[])_store[nameof(CommandLineArgs)]; + } + } + + public string Features + { + set + { + _store[nameof(Features)] = value; + } + + get + { + return (string)_store[nameof(Features)]; + } + } + + public ITaskItem[] Imports + { + set + { + _store[nameof(Imports)] = value; + } + + get + { + return (ITaskItem[])_store[nameof(Imports)]; + } + } + + public bool ProvideCommandLineArgs + { + set + { + _store[nameof(ProvideCommandLineArgs)] = value; + } + + get + { + return _store.GetOrDefault(nameof(ProvideCommandLineArgs), false); + } + } + + public ITaskItem[] References + { + set + { + _store[nameof(References)] = value; + } + + get + { + return (ITaskItem[])_store[nameof(References)]; + } + } + + public ITaskItem[] ResponseFiles + { + set + { + _store[nameof(ResponseFiles)] = value; + } + + get + { + return (ITaskItem[])_store[nameof(ResponseFiles)]; + } + } + + public string[] ScriptArguments + { + set + { + _store[nameof(ScriptArguments)] = value; + } + + get + { + return (string[])_store[nameof(ScriptArguments)]; + } + } + + public ITaskItem[] ScriptResponseFiles + { + set + { + _store[nameof(ScriptResponseFiles)] = value; + } + + get + { + return (ITaskItem[])_store[nameof(ScriptResponseFiles)]; + } + } + + public bool SkipInteractiveExecution + { + set + { + _store[nameof(SkipInteractiveExecution)] = value; + } + + get + { + return _store.GetOrDefault(nameof(SkipInteractiveExecution), false); + } + } + + public ITaskItem Source + { + set + { + _store[nameof(Source)] = value; + } + + get + { + return (ITaskItem)_store[nameof(Source)]; + } + } + #endregion + + #region Tool Members + protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) + { + if (ProvideCommandLineArgs) + { + CommandLineArgs = GetArguments(commandLineCommands, responseFileCommands).Select(arg => new TaskItem(arg)).ToArray(); + } + + return (SkipInteractiveExecution) ? 0 : base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); + } + + public string GenerateCommandLineContents() => GenerateCommandLineCommands(); + + protected override string GenerateCommandLineCommands() + { + var commandLineBuilder = new CommandLineBuilderExtension(); + AddCommandLineCommands(commandLineBuilder); + return commandLineBuilder.ToString(); + } + + /// + /// Return the path to the tool to execute. + /// + protected override string GenerateFullPathToTool() + { + string pathToTool = ToolLocationHelper.GetPathToBuildToolsFile(ToolName, ToolLocationHelper.CurrentToolsVersion); + + if (pathToTool == null) + { + pathToTool = ToolLocationHelper.GetPathToDotNetFrameworkFile(ToolName, TargetDotNetFrameworkVersion.VersionLatest); + + if (pathToTool == null) + { + Log.LogErrorWithCodeFromResources("General_FrameworksFileNotFound", ToolName, ToolLocationHelper.GetDotNetFrameworkVersionFolderPrefix(TargetDotNetFrameworkVersion.VersionLatest)); + } + } + + return pathToTool; + } + + public string GenerateResponseFileContents() => GenerateResponseFileCommands(); + + protected override string GenerateResponseFileCommands() + { + var commandLineBuilder = new CommandLineBuilderExtension(); + AddResponseFileCommands(commandLineBuilder); + return commandLineBuilder.ToString(); + } + + protected override bool ValidateParameters() => ManagedCompiler.ListHasNoDuplicateItems(Sources, nameof(Sources), Log); + #endregion + + /// + /// Fills the provided CommandLineBuilderExtension with those switches and other information that can't go into a response file and + /// must go directly onto the command line. + /// + protected virtual void AddCommandLineCommands(CommandLineBuilderExtension commandLine) + { + } + + /// + /// Fills the provided CommandLineBuilderExtension with those switches and other information that can go into a response file. + /// + protected virtual void AddResponseFileCommands(CommandLineBuilderExtension commandLine) + { + commandLine.AppendSwitch("/i-"); + + ManagedCompiler.AddFeatures(commandLine, Features); + + if (ResponseFiles != null) + { + foreach (var response in ResponseFiles) + { + commandLine.AppendSwitchIfNotNull("@", response.ItemSpec); + } + } + + commandLine.AppendFileNameIfNotNull(Source); + + foreach (var scriptArgument in ScriptArguments) + { + commandLine.AppendTextUnquoted(scriptArgument); + } + + if (ResponseFiles != null) + { + foreach (var scriptResponse in ScriptResponseFiles) + { + commandLine.AppendSwitchIfNotNull("@", scriptResponse.ItemSpec); + } + } + } + + /// + /// Get the command line arguments to pass to the compiler. + /// + private string[] GetArguments(string commandLineCommands, string responseFileCommands) + { + var commandLineArguments = CommandLineParser.SplitCommandLineIntoArguments(commandLineCommands, removeHashComments: true); + var responseFileArguments = CommandLineParser.SplitCommandLineIntoArguments(responseFileCommands, removeHashComments: true); + return commandLineArguments.Concat(responseFileArguments).ToArray(); + } + } +} diff --git a/src/Compilers/Core/MSBuildTask/MSBuildTask.csproj b/src/Compilers/Core/MSBuildTask/MSBuildTask.csproj index d95e832347a..56e65047e83 100644 --- a/src/Compilers/Core/MSBuildTask/MSBuildTask.csproj +++ b/src/Compilers/Core/MSBuildTask/MSBuildTask.csproj @@ -53,12 +53,14 @@ + True True ErrorString.resx + diff --git a/src/Compilers/Core/MSBuildTask/ManagedCompiler.cs b/src/Compilers/Core/MSBuildTask/ManagedCompiler.cs index f49e1a92726..8eae8073ef4 100644 --- a/src/Compilers/Core/MSBuildTask/ManagedCompiler.cs +++ b/src/Compilers/Core/MSBuildTask/ManagedCompiler.cs @@ -27,7 +27,7 @@ public abstract class ManagedCompiler : ToolTask public ManagedCompiler() { - this.TaskResources = ErrorString.ResourceManager; + TaskResources = ErrorString.ResourceManager; } #region Properties @@ -311,8 +311,8 @@ internal string PlatformWith32BitPreference { get { - string platform = this.Platform; - if ((String.IsNullOrEmpty(platform) || platform.Equals("anycpu", StringComparison.OrdinalIgnoreCase)) && this.Prefer32Bit) + string platform = Platform; + if ((string.IsNullOrEmpty(platform) || platform.Equals("anycpu", StringComparison.OrdinalIgnoreCase)) && Prefer32Bit) { platform = "anycpu32bitpreferred"; } @@ -348,7 +348,7 @@ protected override int ExecuteTool(string pathToTool, string responseFileCommand return 0; } - if (!UseSharedCompilation || !String.IsNullOrEmpty(this.ToolPath)) + if (!UseSharedCompilation || !string.IsNullOrEmpty(ToolPath)) { return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); } @@ -446,10 +446,10 @@ private string LibDirectoryToUse() string libDirectory = Environment.GetEnvironmentVariable("LIB"); // Now go through additional environment variables. - string[] additionalVariables = this.EnvironmentVariables; + string[] additionalVariables = EnvironmentVariables; if (additionalVariables != null) { - foreach (string var in this.EnvironmentVariables) + foreach (string var in EnvironmentVariables) { if (var.StartsWith("LIB=", StringComparison.OrdinalIgnoreCase)) { @@ -482,7 +482,7 @@ private int HandleResponse(BuildResponse response, string pathToTool, string res case BuildResponse.ResponseType.Completed: var completedResponse = (CompletedBuildResponse)response; - LogMessages(completedResponse.Output, this.StandardOutputImportanceToUse); + LogMessages(completedResponse.Output, StandardOutputImportanceToUse); if (LogStandardErrorAsError) { @@ -490,7 +490,7 @@ private int HandleResponse(BuildResponse response, string pathToTool, string res } else { - LogMessages(completedResponse.ErrorOutput, this.StandardErrorImportanceToUse); + LogMessages(completedResponse.ErrorOutput, StandardErrorImportanceToUse); } return completedResponse.ReturnCode; @@ -567,7 +567,7 @@ protected override string GenerateCommandLineCommands() /// protected internal virtual void AddCommandLineCommands(CommandLineBuilderExtension commandLine) { - commandLine.AppendWhenTrue("/noconfig", this._store, "NoConfig"); + commandLine.AppendWhenTrue("/noconfig", _store, nameof(NoConfig)); } /// @@ -582,7 +582,7 @@ protected internal virtual void AddResponseFileCommands(CommandLineBuilderExtens (OutputAssembly == null) && (Sources != null) && (Sources.Length > 0) && - (this.ResponseFiles == null) // The response file may already have a /out: switch in it, so don't try to be smart here. + (ResponseFiles == null) // The response file may already have a /out: switch in it, so don't try to be smart here. ) { try @@ -593,11 +593,11 @@ protected internal virtual void AddResponseFileCommands(CommandLineBuilderExtens { throw new ArgumentException(e.Message, "Sources"); } - if (String.Compare(TargetType, "library", StringComparison.OrdinalIgnoreCase) == 0) + if (string.Compare(TargetType, "library", StringComparison.OrdinalIgnoreCase) == 0) { OutputAssembly.ItemSpec += ".dll"; } - else if (String.Compare(TargetType, "module", StringComparison.OrdinalIgnoreCase) == 0) + else if (string.Compare(TargetType, "module", StringComparison.OrdinalIgnoreCase) == 0) { OutputAssembly.ItemSpec += ".netmodule"; } @@ -607,8 +607,8 @@ protected internal virtual void AddResponseFileCommands(CommandLineBuilderExtens } } - commandLine.AppendSwitchIfNotNull("/addmodule:", this.AddModules, ","); - commandLine.AppendSwitchWithInteger("/codepage:", this._store, "CodePage"); + commandLine.AppendSwitchIfNotNull("/addmodule:", AddModules, ","); + commandLine.AppendSwitchWithInteger("/codepage:", _store, nameof(CodePage)); ConfigureDebugProperties(); @@ -616,36 +616,36 @@ protected internal virtual void AddResponseFileCommands(CommandLineBuilderExtens // because it's more specific. Order matters on the command-line, and the last one wins. // /debug+ is just a shorthand for /debug:full. And /debug- is just a shorthand for /debug:none. - commandLine.AppendPlusOrMinusSwitch("/debug", this._store, "EmitDebugInformation"); - commandLine.AppendSwitchIfNotNull("/debug:", this.DebugType); + commandLine.AppendPlusOrMinusSwitch("/debug", _store, nameof(EmitDebugInformation)); + commandLine.AppendSwitchIfNotNull("/debug:", DebugType); - commandLine.AppendPlusOrMinusSwitch("/delaysign", this._store, "DelaySign"); + commandLine.AppendPlusOrMinusSwitch("/delaysign", _store, nameof(DelaySign)); - commandLine.AppendSwitchWithInteger("/filealign:", this._store, "FileAlignment"); - commandLine.AppendSwitchIfNotNull("/keycontainer:", this.KeyContainer); - commandLine.AppendSwitchIfNotNull("/keyfile:", this.KeyFile); + commandLine.AppendSwitchWithInteger("/filealign:", _store, nameof(FileAlignment)); + commandLine.AppendSwitchIfNotNull("/keycontainer:", KeyContainer); + commandLine.AppendSwitchIfNotNull("/keyfile:", KeyFile); // If the strings "LogicalName" or "Access" ever change, make sure to search/replace everywhere in vsproject. - commandLine.AppendSwitchIfNotNull("/linkresource:", this.LinkResources, new string[] { "LogicalName", "Access" }); - commandLine.AppendWhenTrue("/nologo", this._store, "NoLogo"); - commandLine.AppendWhenTrue("/nowin32manifest", this._store, "NoWin32Manifest"); - commandLine.AppendPlusOrMinusSwitch("/optimize", this._store, "Optimize"); - commandLine.AppendPlusOrMinusSwitch("/deterministic", this._store, "Deterministic"); - commandLine.AppendSwitchIfNotNull("/out:", this.OutputAssembly); - commandLine.AppendSwitchIfNotNull("/ruleset:", this.CodeAnalysisRuleSet); - commandLine.AppendSwitchIfNotNull("/errorlog:", this.ErrorLog); - commandLine.AppendSwitchIfNotNull("/subsystemversion:", this.SubsystemVersion); - commandLine.AppendWhenTrue("/reportanalyzer", this._store, "ReportAnalyzer"); + commandLine.AppendSwitchIfNotNull("/linkresource:", LinkResources, new string[] { "LogicalName", "Access" }); + commandLine.AppendWhenTrue("/nologo", _store, nameof(NoLogo)); + commandLine.AppendWhenTrue("/nowin32manifest", _store, nameof(NoWin32Manifest)); + commandLine.AppendPlusOrMinusSwitch("/optimize", _store, nameof(Optimize)); + commandLine.AppendPlusOrMinusSwitch("/deterministic", _store, nameof(Deterministic)); + commandLine.AppendSwitchIfNotNull("/out:", OutputAssembly); + commandLine.AppendSwitchIfNotNull("/ruleset:", CodeAnalysisRuleSet); + commandLine.AppendSwitchIfNotNull("/errorlog:", ErrorLog); + commandLine.AppendSwitchIfNotNull("/subsystemversion:", SubsystemVersion); + commandLine.AppendWhenTrue("/reportanalyzer", _store, nameof(ReportAnalyzer)); // If the strings "LogicalName" or "Access" ever change, make sure to search/replace everywhere in vsproject. - commandLine.AppendSwitchIfNotNull("/resource:", this.Resources, new string[] { "LogicalName", "Access" }); - commandLine.AppendSwitchIfNotNull("/target:", this.TargetType); - commandLine.AppendPlusOrMinusSwitch("/warnaserror", this._store, "TreatWarningsAsErrors"); - commandLine.AppendWhenTrue("/utf8output", this._store, "Utf8Output"); - commandLine.AppendSwitchIfNotNull("/win32icon:", this.Win32Icon); - commandLine.AppendSwitchIfNotNull("/win32manifest:", this.Win32Manifest); + commandLine.AppendSwitchIfNotNull("/resource:", Resources, new string[] { "LogicalName", "Access" }); + commandLine.AppendSwitchIfNotNull("/target:", TargetType); + commandLine.AppendPlusOrMinusSwitch("/warnaserror", _store, nameof(TreatWarningsAsErrors)); + commandLine.AppendWhenTrue("/utf8output", _store, nameof(Utf8Output)); + commandLine.AppendSwitchIfNotNull("/win32icon:", Win32Icon); + commandLine.AppendSwitchIfNotNull("/win32manifest:", Win32Manifest); - this.AddFeatures(commandLine); - this.AddAnalyzersToCommandLine(commandLine); - this.AddAdditionalFilesToCommandLine(commandLine); + AddFeatures(commandLine, Features); + AddAnalyzersToCommandLine(commandLine, Analyzers); + AddAdditionalFilesToCommandLine(commandLine); // Append the sources. commandLine.AppendFileNamesIfNotNull(Sources, " "); @@ -654,10 +654,8 @@ protected internal virtual void AddResponseFileCommands(CommandLineBuilderExtens /// /// Adds a "/features:" switch to the command line for each provided feature. /// - /// - private void AddFeatures(CommandLineBuilderExtension commandLine) + internal static void AddFeatures(CommandLineBuilderExtension commandLine, string features) { - var features = Features; if (string.IsNullOrEmpty(features)) { return; @@ -672,16 +670,16 @@ private void AddFeatures(CommandLineBuilderExtension commandLine) /// /// Adds a "/analyzer:" switch to the command line for each provided analyzer. /// - private void AddAnalyzersToCommandLine(CommandLineBuilderExtension commandLine) + internal static void AddAnalyzersToCommandLine(CommandLineBuilderExtension commandLine, ITaskItem[] analyzers) { // If there were no analyzers passed in, don't add any /analyzer: switches // on the command-line. - if ((this.Analyzers == null) || (this.Analyzers.Length == 0)) + if (analyzers == null) { return; } - foreach (ITaskItem analyzer in this.Analyzers) + foreach (ITaskItem analyzer in analyzers) { commandLine.AppendSwitchIfNotNull("/analyzer:", analyzer.ItemSpec); } @@ -694,12 +692,12 @@ private void AddAdditionalFilesToCommandLine(CommandLineBuilderExtension command { // If there were no additional files passed in, don't add any /additionalfile: switches // on the command-line. - if ((this.AdditionalFiles == null) || (this.AdditionalFiles.Length == 0)) + if (AdditionalFiles == null) { return; } - foreach (ITaskItem additionalFile in this.AdditionalFiles) + foreach (ITaskItem additionalFile in AdditionalFiles) { commandLine.AppendSwitchIfNotNull("/additionalfile:", additionalFile.ItemSpec); } @@ -733,13 +731,13 @@ private void ConfigureDebugProperties() { // If debug type is set we need to take some action depending on the value. If debugtype is not set // We don't need to modify the EmitDebugInformation switch as its value will be used as is. - if (_store["DebugType"] != null) + if (_store[nameof(DebugType)] != null) { // If debugtype is none then only show debug- else use the debug type and the debugsymbols as is. - if (string.Compare((string)_store["DebugType"], "none", StringComparison.OrdinalIgnoreCase) == 0) + if (string.Compare((string)_store[nameof(DebugType)], "none", StringComparison.OrdinalIgnoreCase) == 0) { - _store["DebugType"] = null; - _store["EmitDebugInformation"] = false; + _store[nameof(DebugType)] = null; + _store[nameof(EmitDebugInformation)] = false; } } } @@ -750,15 +748,15 @@ private void ConfigureDebugProperties() /// protected override bool ValidateParameters() { - return ListHasNoDuplicateItems(this.Resources, "Resources", "LogicalName") && ListHasNoDuplicateItems(this.Sources, "Sources"); + return ListHasNoDuplicateItems(Resources, nameof(Resources), "LogicalName", Log) && ListHasNoDuplicateItems(Sources, nameof(Sources), Log); } /// /// Returns true if the provided item list contains duplicate items, false otherwise. /// - protected bool ListHasNoDuplicateItems(ITaskItem[] itemList, string parameterName) + internal static bool ListHasNoDuplicateItems(ITaskItem[] itemList, string parameterName, TaskLoggingHelper log) { - return ListHasNoDuplicateItems(itemList, parameterName, null); + return ListHasNoDuplicateItems(itemList, parameterName, null, log); } /// @@ -767,7 +765,8 @@ protected bool ListHasNoDuplicateItems(ITaskItem[] itemList, string parameterNam /// /// Optional name of metadata that may legitimately disambiguate items. May be null. /// - private bool ListHasNoDuplicateItems(ITaskItem[] itemList, string parameterName, string disambiguatingMetadataName) + /// + private static bool ListHasNoDuplicateItems(ITaskItem[] itemList, string parameterName, string disambiguatingMetadataName, TaskLoggingHelper log) { if (itemList == null || itemList.Length == 0) { @@ -784,7 +783,7 @@ private bool ListHasNoDuplicateItems(ITaskItem[] itemList, string parameterName, disambiguatingMetadataValue = item.GetMetadata(disambiguatingMetadataName); } - if (disambiguatingMetadataName == null || String.IsNullOrEmpty(disambiguatingMetadataValue)) + if (disambiguatingMetadataName == null || string.IsNullOrEmpty(disambiguatingMetadataValue)) { key = item.ItemSpec; } @@ -795,19 +794,19 @@ private bool ListHasNoDuplicateItems(ITaskItem[] itemList, string parameterName, if (alreadySeen.ContainsKey(key)) { - if (disambiguatingMetadataName == null || String.IsNullOrEmpty(disambiguatingMetadataValue)) + if (disambiguatingMetadataName == null || string.IsNullOrEmpty(disambiguatingMetadataValue)) { - Log.LogErrorWithCodeFromResources("General_DuplicateItemsNotSupported", item.ItemSpec, parameterName); + log.LogErrorWithCodeFromResources("General_DuplicateItemsNotSupported", item.ItemSpec, parameterName); } else { - Log.LogErrorWithCodeFromResources("General_DuplicateItemsNotSupportedWithMetadata", item.ItemSpec, parameterName, disambiguatingMetadataValue, disambiguatingMetadataName); + log.LogErrorWithCodeFromResources("General_DuplicateItemsNotSupportedWithMetadata", item.ItemSpec, parameterName, disambiguatingMetadataValue, disambiguatingMetadataName); } return false; } else { - alreadySeen[key] = String.Empty; + alreadySeen[key] = string.Empty; } } @@ -891,7 +890,7 @@ bool resultFromHostObjectSetOperation /// RGoel protected bool CheckAllReferencesExistOnDisk() { - if (null == this.References) + if (null == References) { // No references return true; @@ -899,7 +898,7 @@ protected bool CheckAllReferencesExistOnDisk() bool success = true; - foreach (ITaskItem reference in this.References) + foreach (ITaskItem reference in References) { if (!File.Exists(reference.ItemSpec)) { @@ -937,11 +936,11 @@ string win32Manifest { if (!noDefaultWin32Manifest) { - if (String.IsNullOrEmpty(win32Manifest) && String.IsNullOrEmpty(this.Win32Resource)) + if (string.IsNullOrEmpty(win32Manifest) && string.IsNullOrEmpty(Win32Resource)) { // We only want to consider the default.win32manifest if this is an executable - if (!String.Equals(TargetType, "library", StringComparison.OrdinalIgnoreCase) - && !String.Equals(TargetType, "module", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(TargetType, "library", StringComparison.OrdinalIgnoreCase) + && !string.Equals(TargetType, "module", StringComparison.OrdinalIgnoreCase)) { // We need to compute the path to the default win32 manifest string pathToDefaultManifest = ToolLocationHelper.GetPathToDotNetFrameworkFile diff --git a/src/Compilers/Core/MSBuildTaskTests/CsiTests.cs b/src/Compilers/Core/MSBuildTaskTests/CsiTests.cs new file mode 100644 index 00000000000..1b6970a7ff8 --- /dev/null +++ b/src/Compilers/Core/MSBuildTaskTests/CsiTests.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Xunit; + +namespace Microsoft.CodeAnalysis.BuildTasks.UnitTests +{ + public sealed class CsiTests + { + [Fact] + public void SingleSource() + { + var csi = new Csi(); + csi.Source = MSBuildUtil.CreateTaskItem("test.csx"); + Assert.Equal("test.csx", csi.GenerateResponseFileContents()); + } + + [Fact] + public void Features() + { + Action test = (s) => + { + var csi = new Csi(); + csi.Features = s; + csi.Source = MSBuildUtil.CreateTaskItem("test.csx"); + Assert.Equal("/features:a /features:b test.csx", csi.GenerateResponseFileContents()); + }; + + test("a;b"); + test("a,b"); + test("a b"); + test(",a;b "); + test(";a;;b;"); + test(",a,,b,"); + } + + [Fact] + public void FeaturesEmpty() + { + foreach (var cur in new[] { "", null }) + { + var csi = new Csi(); + csi.Features = cur; + csi.Sources = MSBuildUtil.CreateTaskItems("test.csx"); + Assert.Equal("test.csx", csi.GenerateResponseFileContents()); + } + } + } +} diff --git a/src/Compilers/Core/MSBuildTaskTests/MSBuildTaskTests.csproj b/src/Compilers/Core/MSBuildTaskTests/MSBuildTaskTests.csproj index 9fd25bfacb2..532df485d03 100644 --- a/src/Compilers/Core/MSBuildTaskTests/MSBuildTaskTests.csproj +++ b/src/Compilers/Core/MSBuildTaskTests/MSBuildTaskTests.csproj @@ -89,6 +89,7 @@ + @@ -102,4 +103,4 @@ - + \ No newline at end of file -- GitLab