1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 4 using System; 5 using System.Collections; 6 using System.Globalization; 7 using System.Text; 8 using Microsoft.Build.Framework; 9 using Microsoft.Build.Shared; 10 using Microsoft.Build.Utilities; 11 12 namespace Microsoft.Build.Tasks 13 { 14 /// <summary> 15 /// CommandLineBuilder derived class for specialized logic specific to MSBuild tasks 16 /// </summary> 17 public class CommandLineBuilderExtension : CommandLineBuilder 18 { 19 /// <summary> 20 /// Initializes a new instance of the CommandLineBuilderExtension class. 21 /// </summary> CommandLineBuilderExtension()22 public CommandLineBuilderExtension() 23 { 24 25 } 26 27 /// <summary> 28 /// Initializes a new instance of the CommandLineBuilderExtension class. 29 /// </summary> CommandLineBuilderExtension(bool quoteHyphensOnCommandLine, bool useNewLineSeparator)30 public CommandLineBuilderExtension(bool quoteHyphensOnCommandLine, bool useNewLineSeparator) 31 : base(quoteHyphensOnCommandLine, useNewLineSeparator) 32 { 33 34 } 35 36 /// <summary> 37 /// Set a boolean switch iff its value exists and its value is 'true'. 38 /// </summary> 39 /// <param name="switchName"></param> 40 /// <param name="bag"></param> 41 /// <param name="parameterName"></param> AppendWhenTrue( string switchName, Hashtable bag, string parameterName )42 internal void AppendWhenTrue 43 ( 44 string switchName, 45 Hashtable bag, 46 string parameterName 47 ) 48 { 49 object obj = bag[parameterName]; 50 // If the switch isn't set, don't add it to the command line. 51 if (obj != null) 52 { 53 bool value = (bool)obj; 54 55 if (value) 56 { 57 AppendSwitch(switchName); 58 } 59 } 60 } 61 62 /// <summary> 63 /// Set a boolean switch only if its value exists. 64 /// </summary> 65 /// <param name="switchName"></param> 66 /// <param name="bag"></param> 67 /// <param name="parameterName"></param> AppendPlusOrMinusSwitch( string switchName, Hashtable bag, string parameterName )68 internal void AppendPlusOrMinusSwitch 69 ( 70 string switchName, 71 Hashtable bag, 72 string parameterName 73 ) 74 { 75 object obj = bag[parameterName]; 76 // If the switch isn't set, don't add it to the command line. 77 if (obj != null) 78 { 79 bool value = (bool)obj; 80 // Do not quote - or + as they are part of the switch 81 AppendSwitchUnquotedIfNotNull(switchName, (value ? "+" : "-")); 82 } 83 } 84 85 86 /// <summary> 87 /// Set a switch if its value exists by choosing from the input choices 88 /// </summary> 89 /// <param name="switchName"></param> 90 /// <param name="bag"></param> 91 /// <param name="parameterName"></param> 92 /// <param name="choice1"></param> 93 /// <param name="choice2"></param> AppendByChoiceSwitch( string switchName, Hashtable bag, string parameterName, string choice1, string choice2 )94 internal void AppendByChoiceSwitch 95 ( 96 string switchName, 97 Hashtable bag, 98 string parameterName, 99 string choice1, 100 string choice2 101 ) 102 { 103 object obj = bag[parameterName]; 104 // If the switch isn't set, don't add it to the command line. 105 if (obj != null) 106 { 107 bool value = (bool)obj; 108 AppendSwitchUnquotedIfNotNull(switchName, (value ? choice1 : choice2)); 109 } 110 } 111 112 /// <summary> 113 /// Set an integer switch only if its value exists. 114 /// </summary> 115 /// <param name="switchName"></param> 116 /// <param name="bag"></param> 117 /// <param name="parameterName"></param> AppendSwitchWithInteger( string switchName, Hashtable bag, string parameterName )118 internal void AppendSwitchWithInteger 119 ( 120 string switchName, 121 Hashtable bag, 122 string parameterName 123 ) 124 { 125 object obj = bag[parameterName]; 126 // If the switch isn't set, don't add it to the command line. 127 if (obj != null) 128 { 129 int value = (int)obj; 130 AppendSwitchIfNotNull(switchName, value.ToString(CultureInfo.InvariantCulture)); 131 } 132 } 133 134 /// <summary> 135 /// Adds an aliased switch, used for ResGen: 136 /// /reference:Foo=System.Xml.dll 137 /// </summary> 138 /// <param name="switchName"></param> 139 /// <param name="alias"></param> 140 /// <param name="parameter"></param> AppendSwitchAliased(string switchName, string alias, string parameter)141 internal void AppendSwitchAliased(string switchName, string alias, string parameter) 142 { 143 AppendSwitchUnquotedIfNotNull(switchName, alias + "="); 144 AppendTextWithQuoting(parameter); 145 } 146 147 /// <summary> 148 /// Adds a nested switch, used by SGen.exe. For example: 149 /// /compiler:"/keyfile:\"c:\some folder\myfile.snk\"" 150 /// </summary> 151 /// <param name="outerSwitchName"></param> 152 /// <param name="innerSwitchName"></param> 153 /// <param name="parameter"></param> AppendNestedSwitch(string outerSwitchName, string innerSwitchName, string parameter)154 internal void AppendNestedSwitch(string outerSwitchName, string innerSwitchName, string parameter) 155 { 156 string quotedParameter = GetQuotedText(parameter); 157 AppendSwitchIfNotNull(outerSwitchName, innerSwitchName + quotedParameter); 158 } 159 160 /// <summary> 161 /// Returns a quoted string appropriate for appending to a command line. 162 /// </summary> 163 /// <remarks> 164 /// Escapes any double quotes in the string. 165 /// </remarks> 166 /// <param name="unquotedText"></param> GetQuotedText(string unquotedText)167 protected string GetQuotedText(string unquotedText) 168 { 169 StringBuilder quotedText = new StringBuilder(); 170 171 AppendQuotedTextToBuffer(quotedText, unquotedText); 172 173 return quotedText.ToString(); 174 } 175 176 /// <summary> 177 /// Appends a command-line switch that takes a compound string parameter. The parameter is built up from the item-spec and 178 /// the specified attributes. The switch is appended as many times as there are parameters given. 179 /// </summary> 180 /// <param name="switchName"></param> 181 /// <param name="parameters"></param> 182 /// <param name="attributes"></param> AppendSwitchIfNotNull( string switchName, ITaskItem[] parameters, string[] attributes )183 internal void AppendSwitchIfNotNull 184 ( 185 string switchName, 186 ITaskItem[] parameters, 187 string[] attributes 188 ) 189 { 190 AppendSwitchIfNotNull(switchName, parameters, attributes, null /* treatAsFlag */); 191 } 192 193 /// <summary> 194 /// Append a switch if 'parameter' is not null. 195 /// Split on the characters provided. 196 /// </summary> 197 /// <param name="switchName"></param> 198 /// <param name="parameters"></param> 199 /// <param name="quoteChars"></param> AppendSwitchWithSplitting(string switchName, string parameter, string delimiter, params char[] splitOn)200 internal void AppendSwitchWithSplitting(string switchName, string parameter, string delimiter, params char[] splitOn) 201 { 202 if (parameter != null) 203 { 204 string[] splits = parameter.Split(splitOn, /* omitEmptyEntries */ StringSplitOptions.RemoveEmptyEntries); 205 string[] splitAndTrimmed = new string[splits.Length]; 206 for (int i = 0; i < splits.Length; ++i) 207 { 208 splitAndTrimmed[i] = splits[i].Trim(); 209 } 210 AppendSwitchIfNotNull(switchName, splitAndTrimmed, delimiter); 211 } 212 } 213 214 /// <summary> 215 /// Returns true if the parameter is empty in spirits, 216 /// even if it contains the separators and white space only 217 /// Split on the characters provided. 218 /// </summary> 219 /// <param name="parameters"></param> 220 /// <param name="splitOn"></param> IsParameterEmpty(string parameter, params char[] splitOn)221 internal static bool IsParameterEmpty(string parameter, params char[] splitOn) 222 { 223 if (parameter != null) 224 { 225 string[] splits = parameter.Split(splitOn, /* omitEmptyEntries */ StringSplitOptions.RemoveEmptyEntries); 226 for (int i = 0; i < splits.Length; ++i) 227 { 228 if (!String.IsNullOrEmpty(splits[i].Trim())) 229 { 230 return false; 231 } 232 } 233 } 234 return true; 235 } 236 /// <summary> 237 /// Designed to handle the /link and /embed swithes: 238 /// 239 /// /embed[resource]:<filename>[,<name>[,Private]] 240 /// /link[resource]:<filename>[,<name>[,Private]] 241 /// 242 /// Where the last flag--Private--is either present or not present 243 /// depending on whether the ITaskItem has a Private="True" attribue. 244 /// </summary> 245 /// <param name="switchName"></param> 246 /// <param name="parameters"></param> 247 /// <param name="metadataNames"></param> 248 /// <param name="treatAsFlags"></param> AppendSwitchIfNotNull( string switchName, ITaskItem[] parameters, string[] metadataNames, bool[] treatAsFlags )249 internal void AppendSwitchIfNotNull 250 ( 251 string switchName, 252 ITaskItem[] parameters, 253 string[] metadataNames, 254 bool[] treatAsFlags // May be null. In this case no metadata are treated as flags. 255 ) 256 { 257 ErrorUtilities.VerifyThrow 258 ( 259 treatAsFlags == null || 260 (metadataNames != null && metadataNames.Length == treatAsFlags.Length), 261 "metadataNames and treatAsFlags should have the same length." 262 ); 263 264 if (parameters != null) 265 { 266 foreach (ITaskItem parameter in parameters) 267 { 268 AppendSwitchIfNotNull(switchName, parameter.ItemSpec); 269 270 if (metadataNames != null) 271 { 272 for (int i = 0; i < metadataNames.Length; ++i) 273 { 274 string metadataValue = parameter.GetMetadata(metadataNames[i]); 275 276 if ((metadataValue != null) && (metadataValue.Length > 0)) 277 { 278 // Treat attribute as a boolean flag? 279 if (treatAsFlags == null || treatAsFlags[i] == false) 280 { 281 // Not a boolean flag. 282 CommandLine.Append(','); 283 AppendTextWithQuoting(metadataValue); 284 } 285 else 286 { 287 // A boolean flag. 288 bool flagSet = false; 289 290 flagSet = MetadataConversionUtilities.TryConvertItemMetadataToBool(parameter, metadataNames[i]); 291 292 if (flagSet) 293 { 294 CommandLine.Append(','); 295 AppendTextWithQuoting(metadataNames[i]); 296 } 297 } 298 } 299 else 300 { 301 if (treatAsFlags == null || treatAsFlags[i] == false) 302 { 303 // If the caller of this method asked us to add metadata 304 // A, B, and C, and metadata A doesn't exist on the item, 305 // then it doesn't make sense to check for B and C. Because 306 // since these metadata are just being appended on the 307 // command-line switch with comma-separation, you can't pass 308 // in the B metadata unless you've also passed in the A 309 // metadata. Otherwise the tool's command-line parser will 310 // get totally confused. 311 312 // This only applies to non-flag attributes. 313 break; 314 } 315 } 316 } 317 } 318 } 319 } 320 } 321 } 322 } 323