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]:&lt;filename&gt;[,&lt;name&gt;[,Private]]
240         ///      /link[resource]:&lt;filename&gt;[,&lt;name&gt;[,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