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.Collections.Generic;
7 using System.Xml;
8 using System.Globalization;
9 
10 namespace Microsoft.Build.Shared
11 {
12     /// <summary>
13     /// Contains the names of the known attributes in the XML project file.
14     /// </summary>
15     internal static class XMakeAttributes
16     {
17         internal const string condition = "Condition";
18         internal const string executeTargets = "ExecuteTargets";
19         internal const string name = "Name";
20         internal const string msbuildVersion = "MSBuildVersion";
21         internal const string xmlns = "xmlns";
22         internal const string defaultTargets = "DefaultTargets";
23         internal const string initialTargets = "InitialTargets";
24         internal const string treatAsLocalProperty = "TreatAsLocalProperty";
25         internal const string dependsOnTargets = "DependsOnTargets";
26         internal const string beforeTargets = "BeforeTargets";
27         internal const string afterTargets = "AfterTargets";
28         internal const string include = "Include";
29         internal const string exclude = "Exclude";
30         internal const string remove = "Remove";
31         internal const string update = "Update";
32         internal const string keepMetadata = "KeepMetadata";
33         internal const string removeMetadata = "RemoveMetadata";
34         internal const string keepDuplicates = "KeepDuplicates";
35         internal const string inputs = "Inputs";
36         internal const string outputs = "Outputs";
37         internal const string keepDuplicateOutputs = "KeepDuplicateOutputs";
38         internal const string assemblyName = "AssemblyName";
39         internal const string assemblyFile = "AssemblyFile";
40         internal const string taskName = "TaskName";
41         internal const string continueOnError = "ContinueOnError";
42         internal const string project = "Project";
43         internal const string taskParameter = "TaskParameter";
44         internal const string itemName = "ItemName";
45         internal const string propertyName = "PropertyName";
46         internal const string sdk = "Sdk";
47         internal const string sdkName = "Name";
48         internal const string sdkVersion = "Version";
49         internal const string sdkMinimumVersion = "MinimumVersion";
50         internal const string toolsVersion = "ToolsVersion";
51         internal const string runtime = "Runtime";
52         internal const string msbuildRuntime = "MSBuildRuntime";
53         internal const string architecture = "Architecture";
54         internal const string msbuildArchitecture = "MSBuildArchitecture";
55         internal const string taskFactory = "TaskFactory";
56         internal const string parameterType = "ParameterType";
57         internal const string required = "Required";
58         internal const string output = "Output";
59         internal const string defaultValue = "DefaultValue";
60         internal const string evaluate = "Evaluate";
61         internal const string label = "Label";
62         internal const string returns = "Returns";
63 
64         // Obsolete
65         internal const string requiredRuntime = "RequiredRuntime";
66         internal const string requiredPlatform = "RequiredPlatform";
67 
68         internal struct ContinueOnErrorValues
69         {
70             internal const string errorAndContinue = "ErrorAndContinue";
71             internal const string errorAndStop = "ErrorAndStop";
72             internal const string warnAndContinue = "WarnAndContinue";
73         }
74 
75         internal struct MSBuildRuntimeValues
76         {
77             internal const string clr2 = "CLR2";
78             internal const string clr4 = "CLR4";
79             internal const string currentRuntime = "CurrentRuntime";
80             internal const string any = "*";
81         }
82 
83         internal struct MSBuildArchitectureValues
84         {
85             internal const string x86 = "x86";
86             internal const string x64 = "x64";
87             internal const string currentArchitecture = "CurrentArchitecture";
88             internal const string any = "*";
89         }
90 
91         /////////////////////////////////////////////////////////////////////////////////////////////
92         // If we ever add a new MSBuild namespace (or change this one) we must update the registry key
93         // we set during install to disable the XSL debugger from working on MSBuild format files.
94         /////////////////////////////////////////////////////////////////////////////////////////////
95         internal const string defaultXmlNamespace = "http://schemas.microsoft.com/developer/msbuild/2003";
96 
97         /// <summary>
98         /// Returns true if and only if the specified attribute is one of the attributes that the engine specifically recognizes
99         /// on a task and treats in a special way.
100         /// </summary>
101         /// <param name="attribute"></param>
102         /// <returns>true, if given attribute is a reserved task attribute</returns>
IsSpecialTaskAttribute( string attribute )103         internal static bool IsSpecialTaskAttribute
104         (
105             string attribute
106         )
107         {
108             // Currently the known "special" attributes for a task are:
109             //  Condition, ContinueOnError
110             //
111             // We want to match case-sensitively on all of them
112             return ((attribute == condition) ||
113                     (attribute == continueOnError) ||
114                     (attribute == msbuildRuntime) ||
115                     (attribute == msbuildArchitecture) ||
116                     (attribute == xmlns));
117         }
118 
119         /// <summary>
120         /// Checks if the specified attribute is a reserved task attribute with incorrect casing.
121         /// </summary>
122         /// <param name="attribute"></param>
123         /// <returns>true, if the given attribute is reserved and badly cased</returns>
IsBadlyCasedSpecialTaskAttribute(string attribute)124         internal static bool IsBadlyCasedSpecialTaskAttribute(string attribute)
125         {
126             return (!IsSpecialTaskAttribute(attribute) &&
127                 ((String.Compare(attribute, condition, StringComparison.OrdinalIgnoreCase) == 0) ||
128                 (String.Compare(attribute, continueOnError, StringComparison.OrdinalIgnoreCase) == 0) ||
129                 (String.Compare(attribute, msbuildRuntime, StringComparison.OrdinalIgnoreCase) == 0) ||
130                 (String.Compare(attribute, msbuildArchitecture, StringComparison.OrdinalIgnoreCase) == 0)));
131         }
132 
133         /// <summary>
134         /// Indicates if the specified attribute cannot be used for batching targets.
135         /// </summary>
136         /// <param name="attribute"></param>
137         /// <returns>true, if a target cannot batch on the given attribute</returns>
IsNonBatchingTargetAttribute(string attribute)138         internal static bool IsNonBatchingTargetAttribute(string attribute)
139         {
140             return ((attribute == name) ||
141                     (attribute == condition) ||
142                     (attribute == dependsOnTargets) ||
143                     (attribute == beforeTargets) ||
144                     (attribute == afterTargets));
145         }
146 
147         /// <summary>
148         /// Returns true if the given string is a valid member of the MSBuildRuntimeValues set
149         /// </summary>
IsValidMSBuildRuntimeValue(string runtime)150         internal static bool IsValidMSBuildRuntimeValue(string runtime)
151         {
152             return (runtime == null ||
153                     XMakeAttributes.MSBuildRuntimeValues.clr2.Equals(runtime, StringComparison.OrdinalIgnoreCase) ||
154                     XMakeAttributes.MSBuildRuntimeValues.clr4.Equals(runtime, StringComparison.OrdinalIgnoreCase) ||
155                     XMakeAttributes.MSBuildRuntimeValues.currentRuntime.Equals(runtime, StringComparison.OrdinalIgnoreCase) ||
156                     XMakeAttributes.MSBuildRuntimeValues.any.Equals(runtime, StringComparison.OrdinalIgnoreCase));
157         }
158 
159         /// <summary>
160         /// Returns true if the given string is a valid member of the MSBuildArchitectureValues set
161         /// </summary>
IsValidMSBuildArchitectureValue(string architecture)162         internal static bool IsValidMSBuildArchitectureValue(string architecture)
163         {
164             return (architecture == null ||
165                     XMakeAttributes.MSBuildArchitectureValues.x86.Equals(architecture, StringComparison.OrdinalIgnoreCase) ||
166                     XMakeAttributes.MSBuildArchitectureValues.x64.Equals(architecture, StringComparison.OrdinalIgnoreCase) ||
167                     XMakeAttributes.MSBuildArchitectureValues.currentArchitecture.Equals(architecture, StringComparison.OrdinalIgnoreCase) ||
168                     XMakeAttributes.MSBuildArchitectureValues.any.Equals(architecture, StringComparison.OrdinalIgnoreCase));
169         }
170 
171         /// <summary>
172         /// Compares two members of MSBuildRuntimeValues, returning true if they count as a match, and false otherwise.
173         /// </summary>
RuntimeValuesMatch(string runtimeA, string runtimeB)174         internal static bool RuntimeValuesMatch(string runtimeA, string runtimeB)
175         {
176             ErrorUtilities.VerifyThrow(runtimeA != String.Empty && runtimeB != String.Empty, "We should never get an empty string passed to this method");
177 
178             if (runtimeA == null || runtimeB == null)
179             {
180                 // neither one cares, or only one cares, so they match by default.
181                 return true;
182             }
183 
184             if (runtimeA.Equals(runtimeB, StringComparison.OrdinalIgnoreCase))
185             {
186                 // if they are equal, of course they match
187                 return true;
188             }
189 
190             if (runtimeA.Equals(MSBuildRuntimeValues.any, StringComparison.OrdinalIgnoreCase) || runtimeB.Equals(MSBuildRuntimeValues.any, StringComparison.OrdinalIgnoreCase))
191             {
192                 // one or both explicitly don't care -- still a match.
193                 return true;
194             }
195 
196             if ((runtimeA.Equals(MSBuildRuntimeValues.currentRuntime, StringComparison.OrdinalIgnoreCase) && runtimeB.Equals(MSBuildRuntimeValues.clr4, StringComparison.OrdinalIgnoreCase)) ||
197                 (runtimeA.Equals(MSBuildRuntimeValues.clr4, StringComparison.OrdinalIgnoreCase) && runtimeB.Equals(MSBuildRuntimeValues.currentRuntime, StringComparison.OrdinalIgnoreCase)))
198             {
199                 // CLR4 is the current runtime, so this is also a match.
200                 return true;
201             }
202 
203             // if none of the above is true, then it doesn't match ...
204             return false;
205         }
206 
207         /// <summary>
208         /// Given two MSBuildRuntime values, returns the concrete result of merging the two.  If the merge fails, the merged runtime
209         /// string is returned null, and the return value of the method is false.  Otherwise, if the merge succeeds, the method returns
210         /// true with the merged runtime value.  E.g.:
211         /// "CLR4" + "CLR2" = null (false)
212         /// "CLR2" + "don't care" = "CLR2" (true)
213         /// "current runtime" + "CLR4" = "CLR4" (true)
214         /// "current runtime" + "don't care" = "CLR4" (true)
215         /// If both specify "don't care", then defaults to the current runtime -- CLR4.
216         /// A null or empty string is interpreted as "don't care".
217         /// </summary>
TryMergeRuntimeValues(string runtimeA, string runtimeB, out string mergedRuntime)218         internal static bool TryMergeRuntimeValues(string runtimeA, string runtimeB, out string mergedRuntime)
219         {
220             ErrorUtilities.VerifyThrow(runtimeA != String.Empty && runtimeB != String.Empty, "We should never get an empty string passed to this method");
221 
222             // set up the defaults
223             if (runtimeA == null)
224             {
225                 runtimeA = MSBuildRuntimeValues.any;
226             }
227 
228             if (runtimeB == null)
229             {
230                 runtimeB = MSBuildRuntimeValues.any;
231             }
232 
233             // if they're equal, then there's no problem -- just return the equivalent runtime.
234             if (runtimeA.Equals(runtimeB, StringComparison.OrdinalIgnoreCase))
235             {
236                 if (runtimeA.Equals(MSBuildRuntimeValues.currentRuntime, StringComparison.OrdinalIgnoreCase) ||
237                     runtimeA.Equals(MSBuildRuntimeValues.any, StringComparison.OrdinalIgnoreCase))
238                 {
239                     mergedRuntime = MSBuildRuntimeValues.clr4;
240                 }
241                 else
242                 {
243                     mergedRuntime = runtimeA;
244                 }
245 
246                 return true;
247             }
248 
249             // if both A and B are one of CLR4, don't care, or current, then the end result will be CLR4 no matter what.
250             if (
251                 (
252                  runtimeA.Equals(MSBuildRuntimeValues.clr4, StringComparison.OrdinalIgnoreCase) ||
253                  runtimeA.Equals(MSBuildRuntimeValues.currentRuntime, StringComparison.OrdinalIgnoreCase) ||
254                  runtimeA.Equals(MSBuildRuntimeValues.any, StringComparison.OrdinalIgnoreCase)
255                 ) &&
256                 (
257                  runtimeB.Equals(MSBuildRuntimeValues.clr4, StringComparison.OrdinalIgnoreCase) ||
258                  runtimeB.Equals(MSBuildRuntimeValues.currentRuntime, StringComparison.OrdinalIgnoreCase) ||
259                  runtimeB.Equals(MSBuildRuntimeValues.any, StringComparison.OrdinalIgnoreCase)
260                 )
261                )
262             {
263                 mergedRuntime = MSBuildRuntimeValues.clr4;
264                 return true;
265             }
266 
267             // If A doesn't care, then it's B -- and we can say B straight out, because if B were one of the
268             // special cases (current runtime or don't care) then it would already have been caught in the
269             // previous clause.
270             if (runtimeA.Equals(MSBuildRuntimeValues.any, StringComparison.OrdinalIgnoreCase))
271             {
272                 mergedRuntime = runtimeB;
273                 return true;
274             }
275 
276             // And vice versa
277             if (runtimeB.Equals(MSBuildRuntimeValues.any, StringComparison.OrdinalIgnoreCase))
278             {
279                 mergedRuntime = runtimeA;
280                 return true;
281             }
282 
283             // and now we've run out of things that it could be -- all the remaining options are non-matches.
284             mergedRuntime = null;
285             return false;
286         }
287 
288         /// <summary>
289         /// Compares two members of MSBuildArchitectureValues, returning true if they count as a match, and false otherwise.
290         /// </summary>
ArchitectureValuesMatch(string architectureA, string architectureB)291         internal static bool ArchitectureValuesMatch(string architectureA, string architectureB)
292         {
293             ErrorUtilities.VerifyThrow(architectureA != String.Empty && architectureB != String.Empty, "We should never get an empty string passed to this method");
294 
295             if (architectureA == null || architectureB == null)
296             {
297                 // neither one cares, or only one cares, so they match by default.
298                 return true;
299             }
300 
301             if (architectureA.Equals(architectureB, StringComparison.OrdinalIgnoreCase))
302             {
303                 // if they are equal, of course they match
304                 return true;
305             }
306 
307             if (architectureA.Equals(MSBuildArchitectureValues.any, StringComparison.OrdinalIgnoreCase) || architectureB.Equals(MSBuildArchitectureValues.any, StringComparison.OrdinalIgnoreCase))
308             {
309                 // one or both explicitly don't care -- still a match.
310                 return true;
311             }
312 
313             string currentArchitecture = GetCurrentMSBuildArchitecture();
314 
315             if ((architectureA.Equals(MSBuildArchitectureValues.currentArchitecture, StringComparison.OrdinalIgnoreCase) && architectureB.Equals(currentArchitecture, StringComparison.OrdinalIgnoreCase)) ||
316                 (architectureA.Equals(currentArchitecture, StringComparison.OrdinalIgnoreCase) && architectureB.Equals(MSBuildArchitectureValues.currentArchitecture, StringComparison.OrdinalIgnoreCase)))
317             {
318                 return true;
319             }
320 
321             // if none of the above is true, then it doesn't match ...
322             return false;
323         }
324 
325         /// <summary>
326         /// Given an MSBuildRuntime value that may be non-explicit -- e.g. "CurrentRuntime" or "Any" --
327         /// return the specific MSBuildRuntime value that it would map to in this case. If it does not map
328         /// to any known runtime, just return it as is -- maybe someone else knows what to do with it; if
329         /// not, they'll certainly have more context on logging or throwing the error.
330         /// </summary>
GetExplicitMSBuildRuntime(string runtime)331         internal static string GetExplicitMSBuildRuntime(string runtime)
332         {
333             if (runtime == null ||
334                 MSBuildRuntimeValues.any.Equals(runtime, StringComparison.OrdinalIgnoreCase) ||
335                 MSBuildRuntimeValues.currentRuntime.Equals(runtime, StringComparison.OrdinalIgnoreCase))
336             {
337                 // Default to CLR4.
338                 return MSBuildRuntimeValues.clr4;
339             }
340             else
341             {
342                 // either it's already a valid, specific runtime, or we don't know what to do with it.  Either way, return.
343                 return runtime;
344             }
345         }
346 
347         /// <summary>
348         /// Given two MSBuildArchitecture values, returns the concrete result of merging the two.  If the merge fails, the merged architecture
349         /// string is returned null, and the return value of the method is false.  Otherwise, if the merge succeeds, the method returns
350         /// true with the merged architecture value.  E.g.:
351         /// "x86" + "x64" = null (false)
352         /// "x86" + "don't care" = "x86" (true)
353         /// "current architecture" + "x86" = "x86" (true) on a 32-bit process, and null (false) on a 64-bit process
354         /// "current architecture" + "don't care" = "x86" (true) on a 32-bit process, and "x64" (true) on a 64-bit process
355         /// A null or empty string is interpreted as "don't care".
356         /// If both specify "don't care", then defaults to whatever the current process architecture is.
357         /// </summary>
TryMergeArchitectureValues(string architectureA, string architectureB, out string mergedArchitecture)358         internal static bool TryMergeArchitectureValues(string architectureA, string architectureB, out string mergedArchitecture)
359         {
360             ErrorUtilities.VerifyThrow(architectureA != String.Empty && architectureB != String.Empty, "We should never get an empty string passed to this method");
361 
362             // set up the defaults
363             if (architectureA == null)
364             {
365                 architectureA = MSBuildArchitectureValues.any;
366             }
367 
368             if (architectureB == null)
369             {
370                 architectureB = MSBuildArchitectureValues.any;
371             }
372 
373             string currentArchitecture = GetCurrentMSBuildArchitecture();
374 
375             // if they're equal, then there's no problem -- just return the equivalent runtime.
376             if (architectureA.Equals(architectureB, StringComparison.OrdinalIgnoreCase))
377             {
378                 if (architectureA.Equals(MSBuildArchitectureValues.currentArchitecture, StringComparison.OrdinalIgnoreCase) ||
379                     architectureA.Equals(MSBuildArchitectureValues.any, StringComparison.OrdinalIgnoreCase))
380                 {
381                     mergedArchitecture = currentArchitecture;
382                 }
383                 else
384                 {
385                     mergedArchitecture = architectureA;
386                 }
387 
388                 return true;
389             }
390 
391             // if both A and B are one of CLR4, don't care, or current, then the end result will be CLR4 no matter what.
392             if (
393                 (
394                  architectureA.Equals(currentArchitecture, StringComparison.OrdinalIgnoreCase) ||
395                  architectureA.Equals(MSBuildArchitectureValues.currentArchitecture, StringComparison.OrdinalIgnoreCase) ||
396                  architectureA.Equals(MSBuildArchitectureValues.any, StringComparison.OrdinalIgnoreCase)
397                 ) &&
398                 (
399                  architectureB.Equals(currentArchitecture, StringComparison.OrdinalIgnoreCase) ||
400                  architectureB.Equals(MSBuildArchitectureValues.currentArchitecture, StringComparison.OrdinalIgnoreCase) ||
401                  architectureB.Equals(MSBuildArchitectureValues.any, StringComparison.OrdinalIgnoreCase)
402                 )
403                )
404             {
405                 mergedArchitecture = currentArchitecture;
406                 return true;
407             }
408 
409             // If A doesn't care, then it's B -- and we can say B straight out, because if B were one of the
410             // special cases (current runtime or don't care) then it would already have been caught in the
411             // previous clause.
412             if (architectureA.Equals(MSBuildArchitectureValues.any, StringComparison.OrdinalIgnoreCase))
413             {
414                 mergedArchitecture = architectureB;
415                 return true;
416             }
417 
418             // And vice versa
419             if (architectureB.Equals(MSBuildArchitectureValues.any, StringComparison.OrdinalIgnoreCase))
420             {
421                 mergedArchitecture = architectureA;
422                 return true;
423             }
424 
425             // and now we've run out of things that it could be -- all the remaining options are non-matches.
426             mergedArchitecture = null;
427             return false;
428         }
429 
430         /// <summary>
431         /// Returns the MSBuildArchitecture value corresponding to the current process' architecture.
432         /// </summary>
433         /// <comments>
434         /// Revisit if we ever run on something other than Intel.
435         /// </comments>
GetCurrentMSBuildArchitecture()436         internal static string GetCurrentMSBuildArchitecture()
437         {
438             string currentArchitecture = (IntPtr.Size == sizeof(Int64)) ? MSBuildArchitectureValues.x64 : MSBuildArchitectureValues.x86;
439             return currentArchitecture;
440         }
441 
442         /// <summary>
443         /// Given an MSBuildArchitecture value that may be non-explicit -- e.g. "CurrentArchitecture" or "Any" --
444         /// return the specific MSBuildArchitecture value that it would map to in this case.  If it does not map
445         /// to any known architecture, just return it as is -- maybe someone else knows what to do with it; if
446         /// not, they'll certainly have more context on logging or throwing the error.
447         /// </summary>
GetExplicitMSBuildArchitecture(string architecture)448         internal static string GetExplicitMSBuildArchitecture(string architecture)
449         {
450             if (architecture == null ||
451                 MSBuildArchitectureValues.any.Equals(architecture, StringComparison.OrdinalIgnoreCase) ||
452                 MSBuildArchitectureValues.currentArchitecture.Equals(architecture, StringComparison.OrdinalIgnoreCase))
453             {
454                 string currentArchitecture = GetCurrentMSBuildArchitecture();
455                 return currentArchitecture;
456             }
457             else
458             {
459                 // either it's already a valid, specific architecture, or we don't know what to do with it.  Either way, return.
460                 return architecture;
461             }
462         }
463     }
464 }
465