|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
#if !CLR2COMPATIBILITY
using System.Runtime.InteropServices;
#endif
#nullable disable
namespace Microsoft.Build.Shared
{
/// <summary>
/// Contains the names of the known attributes in the XML project file.
/// </summary>
internal static class XMakeAttributes
{
internal const string condition = "Condition";
internal const string executeTargets = "ExecuteTargets";
internal const string name = "Name";
internal const string msbuildVersion = "MSBuildVersion";
internal const string xmlns = "xmlns";
internal const string defaultTargets = "DefaultTargets";
internal const string initialTargets = "InitialTargets";
internal const string treatAsLocalProperty = "TreatAsLocalProperty";
internal const string dependsOnTargets = "DependsOnTargets";
internal const string beforeTargets = "BeforeTargets";
internal const string afterTargets = "AfterTargets";
internal const string include = "Include";
internal const string exclude = "Exclude";
internal const string remove = "Remove";
internal const string update = "Update";
internal const string matchOnMetadata = "MatchOnMetadata";
internal const string matchOnMetadataOptions = "MatchOnMetadataOptions";
internal const string overrideUsingTask = "Override";
internal const string keepMetadata = "KeepMetadata";
internal const string removeMetadata = "RemoveMetadata";
internal const string keepDuplicates = "KeepDuplicates";
internal const string inputs = "Inputs";
internal const string outputs = "Outputs";
internal const string keepDuplicateOutputs = "KeepDuplicateOutputs";
internal const string assemblyName = "AssemblyName";
internal const string assemblyFile = "AssemblyFile";
internal const string taskName = "TaskName";
internal const string continueOnError = "ContinueOnError";
internal const string project = "Project";
internal const string taskParameter = "TaskParameter";
internal const string itemName = "ItemName";
internal const string propertyName = "PropertyName";
internal const string sdk = "Sdk";
internal const string sdkName = "Name";
internal const string sdkVersion = "Version";
internal const string sdkMinimumVersion = "MinimumVersion";
internal const string toolsVersion = "ToolsVersion";
internal const string runtime = "Runtime";
internal const string msbuildRuntime = "MSBuildRuntime";
internal const string architecture = "Architecture";
internal const string msbuildArchitecture = "MSBuildArchitecture";
internal const string taskFactory = "TaskFactory";
internal const string parameterType = "ParameterType";
internal const string required = "Required";
internal const string output = "Output";
internal const string defaultValue = "DefaultValue";
internal const string evaluate = "Evaluate";
internal const string label = "Label";
internal const string returns = "Returns";
// Obsolete
internal const string requiredRuntime = "RequiredRuntime";
internal const string requiredPlatform = "RequiredPlatform";
internal struct ContinueOnErrorValues
{
internal const string errorAndContinue = "ErrorAndContinue";
internal const string errorAndStop = "ErrorAndStop";
internal const string warnAndContinue = "WarnAndContinue";
}
internal struct MSBuildRuntimeValues
{
internal const string clr2 = "CLR2";
internal const string clr4 = "CLR4";
internal const string currentRuntime = "CurrentRuntime";
internal const string net = "NET";
internal const string any = "*";
}
internal struct MSBuildArchitectureValues
{
internal const string x86 = "x86";
internal const string x64 = "x64";
internal const string arm64 = "arm64";
internal const string currentArchitecture = "CurrentArchitecture";
internal const string any = "*";
}
/////////////////////////////////////////////////////////////////////////////////////////////
// If we ever add a new MSBuild namespace (or change this one) we must update the registry key
// we set during install to disable the XSL debugger from working on MSBuild format files.
/////////////////////////////////////////////////////////////////////////////////////////////
internal const string defaultXmlNamespace = "http://schemas.microsoft.com/developer/msbuild/2003";
private static readonly HashSet<string> KnownSpecialTaskAttributes = new HashSet<string> { condition, continueOnError, msbuildRuntime, msbuildArchitecture, xmlns };
private static readonly HashSet<string> KnownSpecialTaskAttributesIgnoreCase = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { condition, continueOnError, msbuildRuntime, msbuildArchitecture, xmlns };
private static readonly HashSet<string> KnownBatchingTargetAttributes = new HashSet<string> { name, condition, dependsOnTargets, beforeTargets, afterTargets };
private static readonly HashSet<string> ValidMSBuildRuntimeValues = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { MSBuildRuntimeValues.clr2, MSBuildRuntimeValues.clr4, MSBuildRuntimeValues.currentRuntime, MSBuildRuntimeValues.net, MSBuildRuntimeValues.any };
private static readonly HashSet<string> ValidMSBuildArchitectureValues = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { MSBuildArchitectureValues.x86, MSBuildArchitectureValues.x64, MSBuildArchitectureValues.arm64, MSBuildArchitectureValues.currentArchitecture, MSBuildArchitectureValues.any };
/// <summary>
/// Returns true if and only if the specified attribute is one of the attributes that the engine specifically recognizes
/// on a task and treats in a special way.
/// </summary>
/// <param name="attribute"></param>
/// <returns>true, if given attribute is a reserved task attribute</returns>
internal static bool IsSpecialTaskAttribute(string attribute)
{
// Currently the known "special" attributes for a task are:
// Condition, ContinueOnError
//
// We want to match case-sensitively on all of them
return KnownSpecialTaskAttributes.Contains(attribute);
}
/// <summary>
/// Checks if the specified attribute is a reserved task attribute with incorrect casing.
/// </summary>
/// <param name="attribute"></param>
/// <returns>true, if the given attribute is reserved and badly cased</returns>
internal static bool IsBadlyCasedSpecialTaskAttribute(string attribute)
{
return !IsSpecialTaskAttribute(attribute) && KnownSpecialTaskAttributesIgnoreCase.Contains(attribute);
}
/// <summary>
/// Indicates if the specified attribute cannot be used for batching targets.
/// </summary>
/// <param name="attribute"></param>
/// <returns>true, if a target cannot batch on the given attribute</returns>
internal static bool IsNonBatchingTargetAttribute(string attribute)
{
return KnownBatchingTargetAttributes.Contains(attribute);
}
/// <summary>
/// Returns true if the given string is a valid member of the MSBuildRuntimeValues set
/// </summary>
internal static bool IsValidMSBuildRuntimeValue(string runtime)
{
return runtime == null || ValidMSBuildRuntimeValues.Contains(runtime);
}
/// <summary>
/// Returns true if the given string is a valid member of the MSBuildArchitectureValues set
/// </summary>
internal static bool IsValidMSBuildArchitectureValue(string architecture)
{
return architecture == null || ValidMSBuildArchitectureValues.Contains(architecture);
}
/// <summary>
/// Compares two members of MSBuildRuntimeValues, returning true if they count as a match, and false otherwise.
/// </summary>
internal static bool RuntimeValuesMatch(string runtimeA, string runtimeB)
{
ErrorUtilities.VerifyThrow(runtimeA != String.Empty && runtimeB != String.Empty, "We should never get an empty string passed to this method");
if (runtimeA == null || runtimeB == null)
{
// neither one cares, or only one cares, so they match by default.
return true;
}
if (runtimeA.Equals(runtimeB, StringComparison.OrdinalIgnoreCase))
{
// if they are equal, of course they match
return true;
}
if (runtimeA.Equals(MSBuildRuntimeValues.any, StringComparison.OrdinalIgnoreCase) || runtimeB.Equals(MSBuildRuntimeValues.any, StringComparison.OrdinalIgnoreCase))
{
// one or both explicitly don't care -- still a match.
return true;
}
if ((runtimeA.Equals(MSBuildRuntimeValues.currentRuntime, StringComparison.OrdinalIgnoreCase) && runtimeB.Equals(GetCurrentMSBuildRuntime(), StringComparison.OrdinalIgnoreCase)) ||
(runtimeA.Equals(GetCurrentMSBuildRuntime(), StringComparison.OrdinalIgnoreCase) && runtimeB.Equals(MSBuildRuntimeValues.currentRuntime, StringComparison.OrdinalIgnoreCase)))
{
// Matches the current runtime, so match.
return true;
}
// if none of the above is true, then it doesn't match ...
return false;
}
/// <summary>
/// Given two MSBuildRuntime values, returns the concrete result of merging the two. If the merge fails, the merged runtime
/// string is returned null, and the return value of the method is false. Otherwise, if the merge succeeds, the method returns
/// true with the merged runtime value. E.g.:
/// "CLR4" + "CLR2" = null (false)
/// "CLR2" + "don't care" = "CLR2" (true)
/// "current runtime" + "CLR4" = "CLR4" (true)
/// "current runtime" + "don't care" = "CLR4" (true)
/// If both specify "don't care", then defaults to the current runtime -- CLR4.
/// A null or empty string is interpreted as "don't care".
/// </summary>
internal static bool TryMergeRuntimeValues(string runtimeA, string runtimeB, out string mergedRuntime)
{
ErrorUtilities.VerifyThrow(runtimeA != String.Empty && runtimeB != String.Empty, "We should never get an empty string passed to this method");
// set up the defaults
if (runtimeA == null)
{
runtimeA = MSBuildRuntimeValues.any;
}
if (runtimeB == null)
{
runtimeB = MSBuildRuntimeValues.any;
}
string actualCurrentRuntime = GetCurrentMSBuildRuntime();
// if they're equal, then there's no problem -- just return the equivalent runtime.
if (runtimeA.Equals(runtimeB, StringComparison.OrdinalIgnoreCase))
{
if (runtimeA.Equals(MSBuildRuntimeValues.currentRuntime, StringComparison.OrdinalIgnoreCase) ||
runtimeA.Equals(MSBuildRuntimeValues.any, StringComparison.OrdinalIgnoreCase))
{
mergedRuntime = actualCurrentRuntime;
}
else
{
mergedRuntime = runtimeA;
}
return true;
}
// if both A and B are one of actual-current-runtime, don't care or current,
// then the end result will be current-runtime no matter what.
if (
(
runtimeA.Equals(actualCurrentRuntime, StringComparison.OrdinalIgnoreCase) ||
runtimeA.Equals(MSBuildRuntimeValues.currentRuntime, StringComparison.OrdinalIgnoreCase) ||
runtimeA.Equals(MSBuildRuntimeValues.any, StringComparison.OrdinalIgnoreCase)) &&
(
runtimeB.Equals(actualCurrentRuntime, StringComparison.OrdinalIgnoreCase) ||
runtimeB.Equals(MSBuildRuntimeValues.currentRuntime, StringComparison.OrdinalIgnoreCase) ||
runtimeB.Equals(MSBuildRuntimeValues.any, StringComparison.OrdinalIgnoreCase)))
{
mergedRuntime = actualCurrentRuntime;
return true;
}
// If A doesn't care, then it's B -- and we can say B straight out, because if B were one of the
// special cases (current runtime or don't care) then it would already have been caught in the
// previous clause.
if (runtimeA.Equals(MSBuildRuntimeValues.any, StringComparison.OrdinalIgnoreCase))
{
mergedRuntime = runtimeB;
return true;
}
// And vice versa
if (runtimeB.Equals(MSBuildRuntimeValues.any, StringComparison.OrdinalIgnoreCase))
{
mergedRuntime = runtimeA;
return true;
}
// and now we've run out of things that it could be -- all the remaining options are non-matches.
mergedRuntime = null;
return false;
}
/// <summary>
/// Compares two members of MSBuildArchitectureValues, returning true if they count as a match, and false otherwise.
/// </summary>
internal static bool ArchitectureValuesMatch(string architectureA, string architectureB)
{
ErrorUtilities.VerifyThrow(architectureA != String.Empty && architectureB != String.Empty, "We should never get an empty string passed to this method");
if (architectureA == null || architectureB == null)
{
// neither one cares, or only one cares, so they match by default.
return true;
}
if (architectureA.Equals(architectureB, StringComparison.OrdinalIgnoreCase))
{
// if they are equal, of course they match
return true;
}
if (architectureA.Equals(MSBuildArchitectureValues.any, StringComparison.OrdinalIgnoreCase) || architectureB.Equals(MSBuildArchitectureValues.any, StringComparison.OrdinalIgnoreCase))
{
// one or both explicitly don't care -- still a match.
return true;
}
string currentArchitecture = GetCurrentMSBuildArchitecture();
if ((architectureA.Equals(MSBuildArchitectureValues.currentArchitecture, StringComparison.OrdinalIgnoreCase) && architectureB.Equals(currentArchitecture, StringComparison.OrdinalIgnoreCase)) ||
(architectureA.Equals(currentArchitecture, StringComparison.OrdinalIgnoreCase) && architectureB.Equals(MSBuildArchitectureValues.currentArchitecture, StringComparison.OrdinalIgnoreCase)))
{
return true;
}
// if none of the above is true, then it doesn't match ...
return false;
}
/// <summary>
/// Given an MSBuildRuntime value that may be non-explicit -- e.g. "CurrentRuntime" or "Any" --
/// return the specific MSBuildRuntime value that it would map to in this case. If it does not map
/// to any known runtime, just return it as is -- maybe someone else knows what to do with it; if
/// not, they'll certainly have more context on logging or throwing the error.
/// </summary>
internal static string GetExplicitMSBuildRuntime(string runtime)
{
if (runtime == null ||
MSBuildRuntimeValues.any.Equals(runtime, StringComparison.OrdinalIgnoreCase) ||
MSBuildRuntimeValues.currentRuntime.Equals(runtime, StringComparison.OrdinalIgnoreCase))
{
// Default to current.
return GetCurrentMSBuildRuntime();
}
else
{
// either it's already a valid, specific runtime, or we don't know what to do with it. Either way, return.
return runtime;
}
}
/// <summary>
/// Given two MSBuildArchitecture values, returns the concrete result of merging the two. If the merge fails, the merged architecture
/// string is returned null, and the return value of the method is false. Otherwise, if the merge succeeds, the method returns
/// true with the merged architecture value. E.g.:
/// "x86" + "x64" = null (false)
/// "x86" + "don't care" = "x86" (true)
/// "current architecture" + "x86" = "x86" (true) on a 32-bit process, and null (false) on a 64-bit process
/// "current architecture" + "don't care" = "x86" (true) on a 32-bit process, and "x64" (true) on a 64-bit process
/// A null or empty string is interpreted as "don't care".
/// If both specify "don't care", then defaults to whatever the current process architecture is.
/// </summary>
internal static bool TryMergeArchitectureValues(string architectureA, string architectureB, out string mergedArchitecture)
{
ErrorUtilities.VerifyThrow(architectureA != String.Empty && architectureB != String.Empty, "We should never get an empty string passed to this method");
// set up the defaults
if (architectureA == null)
{
architectureA = MSBuildArchitectureValues.any;
}
if (architectureB == null)
{
architectureB = MSBuildArchitectureValues.any;
}
string currentArchitecture = GetCurrentMSBuildArchitecture();
// if they're equal, then there's no problem -- just return the equivalent runtime.
if (architectureA.Equals(architectureB, StringComparison.OrdinalIgnoreCase))
{
if (architectureA.Equals(MSBuildArchitectureValues.currentArchitecture, StringComparison.OrdinalIgnoreCase) ||
architectureA.Equals(MSBuildArchitectureValues.any, StringComparison.OrdinalIgnoreCase))
{
mergedArchitecture = currentArchitecture;
}
else
{
mergedArchitecture = architectureA;
}
return true;
}
// if both A and B are one of CLR4, don't care, or current, then the end result will be CLR4 no matter what.
if (
(
architectureA.Equals(currentArchitecture, StringComparison.OrdinalIgnoreCase) ||
architectureA.Equals(MSBuildArchitectureValues.currentArchitecture, StringComparison.OrdinalIgnoreCase) ||
architectureA.Equals(MSBuildArchitectureValues.any, StringComparison.OrdinalIgnoreCase)) &&
(
architectureB.Equals(currentArchitecture, StringComparison.OrdinalIgnoreCase) ||
architectureB.Equals(MSBuildArchitectureValues.currentArchitecture, StringComparison.OrdinalIgnoreCase) ||
architectureB.Equals(MSBuildArchitectureValues.any, StringComparison.OrdinalIgnoreCase)))
{
mergedArchitecture = currentArchitecture;
return true;
}
// If A doesn't care, then it's B -- and we can say B straight out, because if B were one of the
// special cases (current runtime or don't care) then it would already have been caught in the
// previous clause.
if (architectureA.Equals(MSBuildArchitectureValues.any, StringComparison.OrdinalIgnoreCase))
{
mergedArchitecture = architectureB;
return true;
}
// And vice versa
if (architectureB.Equals(MSBuildArchitectureValues.any, StringComparison.OrdinalIgnoreCase))
{
mergedArchitecture = architectureA;
return true;
}
// and now we've run out of things that it could be -- all the remaining options are non-matches.
mergedArchitecture = null;
return false;
}
/// <summary>
/// Returns the MSBuildArchitecture value corresponding to the current process' architecture.
/// </summary>
/// <comments>
/// Revisit if we ever run on something other than Intel.
/// </comments>
internal static string GetCurrentMSBuildArchitecture()
{
#if !CLR2COMPATIBILITY
string currentArchitecture = string.Empty;
switch (RuntimeInformation.ProcessArchitecture)
{
case Architecture.X86:
currentArchitecture = MSBuildArchitectureValues.x86;
break;
case Architecture.X64:
currentArchitecture = MSBuildArchitectureValues.x64;
break;
case Architecture.Arm64:
currentArchitecture = MSBuildArchitectureValues.arm64;
break;
default:
// We're not sure what the architecture is, default to original 32/64bit logic.
// This allows architectures like s390x to continue working.
// https://github.com/dotnet/msbuild/issues/7729
currentArchitecture = (IntPtr.Size == sizeof(Int64)) ? MSBuildArchitectureValues.x64 : MSBuildArchitectureValues.x86;
break;
}
#else
string currentArchitecture = (IntPtr.Size == sizeof(Int64)) ? MSBuildArchitectureValues.x64 : MSBuildArchitectureValues.x86;
#endif
return currentArchitecture;
}
/// <summary>
/// Returns the MSBuildRuntime value corresponding to the current process' runtime.
/// </summary>
internal static string GetCurrentMSBuildRuntime()
{
#if NET40_OR_GREATER
return MSBuildRuntimeValues.clr4;
#else
return MSBuildRuntimeValues.net;
#endif
}
/// <summary>
/// Given an MSBuildArchitecture value that may be non-explicit -- e.g. "CurrentArchitecture" or "Any" --
/// return the specific MSBuildArchitecture value that it would map to in this case. If it does not map
/// to any known architecture, just return it as is -- maybe someone else knows what to do with it; if
/// not, they'll certainly have more context on logging or throwing the error.
/// </summary>
internal static string GetExplicitMSBuildArchitecture(string architecture)
{
if (architecture == null ||
MSBuildArchitectureValues.any.Equals(architecture, StringComparison.OrdinalIgnoreCase) ||
MSBuildArchitectureValues.currentArchitecture.Equals(architecture, StringComparison.OrdinalIgnoreCase))
{
string currentArchitecture = GetCurrentMSBuildArchitecture();
return currentArchitecture;
}
else
{
// either it's already a valid, specific architecture, or we don't know what to do with it. Either way, return.
return architecture;
}
}
}
}
|