From 5da3cc260eedcad85a0482fb586b49bc9ed3ded1 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 08:52:28 -0800 Subject: [PATCH 001/136] Copy all external linked files into MSBuildTaskHost Copies all shared files and files linked from other projects directly into MSBuildTaskHost. In addition, all items in MSBuildTaskHost.csproj have been updated to point to the new files. --- src/MSBuildTaskHost/AssemblyLoadInfo.cs | 239 ++ src/MSBuildTaskHost/AssemblyNameComparer.cs | 104 + src/MSBuildTaskHost/AssemblyNameExtension.cs | 996 +++++++++ src/MSBuildTaskHost/BinaryReaderExtensions.cs | 144 ++ src/MSBuildTaskHost/BinaryWriterExtensions.cs | 143 ++ src/MSBuildTaskHost/BufferedReadStream.cs | 210 ++ src/MSBuildTaskHost/BuildEnvironmentHelper.cs | 724 +++++++ src/MSBuildTaskHost/CollectionHelpers.cs | 82 + .../CommunicationsUtilities.cs | 1304 +++++++++++ src/MSBuildTaskHost/Constants.cs | 270 +++ src/MSBuildTaskHost/CopyOnWriteDictionary.cs | 413 ++++ src/MSBuildTaskHost/EnvironmentUtilities.cs | 106 + src/MSBuildTaskHost/ErrorUtilities.cs | 651 ++++++ src/MSBuildTaskHost/EscapingUtilities.cs | 312 +++ src/MSBuildTaskHost/ExceptionHandling.cs | 441 ++++ src/MSBuildTaskHost/FileSystem/FileSystems.cs | 31 + src/MSBuildTaskHost/FileSystem/IFileSystem.cs | 49 + src/MSBuildTaskHost/FileUtilities.cs | 1604 ++++++++++++++ src/MSBuildTaskHost/FileUtilitiesRegex.cs | 171 ++ .../Framework/AssemblyUtilities.cs | 171 ++ .../Framework/BinaryReaderFactory.cs | 14 + .../Framework/BinaryTranslator.cs | 1817 ++++++++++++++++ .../Framework/BuildEngineResult.cs | 64 + .../Framework/BuildEnvironmentState.cs | 19 + .../BuildException/BuildExceptionBase.cs | 157 ++ .../BuildExceptionRemoteState.cs | 35 + .../BuildExceptionSerializationHelper.cs | 91 + .../GenericBuildTransferredException.cs | 22 + src/MSBuildTaskHost/Framework/ChangeWaves.cs | 215 ++ .../Framework/ErrorUtilities.cs | 62 + .../Framework/FileUtilities.cs | 133 ++ .../Framework/IBuildEngine3.cs | 65 + .../Framework/IConstrainedEqualityComparer.cs | 30 + .../Framework/IExtendedBuildEventArgs.cs | 33 + src/MSBuildTaskHost/Framework/ITaskItem2.cs | 58 + .../Framework/ITranslatable.cs | 18 + src/MSBuildTaskHost/Framework/ITranslator.cs | 477 ++++ .../ImmutableDictionaryExtensions.cs | 47 + .../Framework/InternPathIds.cs | 7 + .../Framework/InternalErrorException.cs | 155 ++ .../Framework/InterningReadTranslator.cs | 84 + .../Framework/InterningWriteTranslator.cs | 173 ++ .../Framework/Logging/AnsiDetector.cs | 52 + .../MSBuildNameIgnoreCaseComparer.cs | 194 ++ .../Framework/NativeMethods.cs | 1913 +++++++++++++++++ .../CallerArgumentExpressionAttribute.cs | 26 + .../Polyfills/StringSyntaxAttribute.cs | 78 + .../Framework/ResponseFileUsedEventArgs.cs | 26 + .../Framework/RunInSTAAttribute.cs | 26 + .../Framework/SerializableMetadata.cs | 62 + .../Framework/StringBuilderCache.cs | 115 + .../Framework/SupportedOSPlatform.cs | 27 + .../Framework/TaskHostParameters.cs | 168 ++ src/MSBuildTaskHost/Framework/Traits.cs | 632 ++++++ .../Framework/VisualStudioLocationHelper.cs | 143 ++ src/MSBuildTaskHost/GlobalUsings.cs | 8 + src/MSBuildTaskHost/INodeEndpoint.cs | 113 + src/MSBuildTaskHost/INodePacket.cs | 381 ++++ src/MSBuildTaskHost/INodePacketFactory.cs | 61 + src/MSBuildTaskHost/INodePacketHandler.cs | 21 + src/MSBuildTaskHost/InterningBinaryReader.cs | 311 +++ src/MSBuildTaskHost/IsExternalInit.cs | 22 + src/MSBuildTaskHost/LoadedType.cs | 256 +++ src/MSBuildTaskHost/LogMessagePacketBase.cs | 1075 +++++++++ src/MSBuildTaskHost/MSBuild/MSBuild.ico | Bin 0 -> 92547 bytes .../MSBuild/NodeEndpointOutOfProcTaskHost.cs | 40 + .../OutOfProcTaskAppDomainWrapperBase.cs | 455 ++++ .../MSBuild/OutOfProcTaskHostNode.cs | 1296 +++++++++++ src/MSBuildTaskHost/MSBuildTaskHost.csproj | 296 +-- src/MSBuildTaskHost/Modifiers.cs | 470 ++++ src/MSBuildTaskHost/NamedPipeUtil.cs | 53 + src/MSBuildTaskHost/NodeBuildComplete.cs | 83 + .../NodeEndpointOutOfProcBase.cs | 817 +++++++ .../NodeEngineShutdownReason.cs | 35 + src/MSBuildTaskHost/NodePacketFactory.cs | 127 ++ src/MSBuildTaskHost/NodeShutdown.cs | 123 ++ .../OutOfProcTaskHostTaskResult.cs | 136 ++ .../ReadOnlyEmptyCollection.cs | 145 ++ .../ReadOnlyEmptyDictionary.cs | 329 +++ src/MSBuildTaskHost/ResourceUtilities.cs | 481 +++++ .../Resources/Strings.shared.resx | 379 ++++ .../Resources/xlf/Strings.shared.cs.xlf | 349 +++ .../Resources/xlf/Strings.shared.de.xlf | 349 +++ .../Resources/xlf/Strings.shared.es.xlf | 349 +++ .../Resources/xlf/Strings.shared.fr.xlf | 349 +++ .../Resources/xlf/Strings.shared.it.xlf | 349 +++ .../Resources/xlf/Strings.shared.ja.xlf | 349 +++ .../Resources/xlf/Strings.shared.ko.xlf | 349 +++ .../Resources/xlf/Strings.shared.pl.xlf | 349 +++ .../Resources/xlf/Strings.shared.pt-BR.xlf | 349 +++ .../Resources/xlf/Strings.shared.ru.xlf | 349 +++ .../Resources/xlf/Strings.shared.tr.xlf | 349 +++ .../Resources/xlf/Strings.shared.xlf | 235 ++ .../Resources/xlf/Strings.shared.zh-Hans.xlf | 349 +++ .../Resources/xlf/Strings.shared.zh-Hant.xlf | 349 +++ .../TaskEngineAssemblyResolver.cs | 169 ++ src/MSBuildTaskHost/TaskHostConfiguration.cs | 557 +++++ src/MSBuildTaskHost/TaskHostTaskCancelled.cs | 48 + src/MSBuildTaskHost/TaskHostTaskComplete.cs | 265 +++ src/MSBuildTaskHost/TaskLoader.cs | 207 ++ src/MSBuildTaskHost/TaskParameter.cs | 948 ++++++++ .../TaskParameterTypeVerifier.cs | 75 + src/MSBuildTaskHost/TranslatorHelpers.cs | 430 ++++ src/MSBuildTaskHost/XMakeAttributes.cs | 483 +++++ 104 files changed, 30666 insertions(+), 204 deletions(-) create mode 100644 src/MSBuildTaskHost/AssemblyLoadInfo.cs create mode 100644 src/MSBuildTaskHost/AssemblyNameComparer.cs create mode 100644 src/MSBuildTaskHost/AssemblyNameExtension.cs create mode 100644 src/MSBuildTaskHost/BinaryReaderExtensions.cs create mode 100644 src/MSBuildTaskHost/BinaryWriterExtensions.cs create mode 100644 src/MSBuildTaskHost/BufferedReadStream.cs create mode 100644 src/MSBuildTaskHost/BuildEnvironmentHelper.cs create mode 100644 src/MSBuildTaskHost/CollectionHelpers.cs create mode 100644 src/MSBuildTaskHost/CommunicationsUtilities.cs create mode 100644 src/MSBuildTaskHost/Constants.cs create mode 100644 src/MSBuildTaskHost/CopyOnWriteDictionary.cs create mode 100644 src/MSBuildTaskHost/EnvironmentUtilities.cs create mode 100644 src/MSBuildTaskHost/ErrorUtilities.cs create mode 100644 src/MSBuildTaskHost/EscapingUtilities.cs create mode 100644 src/MSBuildTaskHost/ExceptionHandling.cs create mode 100644 src/MSBuildTaskHost/FileSystem/FileSystems.cs create mode 100644 src/MSBuildTaskHost/FileSystem/IFileSystem.cs create mode 100644 src/MSBuildTaskHost/FileUtilities.cs create mode 100644 src/MSBuildTaskHost/FileUtilitiesRegex.cs create mode 100644 src/MSBuildTaskHost/Framework/AssemblyUtilities.cs create mode 100644 src/MSBuildTaskHost/Framework/BinaryReaderFactory.cs create mode 100644 src/MSBuildTaskHost/Framework/BinaryTranslator.cs create mode 100644 src/MSBuildTaskHost/Framework/BuildEngineResult.cs create mode 100644 src/MSBuildTaskHost/Framework/BuildEnvironmentState.cs create mode 100644 src/MSBuildTaskHost/Framework/BuildException/BuildExceptionBase.cs create mode 100644 src/MSBuildTaskHost/Framework/BuildException/BuildExceptionRemoteState.cs create mode 100644 src/MSBuildTaskHost/Framework/BuildException/BuildExceptionSerializationHelper.cs create mode 100644 src/MSBuildTaskHost/Framework/BuildException/GenericBuildTransferredException.cs create mode 100644 src/MSBuildTaskHost/Framework/ChangeWaves.cs create mode 100644 src/MSBuildTaskHost/Framework/ErrorUtilities.cs create mode 100644 src/MSBuildTaskHost/Framework/FileUtilities.cs create mode 100644 src/MSBuildTaskHost/Framework/IBuildEngine3.cs create mode 100644 src/MSBuildTaskHost/Framework/IConstrainedEqualityComparer.cs create mode 100644 src/MSBuildTaskHost/Framework/IExtendedBuildEventArgs.cs create mode 100644 src/MSBuildTaskHost/Framework/ITaskItem2.cs create mode 100644 src/MSBuildTaskHost/Framework/ITranslatable.cs create mode 100644 src/MSBuildTaskHost/Framework/ITranslator.cs create mode 100644 src/MSBuildTaskHost/Framework/ImmutableDictionaryExtensions.cs create mode 100644 src/MSBuildTaskHost/Framework/InternPathIds.cs create mode 100644 src/MSBuildTaskHost/Framework/InternalErrorException.cs create mode 100644 src/MSBuildTaskHost/Framework/InterningReadTranslator.cs create mode 100644 src/MSBuildTaskHost/Framework/InterningWriteTranslator.cs create mode 100644 src/MSBuildTaskHost/Framework/Logging/AnsiDetector.cs create mode 100644 src/MSBuildTaskHost/Framework/MSBuildNameIgnoreCaseComparer.cs create mode 100644 src/MSBuildTaskHost/Framework/NativeMethods.cs create mode 100644 src/MSBuildTaskHost/Framework/Polyfills/CallerArgumentExpressionAttribute.cs create mode 100644 src/MSBuildTaskHost/Framework/Polyfills/StringSyntaxAttribute.cs create mode 100644 src/MSBuildTaskHost/Framework/ResponseFileUsedEventArgs.cs create mode 100644 src/MSBuildTaskHost/Framework/RunInSTAAttribute.cs create mode 100644 src/MSBuildTaskHost/Framework/SerializableMetadata.cs create mode 100644 src/MSBuildTaskHost/Framework/StringBuilderCache.cs create mode 100644 src/MSBuildTaskHost/Framework/SupportedOSPlatform.cs create mode 100644 src/MSBuildTaskHost/Framework/TaskHostParameters.cs create mode 100644 src/MSBuildTaskHost/Framework/Traits.cs create mode 100644 src/MSBuildTaskHost/Framework/VisualStudioLocationHelper.cs create mode 100644 src/MSBuildTaskHost/GlobalUsings.cs create mode 100644 src/MSBuildTaskHost/INodeEndpoint.cs create mode 100644 src/MSBuildTaskHost/INodePacket.cs create mode 100644 src/MSBuildTaskHost/INodePacketFactory.cs create mode 100644 src/MSBuildTaskHost/INodePacketHandler.cs create mode 100644 src/MSBuildTaskHost/InterningBinaryReader.cs create mode 100644 src/MSBuildTaskHost/IsExternalInit.cs create mode 100644 src/MSBuildTaskHost/LoadedType.cs create mode 100644 src/MSBuildTaskHost/LogMessagePacketBase.cs create mode 100644 src/MSBuildTaskHost/MSBuild/MSBuild.ico create mode 100644 src/MSBuildTaskHost/MSBuild/NodeEndpointOutOfProcTaskHost.cs create mode 100644 src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs create mode 100644 src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs create mode 100644 src/MSBuildTaskHost/Modifiers.cs create mode 100644 src/MSBuildTaskHost/NamedPipeUtil.cs create mode 100644 src/MSBuildTaskHost/NodeBuildComplete.cs create mode 100644 src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs create mode 100644 src/MSBuildTaskHost/NodeEngineShutdownReason.cs create mode 100644 src/MSBuildTaskHost/NodePacketFactory.cs create mode 100644 src/MSBuildTaskHost/NodeShutdown.cs create mode 100644 src/MSBuildTaskHost/OutOfProcTaskHostTaskResult.cs create mode 100644 src/MSBuildTaskHost/ReadOnlyEmptyCollection.cs create mode 100644 src/MSBuildTaskHost/ReadOnlyEmptyDictionary.cs create mode 100644 src/MSBuildTaskHost/ResourceUtilities.cs create mode 100644 src/MSBuildTaskHost/Resources/Strings.shared.resx create mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.cs.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.de.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.es.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.fr.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.it.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.ja.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.ko.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.pl.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.pt-BR.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.ru.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.tr.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.zh-Hans.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.zh-Hant.xlf create mode 100644 src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs create mode 100644 src/MSBuildTaskHost/TaskHostConfiguration.cs create mode 100644 src/MSBuildTaskHost/TaskHostTaskCancelled.cs create mode 100644 src/MSBuildTaskHost/TaskHostTaskComplete.cs create mode 100644 src/MSBuildTaskHost/TaskLoader.cs create mode 100644 src/MSBuildTaskHost/TaskParameter.cs create mode 100644 src/MSBuildTaskHost/TaskParameterTypeVerifier.cs create mode 100644 src/MSBuildTaskHost/TranslatorHelpers.cs create mode 100644 src/MSBuildTaskHost/XMakeAttributes.cs diff --git a/src/MSBuildTaskHost/AssemblyLoadInfo.cs b/src/MSBuildTaskHost/AssemblyLoadInfo.cs new file mode 100644 index 00000000000..4ced73e1360 --- /dev/null +++ b/src/MSBuildTaskHost/AssemblyLoadInfo.cs @@ -0,0 +1,239 @@ +// 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.Diagnostics; +using System.IO; +using Microsoft.Build.BackEnd; + +#nullable disable + +namespace Microsoft.Build.Shared +{ + /// + /// This class packages information about how to load a given assembly -- an assembly can be loaded by either its assembly + /// name (strong or weak), or its filename/path. + /// + /// + /// Uses factory to instantiate correct private class to save space: only one field is ever used of the two. + /// + internal abstract class AssemblyLoadInfo : ITranslatable, IEquatable + { + /// + /// This constructor initializes the assembly information. + /// + internal static AssemblyLoadInfo Create(string assemblyName, string assemblyFile) + { + ErrorUtilities.VerifyThrow((!string.IsNullOrEmpty(assemblyName)) || (!string.IsNullOrEmpty(assemblyFile)), + "We must have either the assembly name or the assembly file/path."); + ErrorUtilities.VerifyThrow((assemblyName == null) || (assemblyFile == null), + "We must not have both the assembly name and the assembly file/path."); + + if (assemblyName != null) + { + return new AssemblyLoadInfoWithName(assemblyName); + } + else + { + return new AssemblyLoadInfoWithFile(assemblyFile); + } + } + + /// + /// Gets the assembly's identity denoted by its strong/weak name. + /// + public abstract string AssemblyName + { + get; + } + + /// + /// Gets the path to the assembly file. + /// + public abstract string AssemblyFile + { + get; + } + + /// + /// Get the assembly location + /// + internal abstract string AssemblyLocation + { + get; + } + + /// + /// Gets whether this assembly is an inline task assembly that should be loaded from bytes to avoid file locking. + /// + internal abstract bool IsInlineTask + { + get; + } + + /// + /// Computes a hashcode for this assembly info, so this object can be used as a key into + /// a hash table. + /// + public override int GetHashCode() + { + return AssemblyLocation.GetHashCode(); + } + + public bool Equals(AssemblyLoadInfo other) + { + return Equals((object)other); + } + + /// + /// Determines if two AssemblyLoadInfos are effectively the same. + /// + public override bool Equals(Object obj) + { + if (obj == null) + { + return false; + } + + AssemblyLoadInfo otherAssemblyInfo = obj as AssemblyLoadInfo; + + if (otherAssemblyInfo == null) + { + return false; + } + + return (this.AssemblyName == otherAssemblyInfo.AssemblyName) && (this.AssemblyFile == otherAssemblyInfo.AssemblyFile); + } + + public void Translate(ITranslator translator) + { + ErrorUtilities.VerifyThrow(translator.Mode == TranslationDirection.WriteToStream, "write only"); + string assemblyName = AssemblyName; + string assemblyFile = AssemblyFile; + translator.Translate(ref assemblyName); + translator.Translate(ref assemblyFile); + } + + public static AssemblyLoadInfo FactoryForTranslation(ITranslator translator) + { + string assemblyName = null; + string assemblyFile = null; + translator.Translate(ref assemblyName); + translator.Translate(ref assemblyFile); + + return Create(assemblyName, assemblyFile); + } + + /// + /// Assembly represented by name + /// + [DebuggerDisplay("{AssemblyName}")] + private sealed class AssemblyLoadInfoWithName : AssemblyLoadInfo + { + /// + /// Assembly name + /// + private string _assemblyName; + + /// + /// Constructor + /// + internal AssemblyLoadInfoWithName(string assemblyName) + { + _assemblyName = assemblyName; + } + + /// + /// Gets the assembly's identity denoted by its strong/weak name. + /// + public override string AssemblyName + { + get { return _assemblyName; } + } + + /// + /// Gets the path to the assembly file. + /// + public override string AssemblyFile + { + get { return null; } + } + + /// + /// Get the assembly location + /// + internal override string AssemblyLocation + { + get { return _assemblyName; } + } + + /// + /// Gets whether this assembly is an inline task assembly. + /// Assembly names are never inline tasks. + /// + internal override bool IsInlineTask + { + get { return false; } + } + } + + /// + /// Assembly info that uses a file path + /// + [DebuggerDisplay("{AssemblyFile}")] + private sealed class AssemblyLoadInfoWithFile : AssemblyLoadInfo + { + /// + /// Path to assembly + /// + private string _assemblyFile; + + /// + /// Constructor + /// + internal AssemblyLoadInfoWithFile(string assemblyFile) + { + ErrorUtilities.VerifyThrow(Path.IsPathRooted(assemblyFile), "Assembly file path should be rooted"); + + _assemblyFile = assemblyFile; + } + + /// + /// Gets the assembly's identity denoted by its strong/weak name. + /// + public override string AssemblyName + { + get { return null; } + } + + /// + /// Gets the path to the assembly file. + /// + public override string AssemblyFile + { + get { return _assemblyFile; } + } + + /// + /// Get the assembly location + /// + internal override string AssemblyLocation + { + get { return _assemblyFile; } + } + + /// + /// Gets whether this assembly is an inline task assembly. + /// Detects inline tasks by checking if the file path ends with the inline task suffix. + /// + internal override bool IsInlineTask + { +#if !NET35 + get { return _assemblyFile?.EndsWith(TaskFactoryUtilities.InlineTaskSuffix, StringComparison.OrdinalIgnoreCase) == true; } +#else + get { return false; } +#endif + } + } + } +} diff --git a/src/MSBuildTaskHost/AssemblyNameComparer.cs b/src/MSBuildTaskHost/AssemblyNameComparer.cs new file mode 100644 index 00000000000..f9a30fab128 --- /dev/null +++ b/src/MSBuildTaskHost/AssemblyNameComparer.cs @@ -0,0 +1,104 @@ +// 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; +using System.Collections.Generic; + +#nullable disable + +namespace Microsoft.Build.Shared +{ + /// + /// IKeyComparer implementation that compares AssemblyNames for using in Hashtables. + /// + [Serializable] + internal sealed class AssemblyNameComparer : IComparer, IEqualityComparer, IEqualityComparer + { + /// + /// Comparer for two assembly name extensions + /// + internal static readonly IComparer Comparer = new AssemblyNameComparer(false); + + /// + /// Comparer for two assembly name extensions + /// + internal static readonly IComparer ComparerConsiderRetargetable = new AssemblyNameComparer(true); + + /// + /// Comparer for two assembly name extensions + /// + internal static readonly IEqualityComparer GenericComparer = Comparer as IEqualityComparer; + + /// + /// Comparer for two assembly name extensions + /// + internal static readonly IEqualityComparer GenericComparerConsiderRetargetable = ComparerConsiderRetargetable as IEqualityComparer; + + /// + /// Should the comparer consider the retargetable flag when doing comparisons + /// + private readonly bool considerRetargetableFlag; + + /// + /// Private construct so there's only one instance. + /// + private AssemblyNameComparer(bool considerRetargetableFlag) + { + this.considerRetargetableFlag = considerRetargetableFlag; + } + + /// + /// Compare o1 and o2 as AssemblyNames. + /// + public int Compare(object o1, object o2) + { + AssemblyNameExtension a1 = (AssemblyNameExtension)o1; + AssemblyNameExtension a2 = (AssemblyNameExtension)o2; + + int result = a1.CompareTo(a2, considerRetargetableFlag); + return result; + } + + /// + /// Treat o1 and o2 as AssemblyNames. Are they equal? + /// + public new bool Equals(object o1, object o2) + { + AssemblyNameExtension a1 = (AssemblyNameExtension)o1; + AssemblyNameExtension a2 = (AssemblyNameExtension)o2; + return Equals(a1, a2); + } + + /// + /// Get a hashcode for AssemblyName. + /// + public int GetHashCode(object o) + { + AssemblyNameExtension a = (AssemblyNameExtension)o; + return GetHashCode(a); + } + + #region IEqualityComparer Members + + /// + /// Determine if the assembly name extensions are equal + /// + public bool Equals(AssemblyNameExtension x, AssemblyNameExtension y) + { + bool result = x.Equals(y, considerRetargetableFlag); + return result; + } + + /// + /// Get a hashcode for AssemblyName. + /// + public int GetHashCode(AssemblyNameExtension obj) + { + int result = obj.GetHashCode(); + return result; + } + + #endregion + } +} diff --git a/src/MSBuildTaskHost/AssemblyNameExtension.cs b/src/MSBuildTaskHost/AssemblyNameExtension.cs new file mode 100644 index 00000000000..9f4b12b918e --- /dev/null +++ b/src/MSBuildTaskHost/AssemblyNameExtension.cs @@ -0,0 +1,996 @@ +// 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; +using System.Configuration.Assemblies; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; +using Microsoft.Build.BackEnd; + +#nullable disable + +namespace Microsoft.Build.Shared +{ + /// + /// Specifies the parts of the assembly name to partially match + /// + [FlagsAttribute] + internal enum PartialComparisonFlags : int + { + /// + /// Compare SimpleName A.PartialCompare(B,SimpleName) match the simple name on A and B if the simple name on A is not null. + /// + SimpleName = 1, // 0000 0000 0000 0001 + + /// + /// Compare Version A.PartialCompare(B, Version) match the Version on A and B if the Version on A is not null. + /// + Version = 2, // 0000 0000 0000 0010 + + /// + /// Compare Culture A.PartialCompare(B, Culture) match the Culture on A and B if the Culture on A is not null. + /// + Culture = 4, // 0000 0000 0000 0100 + + /// + /// Compare PublicKeyToken A.PartialCompare(B, PublicKeyToken) match the PublicKeyToken on A and B if the PublicKeyToken on A is not null. + /// + PublicKeyToken = 8, // 0000 0000 0000 1000 + + /// + /// When doing a comparison A.PartialCompare(B, Default) compare all fields of A which are not null with B. + /// + Default = 15, // 0000 0000 0000 1111 + } + + /// + /// A replacement for AssemblyName that optimizes calls to FullName which is expensive. + /// The assembly name is represented internally by an AssemblyName and a string, conversion + /// between the two is done lazily on demand. + /// + [Serializable] + internal sealed class AssemblyNameExtension : ISerializable, IEquatable, ITranslatable + { + private AssemblyName asAssemblyName = null; + private string asString = null; + private bool isSimpleName = false; + private bool hasProcessorArchitectureInFusionName; + private bool immutable; + + /// + /// Set of assemblyNameExtensions that THIS assemblyname was remapped from. + /// + private HashSet remappedFrom; + + private static readonly AssemblyNameExtension s_unnamedAssembly = new AssemblyNameExtension(); + + /// + /// Construct an unnamed assembly. + /// Private because we want only one of these. + /// + private AssemblyNameExtension() + { + } + + /// + /// Construct with AssemblyName. + /// + /// + internal AssemblyNameExtension(AssemblyName assemblyName) : this() + { + asAssemblyName = assemblyName; + } + + /// + /// Construct with string. + /// + /// + internal AssemblyNameExtension(string assemblyName) : this() + { + asString = assemblyName; + } + + /// + /// Construct from a string, but immediately construct a real AssemblyName. + /// This will cause an exception to be thrown up front if the assembly name + /// isn't well formed. + /// + /// + /// The string version of the assembly name. + /// + /// + /// Used when the assembly name comes from a user-controlled source like a project file or config file. + /// Does extra checking on the assembly name and will throw exceptions if something is invalid. + /// + internal AssemblyNameExtension(string assemblyName, bool validate) : this() + { + asString = assemblyName; + + if (validate) + { + // This will throw... + CreateAssemblyName(); + } + } + + /// + /// Ctor for deserializing from state file (binary serialization). + /// This is required because AssemblyName is not Serializable on .NET Core. + /// + /// + /// + private AssemblyNameExtension(SerializationInfo info, StreamingContext context) + { + var hasAssemblyName = info.GetBoolean("hasAN"); + + if (hasAssemblyName) + { + var name = info.GetString("name"); + var publicKey = (byte[])info.GetValue("pk", typeof(byte[])); + var publicKeyToken = (byte[])info.GetValue("pkt", typeof(byte[])); + var version = (Version)info.GetValue("ver", typeof(Version)); + var flags = (AssemblyNameFlags)info.GetInt32("flags"); + var processorArchitecture = (ProcessorArchitecture)info.GetInt32("cpuarch"); + + CultureInfo cultureInfo = null; + var hasCultureInfo = info.GetBoolean("hasCI"); + if (hasCultureInfo) + { + cultureInfo = new CultureInfo(info.GetInt32("ci")); + } + + var hashAlgorithm = (System.Configuration.Assemblies.AssemblyHashAlgorithm)info.GetInt32("hashAlg"); + var versionCompatibility = (AssemblyVersionCompatibility)info.GetInt32("verCompat"); + var codeBase = info.GetString("codebase"); + + asAssemblyName = new AssemblyName + { + Name = name, + Version = version, + Flags = flags, + ProcessorArchitecture = processorArchitecture, + CultureInfo = cultureInfo, + HashAlgorithm = hashAlgorithm, + VersionCompatibility = versionCompatibility, + CodeBase = codeBase, + }; + + asAssemblyName.SetPublicKey(publicKey); + asAssemblyName.SetPublicKeyToken(publicKeyToken); + } + + asString = info.GetString("asStr"); + isSimpleName = info.GetBoolean("isSName"); + hasProcessorArchitectureInFusionName = info.GetBoolean("hasCpuArch"); + immutable = info.GetBoolean("immutable"); + remappedFrom = (HashSet)info.GetValue("remapped", typeof(HashSet)); + } + + /// + /// Ctor for deserializing from state file (custom binary serialization) using translator. + /// + internal AssemblyNameExtension(ITranslator translator) : this() + { + Translate(translator); + } + + /// + /// To be used as a delegate. Gets the AssemblyName of the given file. + /// + /// + /// + internal static AssemblyNameExtension GetAssemblyNameEx(string path) + { + try + { + return new AssemblyNameExtension(AssemblyName.GetAssemblyName(path)); + } + catch (FileLoadException) + { + // Its pretty hard to get here, you need an assembly that contains a valid reference + // to a dependent assembly that, in turn, throws a FileLoadException during GetAssemblyName. + // Still it happened once, with an older version of the CLR. + } + catch (FileNotFoundException) + { + // Its pretty hard to get here, also since we do a file existence check right before calling this method so it can only happen if the file got deleted between that check and this call. + } + + return null; + } + + /// + /// Run after the object has been deserialized + /// + [OnDeserialized] + private void SetRemappedFromDefaultAfterSerialization(StreamingContext sc) + { + InitializeRemappedFrom(); + } + + /// + /// Initialize the remapped from structure. + /// + private void InitializeRemappedFrom() + { + if (remappedFrom == null) + { + remappedFrom = CreateRemappedFrom(); + } + } + + /// + /// Create remappedFrom HashSet. Used by deserialization as well. + /// + private static HashSet CreateRemappedFrom() + { + return new HashSet(AssemblyNameComparer.GenericComparerConsiderRetargetable); + } + + /// + /// Assume there is a string version, create the AssemblyName version. + /// + private void CreateAssemblyName() + { + if (asAssemblyName == null) + { + asAssemblyName = GetAssemblyNameFromDisplayName(asString); + + if (asAssemblyName != null) + { + hasProcessorArchitectureInFusionName = asString.IndexOf("ProcessorArchitecture", StringComparison.OrdinalIgnoreCase) != -1; + isSimpleName = ((Version == null) && (CultureInfo == null) && (GetPublicKeyToken() == null) && (!hasProcessorArchitectureInFusionName)); + } + } + } + + /// + /// Assume there is a string version, create the AssemblyName version. + /// + private void CreateFullName() + { + if (asString == null) + { + asString = asAssemblyName.FullName; + } + } + + /// + /// The base name of the assembly. + /// + /// + internal string Name + { + get + { + // Is there a string? + CreateAssemblyName(); + return asAssemblyName.Name; + } + } + + /// + /// Gets the backing AssemblyName, this can be None. + /// + internal ProcessorArchitecture ProcessorArchitecture => + asAssemblyName?.ProcessorArchitecture ?? ProcessorArchitecture.None; + + /// + /// The assembly's version number. + /// + /// + internal Version Version + { + get + { + // Is there a string? + CreateAssemblyName(); + return asAssemblyName.Version; + } + } + + /// + /// Is the assembly a complex name or a simple name. A simple name is where only the name is set + /// a complex name is where the version, culture or publickeytoken is also set + /// + internal bool IsSimpleName + { + get + { + CreateAssemblyName(); + return isSimpleName; + } + } + + /// + /// Does the fullName have the processor architecture defined + /// + internal bool HasProcessorArchitectureInFusionName + { + get + { + CreateAssemblyName(); + return hasProcessorArchitectureInFusionName; + } + } + + /// + /// Replace the current version with a new version. + /// + /// + internal void ReplaceVersion(Version version) + { + ErrorUtilities.VerifyThrow(!immutable, "Object is immutable cannot replace the version"); + CreateAssemblyName(); + if (asAssemblyName.Version != version) + { + asAssemblyName.Version = version; + + // String would now be invalid. + asString = null; + } + } + + /// + /// The assembly's Culture + /// + /// + internal CultureInfo CultureInfo + { + get + { + // Is there a string? + CreateAssemblyName(); + return asAssemblyName.CultureInfo; + } + } + + /// + /// The assembly's retargetable bit + /// + /// + internal bool Retargetable + { + get + { + // Is there a string? + CreateAssemblyName(); + // Cannot use the HasFlag method on the Flags enum because this class needs to work with 3.5 + return (asAssemblyName.Flags & AssemblyNameFlags.Retargetable) == AssemblyNameFlags.Retargetable; + } + } + + /// + /// The full name of the original extension we were before being remapped. + /// + internal IEnumerable RemappedFromEnumerator + { + get + { + InitializeRemappedFrom(); + return remappedFrom; + } + } + + /// + /// Add an assemblyNameExtension which represents an assembly name which was mapped to THIS assemblyName. + /// + internal void AddRemappedAssemblyName(AssemblyNameExtension extensionToAdd) + { + ErrorUtilities.VerifyThrow(extensionToAdd.Immutable, "ExtensionToAdd is not immutable"); + InitializeRemappedFrom(); + remappedFrom.Add(extensionToAdd); + } + + /// + /// As an AssemblyName + /// + /// + internal AssemblyName AssemblyName + { + get + { + // Is there a string? + CreateAssemblyName(); + return asAssemblyName; + } + } + + /// + /// The assembly's full name. + /// + /// + internal string FullName + { + get + { + // Is there a string? + CreateFullName(); + return asString; + } + } + + /// + /// Get the assembly's public key token. + /// + /// + internal byte[] GetPublicKeyToken() + { + // Is there a string? + CreateAssemblyName(); + return asAssemblyName.GetPublicKeyToken(); + } + + + /// + /// A special "unnamed" instance of AssemblyNameExtension. + /// + /// + internal static AssemblyNameExtension UnnamedAssembly => s_unnamedAssembly; + + /// + /// Compare one assembly name to another. + /// + /// + /// + internal int CompareTo(AssemblyNameExtension that) + { + return CompareTo(that, false); + } + + /// + /// Compare one assembly name to another. + /// + internal int CompareTo(AssemblyNameExtension that, bool considerRetargetableFlag) + { + // Are they identical? + if (this.Equals(that, considerRetargetableFlag)) + { + return 0; + } + + // Are the base names not identical? + int result = CompareBaseNameTo(that); + if (result != 0) + { + return result; + } + + // We would like to compare the version numerically rather than alphabetically (because for example version 10.0.0. should be below 9 not between 1 and 2) + if (this.Version != that.Version) + { + if (this.Version == null) + { + // This is therefore less than that. Since this is null and that is not null + return -1; + } + + // Will not return 0 as the this != that check above takes care of the case where they are equal. + return this.Version.CompareTo(that.Version); + } + + // We need some final collating order for these, alphabetical by FullName seems as good as any. + return string.Compare(this.FullName, that.FullName, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Get a hash code for this assembly name. + /// + /// + internal new int GetHashCode() + { + // Ok, so this isn't a great hashing algorithm. However, basenames with different + // versions or PKTs are relatively uncommon and so collisions should be low. + // Hashing on FullName is wrong because the order of tuple fields is undefined. + int hash = StringComparer.OrdinalIgnoreCase.GetHashCode(this.Name); + return hash; + } + + /// + /// Compare two base names as quickly as possible. + /// + /// + /// + internal int CompareBaseNameTo(AssemblyNameExtension that) + { + int result = CompareBaseNameToImpl(that); +#if DEBUG + // Now, compare to the real value to make sure the result was accurate. + AssemblyName a1 = asAssemblyName; + AssemblyName a2 = that.asAssemblyName; + if (a1 == null) + { + a1 = new AssemblyName(asString); + } + if (a2 == null) + { + a2 = new AssemblyName(that.asString); + } + + int baselineResult = string.Compare(a1.Name, a2.Name, StringComparison.OrdinalIgnoreCase); + ErrorUtilities.VerifyThrow(result == baselineResult, "Optimized version of CompareBaseNameTo didn't return the same result as the baseline."); +#endif + return result; + } + + /// + /// An implementation of compare that compares two base + /// names as quickly as possible. + /// + /// + /// + private int CompareBaseNameToImpl(AssemblyNameExtension that) + { + // Pointer compare, if identical then base names are equal. + if (this == that) + { + return 0; + } + + // Do both have assembly names? + if (asAssemblyName != null && that.asAssemblyName != null) + { + // Pointer compare or base name compare. + return asAssemblyName == that.asAssemblyName + ? 0 + : string.Compare(asAssemblyName.Name, that.asAssemblyName.Name, StringComparison.OrdinalIgnoreCase); + } + + // Do both have strings? + if (asString != null && that.asString != null) + { + // If we have two random-case strings, then we need to compare case sensitively. + return CompareBaseNamesStringWise(asString, that.asString); + } + + // Fall back to comparing by name. This is the slow path. + return string.Compare(this.Name, that.Name, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Compare two basenames. + /// + /// + /// + /// + private static int CompareBaseNamesStringWise(string asString1, string asString2) + { + // Identical strings just match. + if (asString1 == asString2) + { + return 0; + } + + // Get the lengths of base names to compare. + int baseLenThis = asString1.IndexOf(','); + int baseLenThat = asString2.IndexOf(','); + if (baseLenThis == -1) + { + baseLenThis = asString1.Length; + } + if (baseLenThat == -1) + { + baseLenThat = asString2.Length; + } + +#if NET + ReadOnlySpan nameThis = asString1.AsSpan(0, baseLenThis); + ReadOnlySpan nameThat = asString2.AsSpan(0, baseLenThat); + return nameThis.CompareTo(nameThat, StringComparison.OrdinalIgnoreCase); +#else + // If the lengths are the same then we can compare without copying. + if (baseLenThis == baseLenThat) + { + return string.Compare(asString1, 0, asString2, 0, baseLenThis, StringComparison.OrdinalIgnoreCase); + } + + // Lengths are different, so string copy is required. + string nameThis = asString1.Substring(0, baseLenThis); + string nameThat = asString2.Substring(0, baseLenThat); + return string.Compare(nameThis, nameThat, StringComparison.OrdinalIgnoreCase); +#endif + } + + /// + /// Clone this assemblyNameExtension + /// + internal AssemblyNameExtension Clone() + { + AssemblyNameExtension newExtension = new(); + + if (asAssemblyName != null) + { + newExtension.asAssemblyName = asAssemblyName.CloneIfPossible(); + } + + newExtension.asString = asString; + newExtension.isSimpleName = isSimpleName; + newExtension.hasProcessorArchitectureInFusionName = hasProcessorArchitectureInFusionName; + newExtension.remappedFrom = remappedFrom; + + // We are cloning so we can now party on the object even if the parent was immutable + newExtension.immutable = false; + + return newExtension; + } + + /// + /// Clone the object but mark and mark the cloned object as immutable + /// + /// + internal AssemblyNameExtension CloneImmutable() + { + AssemblyNameExtension clonedExtension = Clone(); + clonedExtension.MarkImmutable(); + return clonedExtension; + } + + /// + /// Is this object immutable + /// + public bool Immutable => immutable; + + /// + /// Mark this object as immutable + /// + internal void MarkImmutable() + { + immutable = true; + } + + /// + /// Compare two assembly names for equality. + /// + /// + /// + internal bool Equals(AssemblyNameExtension that) + { + return EqualsImpl(that, false, false); + } + + /// + /// Interface method for IEquatable<AssemblyNameExtension> + /// + /// + /// + bool IEquatable.Equals(AssemblyNameExtension other) + { + return Equals(other); + } + + /// + /// Compare two assembly names for equality ignoring version. + /// + /// + /// + internal bool EqualsIgnoreVersion(AssemblyNameExtension that) + { + return EqualsImpl(that, true, false); + } + + /// + /// Compare two assembly names and consider the retargetable flag during the comparison + /// + internal bool Equals(AssemblyNameExtension that, bool considerRetargetableFlag) + { + return EqualsImpl(that, false, considerRetargetableFlag); + } + + /// + /// Compare two assembly names for equality. + /// + private bool EqualsImpl(AssemblyNameExtension that, bool ignoreVersion, bool considerRetargetableFlag) + { + // Pointer compare. + if (object.ReferenceEquals(this, that)) + { + return true; + } + + // If that is null then this and that are not equal. Also, this would cause a crash on the next line. + if (that is null) + { + return false; + } + + // Do both have assembly names? + if (asAssemblyName != null && that.asAssemblyName != null) + { + // Pointer compare. + if (object.ReferenceEquals(asAssemblyName, that.asAssemblyName)) + { + return true; + } + } + + // Do both have strings that equal each-other? + if (asString != null && that.asString != null) + { + if (asString == that.asString) + { + return true; + } + + // If they weren't identical then they might still differ only by + // case. So we can't assume that they don't match. So fall through... + } + + // Do the names match? + if (!string.Equals(Name, that.Name, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (!ignoreVersion && (this.Version != that.Version)) + { + return false; + } + + if (!CompareCultures(AssemblyName, that.AssemblyName)) + { + return false; + } + + if (!ComparePublicKeyToken(that)) + { + return false; + } + + if (considerRetargetableFlag && this.Retargetable != that.Retargetable) + { + return false; + } + + return true; + } + + /// + /// Allows the comparison of the culture. + /// + internal static bool CompareCultures(AssemblyName a, AssemblyName b) + { + // Do the Cultures match? + CultureInfo aCulture = a.CultureInfo; + CultureInfo bCulture = b.CultureInfo; + if (aCulture == null) + { + aCulture = CultureInfo.InvariantCulture; + } + if (bCulture == null) + { + bCulture = CultureInfo.InvariantCulture; + } + + return aCulture.LCID == bCulture.LCID; + } + + /// + /// Allows the comparison of just the PublicKeyToken + /// + internal bool ComparePublicKeyToken(AssemblyNameExtension that) + { + // Do the PKTs match? + byte[] aPKT = GetPublicKeyToken(); + byte[] bPKT = that.GetPublicKeyToken(); + return ComparePublicKeyTokens(aPKT, bPKT); + } + + /// + /// Compare two public key tokens. + /// + internal static bool ComparePublicKeyTokens(byte[] aPKT, byte[] bPKT) + { +#if NET + return aPKT.AsSpan().SequenceEqual(bPKT.AsSpan()); +#else + // Some assemblies (real case was interop assembly) may have null PKTs. + aPKT ??= []; + bPKT ??= []; + + if (aPKT.Length != bPKT.Length) + { + return false; + } + + for (int i = 0; i < aPKT.Length; ++i) + { + if (aPKT[i] != bPKT[i]) + { + return false; + } + } + + return true; +#endif + } + + /// + /// Only the unnamed assembly has both null assemblyname and null string. + /// + /// + internal bool IsUnnamedAssembly => asAssemblyName == null && asString == null; + + /// + /// Given a display name, construct an assembly name. + /// + /// The display name. + /// The assembly name. + private static AssemblyName GetAssemblyNameFromDisplayName(string displayName) + { + AssemblyName assemblyName = new AssemblyName(displayName); + return assemblyName; + } + + /// + /// Return a string that has AssemblyName special characters escaped. + /// Those characters are Equals(=), Comma(,), Quote("), Apostrophe('), Backslash(\). + /// + /// + /// WARNING! This method is not meant as a general purpose escaping method for assembly names. + /// Use only if you really know that this does what you need. + /// + /// + /// + internal static string EscapeDisplayNameCharacters(string displayName) + { + StringBuilder sb = new StringBuilder(displayName); + sb = sb.Replace("\\", "\\\\"); + sb = sb.Replace("=", "\\="); + sb = sb.Replace(",", "\\,"); + sb = sb.Replace("\"", "\\\""); + sb = sb.Replace("'", "\\'"); + return sb.ToString(); + } + + /// + /// Convert to a string for display. + /// + /// + public override string ToString() + { + CreateFullName(); + return asString; + } + + /// + /// Compare the fields of this with that if they are not null. + /// + internal bool PartialNameCompare(AssemblyNameExtension that) + { + return PartialNameCompare(that, PartialComparisonFlags.Default, false /* do not consider retargetable flag*/); + } + + /// + /// Compare the fields of this with that if they are not null. + /// + internal bool PartialNameCompare(AssemblyNameExtension that, bool considerRetargetableFlag) + { + return PartialNameCompare(that, PartialComparisonFlags.Default, considerRetargetableFlag); + } + + /// + /// Do a partial comparison between two assembly name extensions. + /// Compare the fields of A and B on the following conditions: + /// 1) A.Field has a non null value + /// 2) The field has been selected in the comparison flags or the default comparison flags are passed in. + /// + /// If A.Field is null then we will not compare A.Field and B.Field even when the comparison flag is set for that field unless skipNullFields is false. + /// + internal bool PartialNameCompare(AssemblyNameExtension that, PartialComparisonFlags comparisonFlags) + { + return PartialNameCompare(that, comparisonFlags, false /* do not consider retargetable flag*/); + } + + /// + /// Do a partial comparison between two assembly name extensions. + /// Compare the fields of A and B on the following conditions: + /// 1) A.Field has a non null value + /// 2) The field has been selected in the comparison flags or the default comparison flags are passed in. + /// + /// If A.Field is null then we will not compare A.Field and B.Field even when the comparison flag is set for that field unless skipNullFields is false. + /// + internal bool PartialNameCompare(AssemblyNameExtension that, PartialComparisonFlags comparisonFlags, bool considerRetargetableFlag) + { + // Pointer compare. + if (object.ReferenceEquals(this, that)) + { + return true; + } + + // If that is null then this and that are not equal. Also, this would cause a crash on the next line. + if (that is null) + { + return false; + } + + // Do both have assembly names? + if (asAssemblyName != null && that.asAssemblyName != null) + { + // Pointer compare. + if (object.ReferenceEquals(asAssemblyName, that.asAssemblyName)) + { + return true; + } + } + + // Do the names match? + if ((comparisonFlags & PartialComparisonFlags.SimpleName) != 0 && Name != null && !string.Equals(Name, that.Name, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if ((comparisonFlags & PartialComparisonFlags.Version) != 0 && Version != null && this.Version != that.Version) + { + return false; + } + + if ((comparisonFlags & PartialComparisonFlags.Culture) != 0 && CultureInfo != null && (that.CultureInfo == null || !CompareCultures(AssemblyName, that.AssemblyName))) + { + return false; + } + + if ((comparisonFlags & PartialComparisonFlags.PublicKeyToken) != 0 && GetPublicKeyToken() != null && !ComparePublicKeyToken(that)) + { + return false; + } + + if (considerRetargetableFlag && (Retargetable != that.Retargetable)) + { + return false; + } + return true; + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("hasAN", asAssemblyName != null); + if (asAssemblyName != null) + { + info.AddValue("name", asAssemblyName.Name); + info.AddValue("pk", asAssemblyName.GetPublicKey()); + info.AddValue("pkt", asAssemblyName.GetPublicKeyToken()); + info.AddValue("ver", asAssemblyName.Version); + info.AddValue("flags", (int)asAssemblyName.Flags); + info.AddValue("cpuarch", (int)asAssemblyName.ProcessorArchitecture); + + info.AddValue("hasCI", asAssemblyName.CultureInfo != null); + if (asAssemblyName.CultureInfo != null) + { + info.AddValue("ci", asAssemblyName.CultureInfo.LCID); + } + + info.AddValue("hashAlg", asAssemblyName.HashAlgorithm); + info.AddValue("verCompat", asAssemblyName.VersionCompatibility); + info.AddValue("codebase", asAssemblyName.CodeBase); + } + + info.AddValue("asStr", asString); + info.AddValue("isSName", isSimpleName); + info.AddValue("hasCpuArch", hasProcessorArchitectureInFusionName); + info.AddValue("immutable", immutable); + info.AddValue("remapped", remappedFrom); + } + + /// + /// Reads/writes this class + /// + /// + public void Translate(ITranslator translator) + { + translator.Translate(ref asAssemblyName); + translator.Translate(ref asString); + translator.Translate(ref isSimpleName); + translator.Translate(ref hasProcessorArchitectureInFusionName); + translator.Translate(ref immutable); + + // TODO: consider some kind of protection against infinite loop during serialization, hint: pre serialize check for cycle in graph + translator.TranslateHashSet(ref remappedFrom, + (t) => new AssemblyNameExtension(t), + (capacity) => CreateRemappedFrom()); + } + } +} diff --git a/src/MSBuildTaskHost/BinaryReaderExtensions.cs b/src/MSBuildTaskHost/BinaryReaderExtensions.cs new file mode 100644 index 00000000000..9078401ba2f --- /dev/null +++ b/src/MSBuildTaskHost/BinaryReaderExtensions.cs @@ -0,0 +1,144 @@ +// 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; +using System.IO; +using System.Runtime.CompilerServices; +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Shared +{ + internal static class BinaryReaderExtensions + { +#if !TASKHOST + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static string? ReadOptionalString(this BinaryReader reader) + { + return reader.ReadByte() == 0 ? null : reader.ReadString(); + } + +#if !TASKHOST + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static int? ReadOptionalInt32(this BinaryReader reader) + { + return reader.ReadByte() == 0 ? null : reader.ReadInt32(); + } + +#if !TASKHOST + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static int Read7BitEncodedInt(this BinaryReader reader) + { + // Read out an Int32 7 bits at a time. The high bit + // of the byte when on means to continue reading more bytes. + int count = 0; + int shift = 0; + byte b; + do + { + // Check for a corrupted stream. Read a max of 5 bytes. + // In a future version, add a DataFormatException. + if (shift == 5 * 7) // 5 bytes max per Int32, shift += 7 + { + throw new FormatException(); + } + + // ReadByte handles end of stream cases for us. + b = reader.ReadByte(); + count |= (b & 0x7F) << shift; + shift += 7; + } while ((b & 0x80) != 0); + return count; + } + +#if !TASKHOST + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static DateTime ReadTimestamp(this BinaryReader reader) + { + long timestampTicks = reader.ReadInt64(); + DateTimeKind kind = (DateTimeKind)reader.ReadInt32(); + var timestamp = new DateTime(timestampTicks, kind); + return timestamp; + } + +#if !TASKHOST + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BuildEventContext? ReadOptionalBuildEventContext(this BinaryReader reader) + { + if (reader.ReadByte() == 0) + { + return null; + } + + return reader.ReadBuildEventContext(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BuildEventContext ReadBuildEventContext(this BinaryReader reader) + { + int nodeId = reader.ReadInt32(); + int projectContextId = reader.ReadInt32(); + int targetId = reader.ReadInt32(); + int taskId = reader.ReadInt32(); + int submissionId = reader.ReadInt32(); + int projectInstanceId = reader.ReadInt32(); + int evaluationId = reader.ReadInt32(); + + var buildEventContext = new BuildEventContext(submissionId, nodeId, evaluationId, projectInstanceId, projectContextId, targetId, taskId); + return buildEventContext; + } +#endif + +#if !TASKHOST + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static unsafe Guid ReadGuid(this BinaryReader reader) + { + return new Guid(reader.ReadBytes(sizeof(Guid))); + } + + public static void ReadExtendedBuildEventData(this BinaryReader reader, IExtendedBuildEventArgs data) + { + data.ExtendedType = reader.ReadString(); + data.ExtendedData = reader.ReadOptionalString(); + + bool haveMetadata = reader.ReadBoolean(); + if (haveMetadata) + { + data.ExtendedMetadata = new Dictionary(); + + int count = reader.Read7BitEncodedInt(); + for (int i = 0; i < count; i++) + { + string key = reader.ReadString(); + string? value = reader.ReadOptionalString(); + + data.ExtendedMetadata.Add(key, value); + } + } + else + { + data.ExtendedMetadata = null; + } + } + + public static Dictionary ReadDurationDictionary(this BinaryReader reader) + { + int count = reader.Read7BitEncodedInt(); + var durations = new Dictionary(count); + for (int i = 0; i < count; i++) + { + string key = reader.ReadString(); + TimeSpan value = TimeSpan.FromTicks(reader.ReadInt64()); + + durations.Add(key, value); + } + + return durations; + } + } +} diff --git a/src/MSBuildTaskHost/BinaryWriterExtensions.cs b/src/MSBuildTaskHost/BinaryWriterExtensions.cs new file mode 100644 index 00000000000..9cb458f4ec7 --- /dev/null +++ b/src/MSBuildTaskHost/BinaryWriterExtensions.cs @@ -0,0 +1,143 @@ +// 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; +using System.IO; +using System.Runtime.CompilerServices; +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Shared +{ + internal static class BinaryWriterExtensions + { +#if !TASKHOST + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static void WriteOptionalString(this BinaryWriter writer, string? value) + { + if (value == null) + { + writer.Write((byte)0); + } + else + { + writer.Write((byte)1); + writer.Write(value); + } + } + +#if !TASKHOST + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static void WriteOptionalInt32(this BinaryWriter writer, int? value) + { + if (value == null) + { + writer.Write((byte)0); + } + else + { + writer.Write((byte)1); + writer.Write(value.Value); + } + } + +#if !TASKHOST + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static void WriteTimestamp(this BinaryWriter writer, DateTime timestamp) + { + writer.Write(timestamp.Ticks); + writer.Write((Int32)timestamp.Kind); + } + +#if !TASKHOST + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static void Write7BitEncodedInt(this BinaryWriter writer, int value) + { + // Write out an int 7 bits at a time. The high bit of the byte, + // when on, tells reader to continue reading more bytes. + uint v = (uint)value; // support negative numbers + while (v >= 0x80) + { + writer.Write((byte)(v | 0x80)); + v >>= 7; + } + + writer.Write((byte)v); + } + +#if !TASKHOST + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteOptionalBuildEventContext(this BinaryWriter writer, BuildEventContext? context) + { + if (context == null) + { + writer.Write((byte)0); + } + else + { + writer.Write((byte)1); + writer.WriteBuildEventContext(context); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteBuildEventContext(this BinaryWriter writer, BuildEventContext context) + { + writer.Write(context.NodeId); + writer.Write(context.ProjectContextId); + writer.Write(context.TargetId); + writer.Write(context.TaskId); + writer.Write(context.SubmissionId); + writer.Write(context.ProjectInstanceId); + writer.Write(context.EvaluationId); + } +#endif + +#if !TASKHOST + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static void WriteGuid(this BinaryWriter writer, Guid value) + { + Guid val = value; + unsafe + { + byte* ptr = (byte*)&val; + for (int i = 0; i < sizeof(Guid); i++, ptr++) + { + writer.Write(*ptr); + } + } + } + + public static void WriteExtendedBuildEventData(this BinaryWriter writer, IExtendedBuildEventArgs data) + { + writer.Write(data.ExtendedType); + writer.WriteOptionalString(data.ExtendedData); + + writer.Write(data.ExtendedMetadata != null); + if (data.ExtendedMetadata != null) + { + writer.Write7BitEncodedInt(data.ExtendedMetadata.Count); + foreach (KeyValuePair kvp in data.ExtendedMetadata) + { + writer.Write(kvp.Key); + writer.WriteOptionalString(kvp.Value); + } + } + } + + public static void WriteDurationsDictionary(this BinaryWriter writer, Dictionary durations) + { + writer.Write7BitEncodedInt(durations.Count); + foreach (KeyValuePair kvp in durations) + { + writer.Write(kvp.Key); + writer.Write(kvp.Value.Ticks); + } + } + } +} diff --git a/src/MSBuildTaskHost/BufferedReadStream.cs b/src/MSBuildTaskHost/BufferedReadStream.cs new file mode 100644 index 00000000000..55bba5986f8 --- /dev/null +++ b/src/MSBuildTaskHost/BufferedReadStream.cs @@ -0,0 +1,210 @@ +// 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.IO; +using System.IO.Pipes; +using System.Threading; + +#if NET451_OR_GREATER || NETCOREAPP +using System.Threading.Tasks; +#endif + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + internal class BufferedReadStream : Stream + { + private const int BUFFER_SIZE = 1024; + private NamedPipeServerStream _innerStream; + private byte[] _buffer; + + // The number of bytes in the buffer that have been read from the underlying stream but not read by consumers of this stream + private int _currentlyBufferedByteCount; + private int _currentIndexInBuffer; + + public BufferedReadStream(NamedPipeServerStream innerStream) + { + _innerStream = innerStream; + _buffer = new byte[BUFFER_SIZE]; + + _currentlyBufferedByteCount = 0; + } + + public override bool CanRead { get { return _innerStream.CanRead; } } + + public override bool CanSeek { get { return false; } } + + public override bool CanWrite { get { return _innerStream.CanWrite; } } + + public override long Length { get { return _innerStream.Length; } } + + public override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + public override void Flush() + { + _innerStream.Flush(); + } + + public override int ReadByte() + { + if (_currentlyBufferedByteCount > 0) + { + int ret = _buffer[_currentIndexInBuffer]; + _currentIndexInBuffer++; + _currentlyBufferedByteCount--; + return ret; + } + else + { + // Let the base class handle it, which will end up calling the Read() method + return base.ReadByte(); + } + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (count > BUFFER_SIZE) + { + // Trying to read more data than the buffer can hold + int alreadyCopied = 0; + if (_currentlyBufferedByteCount > 0) + { + Array.Copy(_buffer, _currentIndexInBuffer, buffer, offset, _currentlyBufferedByteCount); + alreadyCopied = _currentlyBufferedByteCount; + _currentIndexInBuffer = 0; + _currentlyBufferedByteCount = 0; + } + int innerReadCount = _innerStream.Read(buffer, offset + alreadyCopied, count - alreadyCopied); + return innerReadCount + alreadyCopied; + } + else if (count <= _currentlyBufferedByteCount) + { + // Enough data buffered to satisfy read request + Array.Copy(_buffer, _currentIndexInBuffer, buffer, offset, count); + _currentIndexInBuffer += count; + _currentlyBufferedByteCount -= count; + return count; + } + else + { + // Need to read more data + int alreadyCopied = 0; + if (_currentlyBufferedByteCount > 0) + { + Array.Copy(_buffer, _currentIndexInBuffer, buffer, offset, _currentlyBufferedByteCount); + alreadyCopied = _currentlyBufferedByteCount; + _currentIndexInBuffer = 0; + _currentlyBufferedByteCount = 0; + } + + int innerReadCount = _innerStream.Read(_buffer, 0, BUFFER_SIZE); + _currentIndexInBuffer = 0; + _currentlyBufferedByteCount = innerReadCount; + + int remainingCopyCount; + + if (alreadyCopied + innerReadCount >= count) + { + remainingCopyCount = count - alreadyCopied; + } + else + { + remainingCopyCount = innerReadCount; + } + + Array.Copy(_buffer, 0, buffer, offset + alreadyCopied, remainingCopyCount); + _currentIndexInBuffer += remainingCopyCount; + _currentlyBufferedByteCount -= remainingCopyCount; + + return alreadyCopied + remainingCopyCount; + } + } + +#if NET451_OR_GREATER || NETCOREAPP + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (count > BUFFER_SIZE) + { + // Trying to read more data than the buffer can hold + int alreadyCopied = CopyToBuffer(buffer, offset); + +#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + int innerReadCount = await _innerStream.ReadAsync(buffer, offset + alreadyCopied, count - alreadyCopied, cancellationToken); +#pragma warning restore CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + return innerReadCount + alreadyCopied; + } + else if (count <= _currentlyBufferedByteCount) + { + // Enough data buffered to satisfy read request + Array.Copy(_buffer, _currentIndexInBuffer, buffer, offset, count); + _currentIndexInBuffer += count; + _currentlyBufferedByteCount -= count; + return count; + } + else + { + // Need to read more data + int alreadyCopied = CopyToBuffer(buffer, offset); + +#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + int innerReadCount = await _innerStream.ReadAsync(_buffer, 0, BUFFER_SIZE, cancellationToken); +#pragma warning restore CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + _currentIndexInBuffer = 0; + _currentlyBufferedByteCount = innerReadCount; + + int remainingCopyCount = alreadyCopied + innerReadCount >= count ? count - alreadyCopied : innerReadCount; + Array.Copy(_buffer, 0, buffer, offset + alreadyCopied, remainingCopyCount); + _currentIndexInBuffer += remainingCopyCount; + _currentlyBufferedByteCount -= remainingCopyCount; + + return alreadyCopied + remainingCopyCount; + } + + int CopyToBuffer(byte[] buffer, int offset) + { + int alreadyCopied = 0; + if (_currentlyBufferedByteCount > 0) + { + Array.Copy(_buffer, _currentIndexInBuffer, buffer, offset, _currentlyBufferedByteCount); + alreadyCopied = _currentlyBufferedByteCount; + _currentIndexInBuffer = 0; + _currentlyBufferedByteCount = 0; + } + + return alreadyCopied; + } + } +#endif + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + _innerStream.Write(buffer, offset, count); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _innerStream.Dispose(); + } + + base.Dispose(disposing); + } + } +} diff --git a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs new file mode 100644 index 00000000000..3a0c945eb7d --- /dev/null +++ b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs @@ -0,0 +1,724 @@ +// 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; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using Microsoft.Build.Framework; +using Microsoft.Build.Shared.FileSystem; + +#nullable disable + +namespace Microsoft.Build.Shared +{ + internal sealed class BuildEnvironmentHelper + { + // Since this class is added as 'link' to shared source in multiple projects, + // MSBuildConstants.CurrentVisualStudioVersion is not available in all of them. + private const string CurrentVisualStudioVersion = "18.0"; + + // MSBuildConstants.CurrentToolsVersion + private const string CurrentToolsVersion = "Current"; + + /// + /// Name of the Visual Studio (and Blend) process. + /// VS ASP intellisense server fails without Microsoft.VisualStudio.Web.Host. Remove when issue fixed: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/574986 + /// + private static readonly string[] s_visualStudioProcess = { "DEVENV", "BLEND", "Microsoft.VisualStudio.Web.Host" }; + + /// + /// Name of the MSBuild process(es) + /// + private static readonly string[] s_msBuildProcess = { "MSBUILD", "MSBUILDTASKHOST" }; + + /// + /// Name of MSBuild executable files. + /// + private static readonly string[] s_msBuildExeNames = { "MSBuild.exe", "MSBuild.dll" }; + + /// + /// Gets the cached Build Environment instance. + /// + public static BuildEnvironment Instance + { + get + { + try + { + return BuildEnvironmentHelperSingleton.s_instance; + } + catch (TypeInitializationException e) + { + if (e.InnerException != null) + { + // Throw the error that caused the TypeInitializationException. + // (likely InvalidOperationException) + throw e.InnerException; + } + + throw; + } + } + } + + /// + /// Find the location of MSBuild.exe based on the current environment. + /// + /// + /// This defines the order and precedence for various methods of discovering MSBuild and associated toolsets. + /// At a high level, an install under Visual Studio is preferred as the user may have SDKs installed to a + /// specific instance of Visual Studio and build will only succeed if we can discover those. See + /// https://github.com/dotnet/msbuild/issues/1461 for details. + /// + /// Build environment. + private static BuildEnvironment Initialize() + { + // See https://github.com/dotnet/msbuild/issues/1461 for specification of ordering and details. + var possibleLocations = new Func[] + { + TryFromEnvironmentVariable, + TryFromVisualStudioProcess, + TryFromMSBuildProcess, + TryFromMSBuildAssembly, + TryFromDevConsole, + TryFromSetupApi, + TryFromAppContextBaseDirectory + }; + + foreach (var location in possibleLocations) + { + var env = location(); + if (env != null) + { + return env; + } + } + + // If we can't find a suitable environment, continue in the 'None' mode. If not running tests, + // we will use the current running process for the CurrentMSBuildExePath value. This is likely + // wrong, but many things use the CurrentMSBuildToolsDirectory value which must be set for basic + // functionality to work. + // + // If we are running tests, then the current running process may be a test runner located in the + // NuGet packages folder. So in that case, we use the location of the current assembly, which + // will be in the output path of the test project, which is what we want. + + string msbuildExePath; + if (s_runningTests()) + { + msbuildExePath = typeof(BuildEnvironmentHelper).Assembly.Location; + } + else + { + msbuildExePath = s_getProcessFromRunningProcess(); + } + + return new BuildEnvironment( + BuildEnvironmentMode.None, + msbuildExePath, + runningTests: s_runningTests(), + runningInMSBuildExe: false, + runningInVisualStudio: false, + visualStudioPath: null); + } + + private static BuildEnvironment TryFromEnvironmentVariable() + { + var msBuildExePath = s_getEnvironmentVariable("MSBUILD_EXE_PATH"); + + return msBuildExePath == null + ? null + : TryFromMSBuildExeUnderVisualStudio(msBuildExePath, allowLegacyToolsVersion: true) ?? TryFromStandaloneMSBuildExe(msBuildExePath); + } + + private static BuildEnvironment TryFromVisualStudioProcess() + { + if (!NativeMethodsShared.IsWindows) + { + return null; + } + + var vsProcess = s_getProcessFromRunningProcess(); + if (!IsProcessInList(vsProcess, s_visualStudioProcess)) + { + return null; + } + + var vsRoot = FileUtilities.GetFolderAbove(vsProcess, 3); + string msBuildExe = GetMSBuildExeFromVsRoot(vsRoot); + + return new BuildEnvironment( + BuildEnvironmentMode.VisualStudio, + msBuildExe, + runningTests: false, + runningInMSBuildExe: false, + runningInVisualStudio: true, + visualStudioPath: vsRoot); + } + + private static BuildEnvironment TryFromMSBuildProcess() + { + var msBuildExe = s_getProcessFromRunningProcess(); + if (!IsProcessInList(msBuildExe, s_msBuildProcess)) + { + return null; + } + + // First check if we're in a VS installation + if (NativeMethodsShared.IsWindows && + Regex.IsMatch(msBuildExe, $@".*\\MSBuild\\{CurrentToolsVersion}\\Bin\\.*MSBuild(?:TaskHost)?\.exe", RegexOptions.IgnoreCase)) + { + return new BuildEnvironment( + BuildEnvironmentMode.VisualStudio, + msBuildExe, + runningTests: false, + runningInMSBuildExe: true, + runningInVisualStudio: false, + visualStudioPath: GetVsRootFromMSBuildAssembly(msBuildExe)); + } + + // Standalone mode running in MSBuild.exe + return new BuildEnvironment( + BuildEnvironmentMode.Standalone, + msBuildExe, + runningTests: false, + runningInMSBuildExe: true, + runningInVisualStudio: false, + visualStudioPath: null); + } + + private static BuildEnvironment TryFromMSBuildAssembly() + { + var buildAssembly = s_getExecutingAssemblyPath(); + if (buildAssembly == null) + { + return null; + } + + // Check for MSBuild.[exe|dll] next to the current assembly + var msBuildExe = Path.Combine(FileUtilities.GetFolderAbove(buildAssembly), "MSBuild.exe"); + var msBuildDll = Path.Combine(FileUtilities.GetFolderAbove(buildAssembly), "MSBuild.dll"); + + // First check if we're in a VS installation + var environment = TryFromMSBuildExeUnderVisualStudio(msBuildExe); + if (environment != null) + { + return environment; + } + + // We're not in VS, check for MSBuild.exe / dll to consider this a standalone environment. + string msBuildPath = null; + if (FileSystems.Default.FileExists(msBuildExe)) + { + msBuildPath = msBuildExe; + } + else if (FileSystems.Default.FileExists(msBuildDll)) + { + msBuildPath = msBuildDll; + } + + if (!string.IsNullOrEmpty(msBuildPath)) + { + // Standalone mode with toolset + return new BuildEnvironment( + BuildEnvironmentMode.Standalone, + msBuildPath, + runningTests: s_runningTests(), + runningInMSBuildExe: false, + runningInVisualStudio: false, + visualStudioPath: null); + } + + return null; + } + + private static BuildEnvironment TryFromMSBuildExeUnderVisualStudio(string msbuildExe, bool allowLegacyToolsVersion = false) + { + string msBuildPathPattern = allowLegacyToolsVersion + ? $@".*\\MSBuild\\({CurrentToolsVersion}|\d+\.0)\\Bin\\.*" + : $@".*\\MSBuild\\{CurrentToolsVersion}\\Bin\\.*"; + + if (NativeMethodsShared.IsWindows && + Regex.IsMatch(msbuildExe, msBuildPathPattern, RegexOptions.IgnoreCase)) + { + string visualStudioRoot = GetVsRootFromMSBuildAssembly(msbuildExe); + return new BuildEnvironment( + BuildEnvironmentMode.VisualStudio, + GetMSBuildExeFromVsRoot(visualStudioRoot), + runningTests: s_runningTests(), + runningInMSBuildExe: false, + runningInVisualStudio: false, + visualStudioPath: visualStudioRoot); + } + + return null; + } + + private static BuildEnvironment TryFromDevConsole() + { + if (s_runningTests()) + { + // If running unit tests, then don't try to get the build environment from MSBuild installed on the machine + // (we should be using the locally built MSBuild instead) + return null; + } + + // VSINSTALLDIR and VisualStudioVersion are set from the Developer Command Prompt. + var vsInstallDir = s_getEnvironmentVariable("VSINSTALLDIR"); + var vsVersion = s_getEnvironmentVariable("VisualStudioVersion"); + + if (string.IsNullOrEmpty(vsInstallDir) || string.IsNullOrEmpty(vsVersion) || + vsVersion != CurrentVisualStudioVersion || !FileSystems.Default.DirectoryExists(vsInstallDir)) + { + return null; + } + + return new BuildEnvironment( + BuildEnvironmentMode.VisualStudio, + GetMSBuildExeFromVsRoot(vsInstallDir), + runningTests: false, + runningInMSBuildExe: false, + runningInVisualStudio: false, + visualStudioPath: vsInstallDir); + } + + private static BuildEnvironment TryFromSetupApi() + { + if (s_runningTests()) + { + // If running unit tests, then don't try to get the build environment from MSBuild installed on the machine + // (we should be using the locally built MSBuild instead) + return null; + } + + Version v = new Version(CurrentVisualStudioVersion); + var instances = s_getVisualStudioInstances() + .Where(i => i.Version.Major == v.Major && FileSystems.Default.DirectoryExists(i.Path)) + .ToList(); + + if (instances.Count == 0) + { + return null; + } + + if (instances.Count > 1) + { + // TODO: Warn user somehow. We may have picked the wrong one. + } + + return new BuildEnvironment( + BuildEnvironmentMode.VisualStudio, + GetMSBuildExeFromVsRoot(instances[0].Path), + runningTests: false, + runningInMSBuildExe: false, + runningInVisualStudio: false, + visualStudioPath: instances[0].Path); + } + + private static BuildEnvironment TryFromAppContextBaseDirectory() + { + // Assemblies compiled against anything older than .NET 4.0 won't have a System.AppContext + // Try the base directory that the assembly resolver uses to probe for assemblies. + // Under certain scenarios the assemblies are loaded from spurious locations like the NuGet package cache + // but the toolset files are copied to the app's directory via "contentFiles". + + var appContextBaseDirectory = s_getAppContextBaseDirectory(); + if (string.IsNullOrEmpty(appContextBaseDirectory)) + { + return null; + } + + // Look for possible MSBuild exe names in the AppContextBaseDirectory + return s_msBuildExeNames + .Select((name) => TryFromStandaloneMSBuildExe(Path.Combine(appContextBaseDirectory, name))) + .FirstOrDefault(env => env != null); + } + + private static BuildEnvironment TryFromStandaloneMSBuildExe(string msBuildExePath) + { + if (!string.IsNullOrEmpty(msBuildExePath) && FileSystems.Default.FileExists(msBuildExePath)) + { + // MSBuild.exe was found outside of Visual Studio. Assume Standalone mode. + return new BuildEnvironment( + BuildEnvironmentMode.Standalone, + msBuildExePath, + runningTests: s_runningTests(), + runningInMSBuildExe: false, + runningInVisualStudio: false, + visualStudioPath: null); + } + + return null; + } + + private static string GetVsRootFromMSBuildAssembly(string msBuildAssembly) + { + string directory = Path.GetDirectoryName(msBuildAssembly); + return FileUtilities.GetFolderAbove(msBuildAssembly, + directory.EndsWith(@"\amd64", StringComparison.OrdinalIgnoreCase) || + directory.EndsWith(@"\arm64", StringComparison.OrdinalIgnoreCase) + ? 5 + : 4); + } + + private static string GetMSBuildExeFromVsRoot(string visualStudioRoot) + { + return FileUtilities.CombinePaths( + visualStudioRoot, + "MSBuild", + CurrentToolsVersion, + "Bin", + NativeMethodsShared.ProcessorArchitecture == Framework.NativeMethods.ProcessorArchitectures.X64 ? "amd64" : + NativeMethodsShared.ProcessorArchitecture == Framework.NativeMethods.ProcessorArchitectures.ARM64 ? "arm64" : string.Empty, + "MSBuild.exe"); + } + + private static bool? _runningTests; + private static readonly LockType _runningTestsLock = new LockType(); + + private static bool CheckIfRunningTests() + { + if (_runningTests != null) + { + return _runningTests.Value; + } + + lock (_runningTestsLock) + { + if (_runningTests != null) + { + return _runningTests.Value; + } + + // Check if running tests via the TestInfo class in Microsoft.Build.Framework. + // See the comments on the TestInfo class for an explanation of why it works this way. + var frameworkAssembly = typeof(Framework.ITask).Assembly; + var testInfoType = frameworkAssembly.GetType("Microsoft.Build.Framework.TestInfo"); + var runningTestsField = testInfoType.GetField("s_runningTests", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static); + + _runningTests = (bool)runningTestsField.GetValue(null); + + return _runningTests.Value; + } + } + + /// + /// Returns true if processName appears in the processList + /// + /// Name of the process + /// List of processes to check + /// + private static bool IsProcessInList(string processName, string[] processList) + { + var processFileName = Path.GetFileNameWithoutExtension(processName); + + if (string.IsNullOrEmpty(processFileName)) + { + return false; + } + + return processList.Any(s => + processFileName.Equals(s, StringComparison.OrdinalIgnoreCase)); + } + + private static string GetProcessFromRunningProcess() + { +#if RUNTIME_TYPE_NETCORE + // The EntryAssembly property can return null when a managed assembly has been loaded from + // an unmanaged application (for example, using custom CLR hosting). + if (AssemblyUtilities.EntryAssembly == null) + { + return EnvironmentUtilities.ProcessPath; + } + + return AssemblyUtilities.GetAssemblyLocation(AssemblyUtilities.EntryAssembly); +#else + + return EnvironmentUtilities.ProcessPath; +#endif + } + + private static string GetExecutingAssemblyPath() + { + return FileUtilities.ExecutingAssemblyPath; + } + + private static string GetAppContextBaseDirectory() + { +#if !CLR2COMPATIBILITY // Assemblies compiled against anything older than .NET 4.0 won't have a System.AppContext + return AppContext.BaseDirectory; +#else + return null; +#endif + } + + private static string GetEnvironmentVariable(string variable) + { + return Environment.GetEnvironmentVariable(variable); + } + + /// + /// Resets the current singleton instance (for testing). + /// + internal static void ResetInstance_ForUnitTestsOnly(Func getProcessFromRunningProcess = null, + Func getExecutingAssemblyPath = null, Func getAppContextBaseDirectory = null, + Func> getVisualStudioInstances = null, + Func getEnvironmentVariable = null, + Func runningTests = null) + { + s_getProcessFromRunningProcess = getProcessFromRunningProcess ?? GetProcessFromRunningProcess; + s_getExecutingAssemblyPath = getExecutingAssemblyPath ?? GetExecutingAssemblyPath; + s_getAppContextBaseDirectory = getAppContextBaseDirectory ?? GetAppContextBaseDirectory; + s_getVisualStudioInstances = getVisualStudioInstances ?? VisualStudioLocationHelper.GetInstances; + s_getEnvironmentVariable = getEnvironmentVariable ?? GetEnvironmentVariable; + + // Tests which specifically test the BuildEnvironmentHelper need it to be able to act as if it is not running tests + s_runningTests = runningTests ?? CheckIfRunningTests; + + _runningTests = null; + BuildEnvironmentHelperSingleton.s_instance = Initialize(); + } + + /// + /// Resets the current singleton instance (for testing). + /// + internal static void ResetInstance_ForUnitTestsOnly(BuildEnvironment buildEnvironment) + { + BuildEnvironmentHelperSingleton.s_instance = buildEnvironment; + _runningTests = buildEnvironment.RunningTests; + } + + private static Func s_getProcessFromRunningProcess = GetProcessFromRunningProcess; + private static Func s_getExecutingAssemblyPath = GetExecutingAssemblyPath; + private static Func s_getAppContextBaseDirectory = GetAppContextBaseDirectory; + private static Func> s_getVisualStudioInstances = VisualStudioLocationHelper.GetInstances; + private static Func s_getEnvironmentVariable = GetEnvironmentVariable; + private static Func s_runningTests = CheckIfRunningTests; + + private static class BuildEnvironmentHelperSingleton + { + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static BuildEnvironmentHelperSingleton() + { } + + public static BuildEnvironment s_instance = Initialize(); + } + } + + /// + /// Enum which defines which environment / mode MSBuild is currently running. + /// + internal enum BuildEnvironmentMode + { + /// + /// Running from Visual Studio directly or from MSBuild installed under an instance of Visual Studio. + /// Toolsets and extensions will be loaded from the Visual Studio instance. + /// + VisualStudio, + + /// + /// Running in a standalone toolset mode. All toolsets and extensions paths are relative to the app + /// running and not dependent on Visual Studio. (e.g. dotnet CLI, open source clone of our repo) + /// + Standalone, + + /// + /// Running without any defined toolsets. Most functionality limited. Likely will not be able to + /// build or evaluate a project. (e.g. reference to Microsoft.*.dll without a toolset definition + /// or Visual Studio instance installed). + /// + None + } + + /// + /// Defines the current environment for build tools. + /// + internal sealed class BuildEnvironment + { + public BuildEnvironment(BuildEnvironmentMode mode, string currentMSBuildExePath, bool runningTests, bool runningInMSBuildExe, bool runningInVisualStudio, + string visualStudioPath) + { + FileInfo currentMSBuildExeFile = null; + DirectoryInfo currentToolsDirectory = null; + + Mode = mode; + RunningTests = runningTests; + RunningInMSBuildExe = runningInMSBuildExe; + RunningInVisualStudio = runningInVisualStudio; + CurrentMSBuildExePath = currentMSBuildExePath; + VisualStudioInstallRootDirectory = visualStudioPath; + +#if !NO_FRAMEWORK_IVT + Framework.BuildEnvironmentState.s_runningTests = runningTests; + Framework.BuildEnvironmentState.s_runningInVisualStudio = runningInVisualStudio; +#endif + + if (!string.IsNullOrEmpty(currentMSBuildExePath)) + { + currentMSBuildExeFile = new FileInfo(currentMSBuildExePath); + currentToolsDirectory = currentMSBuildExeFile.Directory; + + CurrentMSBuildToolsDirectory = currentMSBuildExeFile.DirectoryName; + CurrentMSBuildConfigurationFile = string.Concat(currentMSBuildExePath, ".config"); + MSBuildToolsDirectory32 = CurrentMSBuildToolsDirectory; + MSBuildToolsDirectory64 = CurrentMSBuildToolsDirectory; + MSBuildToolsDirectoryRoot = CurrentMSBuildToolsDirectory; + } + + // We can't detect an environment, don't try to set other paths. + if (mode == BuildEnvironmentMode.None || currentMSBuildExeFile == null || currentToolsDirectory == null) + { + return; + } + + var msBuildExeName = currentMSBuildExeFile.Name; + + if (mode == BuildEnvironmentMode.VisualStudio) + { + // In Visual Studio, the entry-point MSBuild.exe is often from an arch-specific subfolder + MSBuildToolsDirectoryRoot = NativeMethodsShared.ProcessorArchitecture switch + { + NativeMethodsShared.ProcessorArchitectures.X86 => CurrentMSBuildToolsDirectory, + NativeMethodsShared.ProcessorArchitectures.X64 or NativeMethodsShared.ProcessorArchitectures.ARM64 + => currentToolsDirectory.Parent?.FullName, + _ => throw new InternalErrorException("Unknown processor architecture " + NativeMethodsShared.ProcessorArchitecture), + }; + } + else + { + // In the .NET SDK, there's one copy of MSBuild.dll and it's in the root folder. + MSBuildToolsDirectoryRoot = CurrentMSBuildToolsDirectory; + + // If we're standalone, we might not be in the SDK. Rely on folder paths at this point. + if (string.Equals(currentToolsDirectory.Name, "amd64", StringComparison.OrdinalIgnoreCase) || + string.Equals(currentToolsDirectory.Name, "arm64", StringComparison.OrdinalIgnoreCase)) + { + MSBuildToolsDirectoryRoot = currentToolsDirectory.Parent?.FullName; + } + } + + if (MSBuildToolsDirectoryRoot != null) + { + // Calculate potential paths to other architecture MSBuild.exe + var potentialAmd64FromX86 = FileUtilities.CombinePaths(MSBuildToolsDirectoryRoot, "amd64", msBuildExeName); + var potentialARM64FromX86 = FileUtilities.CombinePaths(MSBuildToolsDirectoryRoot, "arm64", msBuildExeName); + + // Check for existence of an MSBuild file. Note this is not necessary in a VS installation where we always want to + // assume the correct layout. + var existsCheck = mode == BuildEnvironmentMode.VisualStudio ? new Func(_ => true) : FileSystems.Default.FileExists; + + MSBuildToolsDirectory32 = MSBuildToolsDirectoryRoot; + MSBuildToolsDirectory64 = existsCheck(potentialAmd64FromX86) ? Path.Combine(MSBuildToolsDirectoryRoot, "amd64") : CurrentMSBuildToolsDirectory; +#if RUNTIME_TYPE_NETCORE + // Fall back to "current" for any architecture since .NET SDK doesn't + // support cross-arch task invocations. + MSBuildToolsDirectoryArm64 = existsCheck(potentialARM64FromX86) ? Path.Combine(MSBuildToolsDirectoryRoot, "arm64") : CurrentMSBuildToolsDirectory; +#else + MSBuildToolsDirectoryArm64 = existsCheck(potentialARM64FromX86) ? Path.Combine(MSBuildToolsDirectoryRoot, "arm64") : null; +#endif + } + + MSBuildExtensionsPath = mode == BuildEnvironmentMode.VisualStudio + ? Path.Combine(VisualStudioInstallRootDirectory, "MSBuild") + : MSBuildToolsDirectory32; + } + + internal BuildEnvironmentMode Mode { get; } + + /// + /// Gets the flag that indicates if we are running in a test harness. + /// + internal bool RunningTests { get; } + + /// + /// Returns true when the entry point application is MSBuild.exe. + /// + internal bool RunningInMSBuildExe { get; } + + /// + /// Returns true when the entry point application is Visual Studio. + /// + internal bool RunningInVisualStudio { get; } + + /// + /// Path to the root of the MSBuild folder (in VS scenarios, MSBuild\Current\bin). + /// + internal string MSBuildToolsDirectoryRoot { get; } + + /// + /// Path to the MSBuild 32-bit tools directory. + /// + internal string MSBuildToolsDirectory32 { get; } + + /// + /// Path to the MSBuild 64-bit (AMD64) tools directory. + /// + internal string MSBuildToolsDirectory64 { get; } + + /// + /// Path to the ARM64 tools directory. + /// if ARM64 tools are not installed. + /// + internal string MSBuildToolsDirectoryArm64 { get; } + + /// + /// Path to the Sdks folder for this MSBuild instance. + /// + internal string MSBuildSDKsPath + { + get + { + string defaultSdkPath; + + if (VisualStudioInstallRootDirectory != null) + { + // Can't use the N-argument form of Combine because it doesn't exist on .NET 3.5 + defaultSdkPath = FileUtilities.CombinePaths(VisualStudioInstallRootDirectory, "MSBuild", "Sdks"); + } + else + { + defaultSdkPath = Path.Combine(CurrentMSBuildToolsDirectory, "Sdks"); + } + + // Allow an environment-variable override of the default SDK location + return Environment.GetEnvironmentVariable("MSBuildSDKsPath") ?? defaultSdkPath; + } + } + + /// + /// Full path to the current MSBuild configuration file. + /// + internal string CurrentMSBuildConfigurationFile { get; } + + /// + /// Full path to current MSBuild.exe. + /// + /// This path is likely not the current running process. We may be inside + /// Visual Studio or a test harness. In that case this will point to the + /// version of MSBuild found to be associated with the current environment. + /// + /// + internal string CurrentMSBuildExePath { get; private set; } + + /// + /// Full path to the current MSBuild tools directory. This will be 32-bit unless + /// we're executing from the 'AMD64' folder. + /// + internal string CurrentMSBuildToolsDirectory { get; } + + /// + /// Path to the root Visual Studio install directory + /// (e.g. 'C:\Program Files (x86)\Microsoft Visual Studio\Preview\Enterprise') + /// + internal string VisualStudioInstallRootDirectory { get; } + + /// + /// MSBuild extensions path. On Standalone this defaults to the MSBuild folder. In + /// VisualStudio mode this folder will be %VSINSTALLDIR%\MSBuild. + /// + internal string MSBuildExtensionsPath { get; set; } + } +} diff --git a/src/MSBuildTaskHost/CollectionHelpers.cs b/src/MSBuildTaskHost/CollectionHelpers.cs new file mode 100644 index 00000000000..17265682d2a --- /dev/null +++ b/src/MSBuildTaskHost/CollectionHelpers.cs @@ -0,0 +1,82 @@ +// 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; +using System.Linq; + +#nullable disable + +namespace Microsoft.Build.Shared +{ + /// + /// Utilities for collections + /// + internal static class CollectionHelpers + { + /// + /// Returns a new list containing the input list + /// contents, except for nulls + /// + /// Type of list elements + internal static List RemoveNulls(List inputs) + { + List inputsWithoutNulls = new List(inputs.Count); + + foreach (T entry in inputs) + { + if (entry != null) + { + inputsWithoutNulls.Add(entry); + } + } + + // Avoid possibly having two identical lists floating around + return (inputsWithoutNulls.Count == inputs.Count) ? inputs : inputsWithoutNulls; + } + + /// + /// Extension method -- combines a TryGet with a check to see that the value is equal. + /// + internal static bool ContainsValueAndIsEqual(this Dictionary dictionary, string key, string value, StringComparison comparer) + { + string valueFromDictionary; + if (dictionary.TryGetValue(key, out valueFromDictionary)) + { + return String.Equals(value, valueFromDictionary, comparer); + } + + return false; + } + +#if !CLR2COMPATIBILITY + internal static bool SetEquivalent(IEnumerable a, IEnumerable b) + { + return a.ToHashSet().SetEquals(b); + } + + internal static bool DictionaryEquals(IReadOnlyDictionary a, IReadOnlyDictionary b) + { + if (a.Count != b.Count) + { + return false; + } + + foreach (var aKvp in a) + { + if (!b.TryGetValue(aKvp.Key, out var bValue)) + { + return false; + } + + if (!Equals(aKvp.Value, bValue)) + { + return false; + } + } + + return true; + } +#endif + } +} diff --git a/src/MSBuildTaskHost/CommunicationsUtilities.cs b/src/MSBuildTaskHost/CommunicationsUtilities.cs new file mode 100644 index 00000000000..119464d22dd --- /dev/null +++ b/src/MSBuildTaskHost/CommunicationsUtilities.cs @@ -0,0 +1,1304 @@ +// 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; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.IO.Pipes; +using System.Runtime.InteropServices; +#if FEATURE_SECURITY_PRINCIPAL_WINDOWS || RUNTIME_TYPE_NETCORE +using System.Security.Principal; +#endif + +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using Microsoft.Build.Framework; +using Microsoft.Build.Shared; +using Microsoft.Build.BackEnd; + +#if !CLR2COMPATIBILITY +using Microsoft.Build.Shared.Debugging; +using System.Collections; +using System.Collections.Frozen; +using Microsoft.NET.StringTools; + +#endif +#if !FEATURE_APM +using System.Threading.Tasks; +#endif + +#nullable disable + +namespace Microsoft.Build.Internal +{ + /// + /// Enumeration of all possible (currently supported) options for handshakes. + /// + [Flags] + internal enum HandshakeOptions + { + None = 0, + + /// + /// Process is a TaskHost. + /// + TaskHost = 1, + + /// + /// Using the 2.0 CLR. + /// + CLR2 = 2, + + /// + /// 64-bit Intel process. + /// + X64 = 4, + + /// + /// Node reuse enabled. + /// + NodeReuse = 8, + + /// + /// Building with BelowNormal priority. + /// + LowPriority = 16, + + /// + /// Building with administrator privileges. + /// + Administrator = 32, + + /// + /// Using the .NET Core/.NET 5.0+ runtime. + /// + NET = 64, + + /// + /// ARM64 process. + /// + Arm64 = 128, + + /// + /// Using a long-running sidecar TaskHost process to reduce startup overhead and reuse in-memory caches. + /// + SidecarTaskHost = 256, + } + + /// + /// Represents a unique key for identifying task host nodes. + /// Combines HandshakeOptions (which specify runtime/architecture configuration) with + /// the scheduled node ID to uniquely identify task hosts in multi-threaded mode. + /// + /// The handshake options specifying runtime and architecture configuration. + /// + /// The scheduled node ID. In traditional multi-proc builds, this is -1 (meaning the task host + /// is identified by HandshakeOptions alone). In multi-threaded mode, each in-proc node has + /// its own task host, so the node ID is used to distinguish them. + /// + internal readonly record struct TaskHostNodeKey(HandshakeOptions HandshakeOptions, int NodeId); + + /// + /// Status codes for the handshake process. + /// It aggregates return values across several functions so we use an aggregate instead of a separate class for each method. + /// + internal enum HandshakeStatus + { + /// + /// The handshake operation completed successfully. + /// + Success = 0, + + /// + /// The other node returned a different value than expected. + /// This can happen either by attempting to connect to a wrong node type + /// (e.g., transient TaskHost trying to connect to a long-running TaskHost) + /// or by trying to connect to a node that has a different MSBuild version. + /// + VersionMismatch = 1, + + /// + /// The handshake was aborted due to connection from an old MSBuild version. + /// Occurs in TryReadInt when detecting legacy MSBuild.exe connections. + /// + OldMSBuild = 2, + + /// + /// The handshake operation timed out before completion. + /// + Timeout = 3, + + /// + /// The stream ended unexpectedly during the handshake operation. + /// Indicates an incomplete or corrupted handshake sequence. + /// + UnexpectedEndOfStream = 4, + + /// + /// The endianness (byte order) of the communicating nodes does not match. + /// Indicates an architecture compatibility issue. + /// + EndiannessMismatch = 5, + + /// + /// The handshake status is undefined or uninitialized. + /// + Undefined, + } + + /// + /// An aggregate class for passing around results of a handshake and adjacent information. + /// ErrorMessage is to propagate error messages where necessary + /// + internal class HandshakeResult + { + /// + /// Gets the status code indicating the result of the handshake operation. + /// + public HandshakeStatus Status { get; } + + /// + /// Handshake in MSBuild is performed as passing integers back and forth. + /// This field holds the value returned from a successful handshake step. + /// + public int Value { get; } + + /// + /// Gets the error message when a handshake operation fails. + /// + public string ErrorMessage { get; } + + /// + /// The negotiated packet version with the child node. + /// It's needed to ensure both sides of the communication can read/write data in pipe. + /// + public byte NegotiatedPacketVersion { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The status of the handshake operation. + /// The value returned from the handshake. + /// The error message if the handshake failed. + /// The packet version from the child node. + private HandshakeResult(HandshakeStatus status, int value, string errorMessage, byte negotiatedPacketVersion = 1) + { + Status = status; + Value = value; + ErrorMessage = errorMessage; + NegotiatedPacketVersion = negotiatedPacketVersion; + } + + /// + /// Creates a successful handshake result with the specified value. + /// + /// The value returned from the handshake operation. + /// The packet version received from the child node. + /// A new instance representing a successful operation. + public static HandshakeResult Success(int value = 0, byte negotiatedPacketVersion = 1) => new(HandshakeStatus.Success, value, null, negotiatedPacketVersion); + + /// + /// Creates a failed handshake result with the specified status and error message. + /// + /// The error status code for the failure. + /// A description of the error that occurred. + /// A new instance representing a failed operation. + public static HandshakeResult Failure(HandshakeStatus status, string errorMessage) => new(status, 0, errorMessage); + } + + internal class Handshake + { + /// + /// Marker indicating that the next integer in the child handshake response is the PacketVersion. + /// + public const int PacketVersionFromChildMarker = -1; + + // The number is selected as an arbitrary value that is unlikely to conflict with any future sdk version. + public const int NetTaskHostHandshakeVersion = 99; + + protected readonly HandshakeComponents _handshakeComponents; + + /// + /// Initializes a new instance of the class with the specified node type + /// and optional predefined tools directory. + /// + /// + /// The that specifies the type of node and configuration options for the handshake operation. + /// + /// + /// An optional directory path used for .NET TaskHost handshake salt calculation (only on .NET Framework). + /// When specified for .NET TaskHost nodes, this directory path is included in the handshake salt + /// to ensure the child dotnet process connects with the expected tools directory context. + /// For non-.NET TaskHost nodes or on .NET Core, the MSBuildToolsDirectoryRoot is used instead. + /// This parameter is ignored when not running .NET TaskHost on .NET Framework. + /// + internal Handshake(HandshakeOptions nodeType, string predefinedToolsDirectory = null) + : this(nodeType, includeSessionId: true, predefinedToolsDirectory) + { + } + + // Helper method to validate handshake option presence + internal static bool IsHandshakeOptionEnabled(HandshakeOptions hostContext, HandshakeOptions option) => (hostContext & option) == option; + + // Source options of the handshake. + internal HandshakeOptions HandshakeOptions { get; } + + protected Handshake(HandshakeOptions nodeType, bool includeSessionId, string predefinedToolsDirectory) + { + HandshakeOptions = nodeType; + + // Build handshake options with version in upper bits + const int handshakeVersion = (int)CommunicationsUtilities.handshakeVersion; + var options = (int)nodeType | (handshakeVersion << 24); + CommunicationsUtilities.Trace("Building handshake for node type {0}, (version {1}): options {2}.", nodeType, handshakeVersion, options); + + // Calculate salt from environment and tools directory + bool isNetTaskHost = IsHandshakeOptionEnabled(nodeType, HandshakeOptions.NET | HandshakeOptions.TaskHost); + string handshakeSalt = Environment.GetEnvironmentVariable("MSBUILDNODEHANDSHAKESALT") ?? ""; + string toolsDirectory = GetToolsDirectory(isNetTaskHost, predefinedToolsDirectory); + int salt = CommunicationsUtilities.GetHashCode($"{handshakeSalt}{toolsDirectory}"); + + CommunicationsUtilities.Trace("Handshake salt is {0}", handshakeSalt); + CommunicationsUtilities.Trace("Tools directory root is {0}", toolsDirectory); + + // Get session ID if needed (expensive call) + int sessionId = 0; + if (includeSessionId) + { + using var currentProcess = Process.GetCurrentProcess(); + sessionId = currentProcess.SessionId; + } + + _handshakeComponents = isNetTaskHost + ? CreateNetTaskHostComponents(options, salt, sessionId) + : CreateStandardComponents(options, salt, sessionId); + } + + private string GetToolsDirectory(bool isNetTaskHost, string predefinedToolsDirectory) => +#if NETFRAMEWORK + isNetTaskHost + + // For .NET TaskHost assembly directory we set the expectation for the child dotnet process to connect to. + ? predefinedToolsDirectory + : BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot; +#else + BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot; +#endif + + private static HandshakeComponents CreateNetTaskHostComponents(int options, int salt, int sessionId) => new( + options, + salt, + NetTaskHostHandshakeVersion, + NetTaskHostHandshakeVersion, + NetTaskHostHandshakeVersion, + NetTaskHostHandshakeVersion, + sessionId); + + private static HandshakeComponents CreateStandardComponents(int options, int salt, int sessionId) + { + var fileVersion = new Version(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion); + + return new( + options, + salt, + fileVersion.Major, + fileVersion.Minor, + fileVersion.Build, + fileVersion.Revision, + sessionId); + } + + public virtual HandshakeComponents RetrieveHandshakeComponents() => new HandshakeComponents( + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.Options), + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.Salt), + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionMajor), + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionMinor), + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionBuild), + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionPrivate), + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.SessionId)); + + public virtual string GetKey() => $"{_handshakeComponents.Options} {_handshakeComponents.Salt} {_handshakeComponents.FileVersionMajor} {_handshakeComponents.FileVersionMinor} {_handshakeComponents.FileVersionBuild} {_handshakeComponents.FileVersionPrivate} {_handshakeComponents.SessionId}".ToString(CultureInfo.InvariantCulture); + + public virtual byte? ExpectedVersionInFirstByte => CommunicationsUtilities.handshakeVersion; + } + + internal sealed class ServerNodeHandshake : Handshake + { + /// + /// Caching computed hash. + /// + private string _computedHash = null; + + public override byte? ExpectedVersionInFirstByte => null; + + internal ServerNodeHandshake(HandshakeOptions nodeType) + : base(nodeType, includeSessionId: false, predefinedToolsDirectory: null) + { + } + + public override HandshakeComponents RetrieveHandshakeComponents() => new HandshakeComponents( + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.Options), + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.Salt), + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionMajor), + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionMinor), + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionBuild), + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionPrivate)); + + public override string GetKey() => $"{_handshakeComponents.Options} {_handshakeComponents.Salt} {_handshakeComponents.FileVersionMajor} {_handshakeComponents.FileVersionMinor} {_handshakeComponents.FileVersionBuild} {_handshakeComponents.FileVersionPrivate}" + .ToString(CultureInfo.InvariantCulture); + + /// + /// Computes Handshake stable hash string representing whole state of handshake. + /// + public string ComputeHash() + { + if (_computedHash == null) + { + var input = GetKey(); + byte[] utf8 = Encoding.UTF8.GetBytes(input); +#if NET + Span bytes = stackalloc byte[SHA256.HashSizeInBytes]; + SHA256.HashData(utf8, bytes); +#else + using var sha = SHA256.Create(); + var bytes = sha.ComputeHash(utf8); +#endif + _computedHash = Convert.ToBase64String(bytes) + .Replace("/", "_") + .Replace("=", string.Empty); + } + return _computedHash; + } + } + + /// + /// This class contains utility methods for the MSBuild engine. + /// + internal static class CommunicationsUtilities + { + /// + /// Indicates to the NodeEndpoint that all the various parts of the Handshake have been sent. + /// + private const int EndOfHandshakeSignal = -0x2a2a2a2a; + + /// + /// The version of the handshake. This should be updated each time the handshake structure is altered. + /// + internal const byte handshakeVersion = 0x01; + + /// + /// The timeout to connect to a node. + /// + private const int DefaultNodeConnectionTimeout = 900 * 1000; // 15 minutes; enough time that a dev will typically do another build in this time + + /// + /// Whether to trace communications + /// + private static readonly bool s_trace = Traits.Instance.DebugNodeCommunication; + + /// + /// Lock trace to ensure we are logging in serial fashion. + /// + private static readonly LockType s_traceLock = new LockType(); + + /// + /// Place to dump trace + /// + private static string s_debugDumpPath; + + /// + /// Ticks at last time logged + /// + private static long s_lastLoggedTicks = DateTime.UtcNow.Ticks; + +#if !CLR2COMPATIBILITY + /// + /// A set of environment variables cached from the last time we called GetEnvironmentVariables. + /// Used to avoid allocations if the environment has not changed. + /// + private static EnvironmentState s_environmentState; +#endif + + /// + /// Delegate to debug the communication utilities. + /// + internal delegate void LogDebugCommunications(string format, params object[] stuff); + + /// + /// On Windows, environment variables should be case-insensitive; + /// on Unix-like systems, they should be case-sensitive, but this might be a breaking change in an edge case. + /// https://github.com/dotnet/msbuild/issues/12858 + /// + internal static StringComparer EnvironmentVariableComparer => StringComparer.OrdinalIgnoreCase; + + /// + /// Gets or sets the node connection timeout. + /// + internal static int NodeConnectionTimeout + { + get { return GetIntegerVariableOrDefault("MSBUILDNODECONNECTIONTIMEOUT", DefaultNodeConnectionTimeout); } + } + + /// + /// Get environment block. + /// + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + internal static extern unsafe char* GetEnvironmentStrings(); + + /// + /// Free environment block. + /// + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + internal static extern unsafe bool FreeEnvironmentStrings(char* pStrings); + +#if NETFRAMEWORK + /// + /// Set environment variable P/Invoke. + /// + [DllImport("kernel32.dll", EntryPoint = "SetEnvironmentVariable", SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool SetEnvironmentVariableNative(string name, string value); + + /// + /// Sets an environment variable using P/Invoke to workaround the .NET Framework BCL implementation. + /// + /// + /// .NET Framework implementation of SetEnvironmentVariable checks the length of the value and throws an exception if + /// it's greater than or equal to 32,767 characters. This limitation does not exist on modern Windows or .NET. + /// + internal static void SetEnvironmentVariable(string name, string value) + { + if (!SetEnvironmentVariableNative(name, value)) + { + throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()); + } + } +#endif + +#if !CLR2COMPATIBILITY + /// + /// A container to atomically swap a cached set of environment variables and the block string used to create it. + /// The environment block property will only be set on Windows, since on Unix we need to directly call + /// Environment.GetEnvironmentVariables(). + /// + private sealed record class EnvironmentState(FrozenDictionary EnvironmentVariables, ReadOnlyMemory EnvironmentBlock = default); +#endif + + /// + /// Returns key value pairs of environment variables in a new dictionary + /// with a case-insensitive key comparer. + /// + /// + /// Copied from the BCL implementation to eliminate some expensive security asserts on .NET Framework. + /// +#if CLR2COMPATIBILITY + internal static Dictionary GetEnvironmentVariables() + { +#else + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + private static FrozenDictionary GetEnvironmentVariablesWindows() + { + // The DebugUtils static constructor can set the MSBUILDDEBUGPATH environment variable to propagate the debug path to out of proc nodes. + // Need to ensure that constructor is called before this method returns in order to capture its env var write. + // Otherwise the env var is not captured and thus gets deleted when RequiestBuilder resets the environment based on the cached results of this method. + ErrorUtilities.VerifyThrowInternalNull(DebugUtils.ProcessInfoString, nameof(DebugUtils.DebugPath)); +#endif + + unsafe + { + char* pEnvironmentBlock = null; + + try + { + pEnvironmentBlock = GetEnvironmentStrings(); + if (pEnvironmentBlock == null) + { + throw new OutOfMemoryException(); + } + + // Search for terminating \0\0 (two unicode \0's). + char* pEnvironmentBlockEnd = pEnvironmentBlock; + while (!(*pEnvironmentBlockEnd == '\0' && *(pEnvironmentBlockEnd + 1) == '\0')) + { + pEnvironmentBlockEnd++; + } + long stringBlockLength = pEnvironmentBlockEnd - pEnvironmentBlock; + +#if !CLR2COMPATIBILITY + // Avoid allocating any objects if the environment still matches the last state. + // We speed this up by comparing the full block instead of individual key-value pairs. + ReadOnlySpan stringBlock = new(pEnvironmentBlock, (int)stringBlockLength); + EnvironmentState lastState = s_environmentState; + if (lastState?.EnvironmentBlock.Span.SequenceEqual(stringBlock) == true) + { + return lastState.EnvironmentVariables; + } +#endif + + Dictionary table = new(200, StringComparer.OrdinalIgnoreCase); // Razzle has 150 environment variables + + // Copy strings out, parsing into pairs and inserting into the table. + // The first few environment variable entries start with an '='! + // The current working directory of every drive (except for those drives + // you haven't cd'ed into in your DOS window) are stored in the + // environment block (as =C:=pwd) and the program's exit code is + // as well (=ExitCode=00000000) Skip all that start with =. + // Read docs about Environment Blocks on MSDN's CreateProcess page. + + // Format for GetEnvironmentStrings is: + // (=HiddenVar=value\0 | Variable=value\0)* \0 + // See the description of Environment Blocks in MSDN's + // CreateProcess page (null-terminated array of null-terminated strings). + // Note the =HiddenVar's aren't always at the beginning. + for (int i = 0; i < stringBlockLength; i++) + { + int startKey = i; + + // Skip to key + // On some old OS, the environment block can be corrupted. + // Some lines will not have '=', so we need to check for '\0'. + while (*(pEnvironmentBlock + i) != '=' && *(pEnvironmentBlock + i) != '\0') + { + i++; + } + + if (*(pEnvironmentBlock + i) == '\0') + { + continue; + } + + // Skip over environment variables starting with '=' + if (i - startKey == 0) + { + while (*(pEnvironmentBlock + i) != 0) + { + i++; + } + + continue; + } + +#if !CLR2COMPATIBILITY + string key = Strings.WeakIntern(new ReadOnlySpan(pEnvironmentBlock + startKey, i - startKey)); +#else + string key = new string(pEnvironmentBlock, startKey, i - startKey); +#endif + + i++; + + // skip over '=' + int startValue = i; + + while (*(pEnvironmentBlock + i) != 0) + { + // Read to end of this entry + i++; + } + +#if !CLR2COMPATIBILITY + string value = Strings.WeakIntern(new ReadOnlySpan(pEnvironmentBlock + startValue, i - startValue)); +#else + string value = new string(pEnvironmentBlock, startValue, i - startValue); +#endif + + // skip over 0 handled by for loop's i++ + table[key] = value; + } + +#if !CLR2COMPATIBILITY + // Update with the current state. + EnvironmentState currentState = + new(table.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase), stringBlock.ToArray()); + s_environmentState = currentState; + return currentState.EnvironmentVariables; +#else + return table; +#endif + } + finally + { + if (pEnvironmentBlock != null) + { + FreeEnvironmentStrings(pEnvironmentBlock); + } + } + } + } + +#if NET + /// + /// Sets an environment variable using . + /// + internal static void SetEnvironmentVariable(string name, string value) + => Environment.SetEnvironmentVariable(name, value); +#endif + +#if !CLR2COMPATIBILITY + /// + /// Returns key value pairs of environment variables in a read-only dictionary + /// with a case-insensitive key comparer. + /// + /// If the environment variables have not changed since the last time + /// this method was called, the same dictionary instance will be returned. + /// + internal static FrozenDictionary GetEnvironmentVariables() + { + // Always call the native method on Windows, as we'll be able to avoid the internal + // string and Hashtable allocations caused by Environment.GetEnvironmentVariables(). + if (NativeMethodsShared.IsWindows) + { + return GetEnvironmentVariablesWindows(); + } + + IDictionary vars = Environment.GetEnvironmentVariables(); + + // Directly use the enumerator since Current will box DictionaryEntry. + IDictionaryEnumerator enumerator = vars.GetEnumerator(); + + // If every key-value pair matches the last state, return a cached dictionary. + FrozenDictionary lastEnvironmentVariables = s_environmentState?.EnvironmentVariables; + if (vars.Count == lastEnvironmentVariables?.Count) + { + bool sameState = true; + + while (enumerator.MoveNext() && sameState) + { + DictionaryEntry entry = enumerator.Entry; + if (!lastEnvironmentVariables.TryGetValue((string)entry.Key, out string value) + || !string.Equals((string)entry.Value, value, StringComparison.Ordinal)) + { + sameState = false; + } + } + + if (sameState) + { + return lastEnvironmentVariables; + } + } + + // Otherwise, allocate and update with the current state. + Dictionary table = new(vars.Count, EnvironmentVariableComparer); + + enumerator.Reset(); + while (enumerator.MoveNext()) + { + DictionaryEntry entry = enumerator.Entry; + string key = Strings.WeakIntern((string)entry.Key); + string value = Strings.WeakIntern((string)entry.Value); + table[key] = value; + } + + EnvironmentState newState = new(table.ToFrozenDictionary(EnvironmentVariableComparer)); + s_environmentState = newState; + + return newState.EnvironmentVariables; + } +#endif + + /// + /// Updates the environment to match the provided dictionary. + /// + internal static void SetEnvironment(IDictionary newEnvironment) + { + if (newEnvironment != null) + { + // First, delete all no longer set variables + IDictionary currentEnvironment = GetEnvironmentVariables(); + foreach (KeyValuePair entry in currentEnvironment) + { + if (!newEnvironment.ContainsKey(entry.Key)) + { + SetEnvironmentVariable(entry.Key, null); + } + } + + // Then, make sure the new ones have their new values. + foreach (KeyValuePair entry in newEnvironment) + { + if (!currentEnvironment.TryGetValue(entry.Key, out string currentValue) || currentValue != entry.Value) + { + SetEnvironmentVariable(entry.Key, entry.Value); + } + } + } + } + +#nullable enable + /// + /// Indicate to the client that all elements of the Handshake have been sent. + /// + internal static void WriteEndOfHandshakeSignal(this PipeStream stream) + { + stream.WriteIntForHandshake(EndOfHandshakeSignal); + } + + /// + /// Extension method to write a series of bytes to a stream + /// + internal static void WriteIntForHandshake(this PipeStream stream, int value) + { + byte[] bytes = BitConverter.GetBytes(value); + + // We want to read the long and send it from left to right (this means big endian) + // if we are little endian we need to reverse the array to keep the left to right reading + if (BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); + } + + ErrorUtilities.VerifyThrow(bytes.Length == 4, "Int should be 4 bytes"); + + stream.Write(bytes, 0, bytes.Length); + } + + internal static bool TryReadEndOfHandshakeSignal( + this PipeStream stream, + bool isProvider, +#if NETCOREAPP2_1_OR_GREATER + int timeout, +#endif + out HandshakeResult result) + { + // Accept only the first byte of the EndOfHandshakeSignal + if (stream.TryReadIntForHandshake( + byteToAccept: null, +#if NETCOREAPP2_1_OR_GREATER + timeout, +#endif + out HandshakeResult innerResult)) + { + byte negotiatedPacketVersion = 1; + + if (innerResult.Value != EndOfHandshakeSignal) + { + // If the received handshake part is not PacketVersionFromChildMarker it means we communicate with the host that does not support packet version negotiation. + // Fallback to the old communication validation pattern. + if (innerResult.Value != Handshake.PacketVersionFromChildMarker) + { + result = CreateVersionMismatchResult(isProvider, innerResult.Value); + return false; + } + + // We detected packet version marker, now let's read actual PacketVersion + if (!stream.TryReadIntForHandshake( + byteToAccept: null, +#if NETCOREAPP2_1_OR_GREATER + timeout, +#endif + out HandshakeResult versionResult)) + { + result = versionResult; + return false; + } + + byte childVersion = (byte)versionResult.Value; + negotiatedPacketVersion = NodePacketTypeExtensions.GetNegotiatedPacketVersion(childVersion); + Trace("Node PacketVersion: {0}, Local: {1}, Negotiated: {2}", childVersion, NodePacketTypeExtensions.PacketVersion, negotiatedPacketVersion); + + if (!stream.TryReadIntForHandshake( + byteToAccept: null, +#if NETCOREAPP2_1_OR_GREATER + timeout, +#endif + out innerResult)) + { + result = innerResult; + return false; + } + + if (innerResult.Value != EndOfHandshakeSignal) + { + result = CreateVersionMismatchResult(isProvider, innerResult.Value); + return false; + } + } + + result = HandshakeResult.Success(0, negotiatedPacketVersion); + return true; + } + else + { + result = innerResult; + return false; + } + } + + private static HandshakeResult CreateVersionMismatchResult(bool isProvider, int receivedValue) + { + var errorMessage = isProvider + ? $"Handshake failed on part {receivedValue}. Probably the client is a different MSBuild build." + : $"Expected end of handshake signal but received {receivedValue}. Probably the host is a different MSBuild build."; + Trace(errorMessage); + + return HandshakeResult.Failure(HandshakeStatus.VersionMismatch, errorMessage); + } + +#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter + /// + /// Extension method to read a series of bytes from a stream. + /// If specified, leading byte matches one in the supplied array if any, returns rejection byte and throws IOException. + /// + internal static bool TryReadIntForHandshake( + this PipeStream stream, + byte? byteToAccept, +#if NETCOREAPP2_1_OR_GREATER + int timeout, +#endif + out HandshakeResult result + ) +#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter + { + byte[] bytes = new byte[4]; + +#if NETCOREAPP2_1_OR_GREATER + if (!NativeMethodsShared.IsWindows) + { + // Enforce a minimum timeout because the timeout passed to Connect() just before + // calling this method does not apply on UNIX domain socket-based + // implementations of PipeStream. + // https://github.com/dotnet/corefx/issues/28791 + timeout = Math.Max(timeout, 50); + + // A legacy MSBuild.exe won't try to connect to MSBuild running + // in a dotnet host process, so we can read the bytes simply. + var readTask = stream.ReadAsync(bytes, 0, bytes.Length); + + // Manual timeout here because the timeout passed to Connect() just before + // calling this method does not apply on UNIX domain socket-based + // implementations of PipeStream. + // https://github.com/dotnet/corefx/issues/28791 + if (!readTask.Wait(timeout)) + { + result = HandshakeResult.Failure(HandshakeStatus.Timeout, String.Format(CultureInfo.InvariantCulture, "Did not receive return handshake in {0}ms", timeout)); + return false; + } + readTask.GetAwaiter().GetResult(); + } + else +#endif + { + int bytesRead = stream.Read(bytes, 0, bytes.Length); + + // Abort for connection attempts from ancient MSBuild.exes + if (byteToAccept != null && bytesRead > 0 && byteToAccept != bytes[0]) + { + stream.WriteIntForHandshake(0x0F0F0F0F); + stream.WriteIntForHandshake(0x0F0F0F0F); + result = HandshakeResult.Failure(HandshakeStatus.OldMSBuild, String.Format(CultureInfo.InvariantCulture, "Client: rejected old host. Received byte {0} instead of {1}.", bytes[0], byteToAccept)); + return false; + } + + if (bytesRead != bytes.Length) + { + // We've unexpectly reached end of stream. + // We are now in a bad state, disconnect on our end + result = HandshakeResult.Failure(HandshakeStatus.UnexpectedEndOfStream, String.Format(CultureInfo.InvariantCulture, "Unexpected end of stream while reading for handshake")); + + return false; + } + } + + try + { + // We want to read the long and send it from left to right (this means big endian) + // If we are little endian the stream has already been reversed by the sender, we need to reverse it again to get the original number + if (BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); + } + + result = HandshakeResult.Success(BitConverter.ToInt32(bytes, 0 /* start index */)); + } + catch (ArgumentException ex) + { + result = HandshakeResult.Failure(HandshakeStatus.EndiannessMismatch, String.Format(CultureInfo.InvariantCulture, "Failed to convert the handshake to big-endian. {0}", ex.Message)); + return false; + } + + return true; + } +#nullable disable + +#if !FEATURE_APM + internal static async ValueTask ReadAsync(Stream stream, byte[] buffer, int bytesToRead) + { + int totalBytesRead = 0; + while (totalBytesRead < bytesToRead) + { + int bytesRead = await stream.ReadAsync(buffer.AsMemory(totalBytesRead, bytesToRead - totalBytesRead), CancellationToken.None); + if (bytesRead == 0) + { + return totalBytesRead; + } + totalBytesRead += bytesRead; + } + + return totalBytesRead; + } +#endif + + /// + /// Given the appropriate information, return the equivalent HandshakeOptions. + /// + internal static HandshakeOptions GetHandshakeOptions( + bool taskHost, + TaskHostParameters taskHostParameters, + string architectureFlagToSet = null, + bool nodeReuse = false, + bool lowPriority = false) + { + HandshakeOptions context = taskHost ? HandshakeOptions.TaskHost : HandshakeOptions.None; + + int clrVersion = 0; + + // We don't know about the TaskHost. + if (taskHost) + { + // No parameters given, default to current + if (taskHostParameters.IsEmpty) + { + clrVersion = typeof(bool).GetTypeInfo().Assembly.GetName().Version.Major; + architectureFlagToSet = XMakeAttributes.GetCurrentMSBuildArchitecture(); + } + else // Figure out flags based on parameters given + { + ErrorUtilities.VerifyThrow(taskHostParameters.Runtime != null, "Should always have an explicit runtime when we call this method."); + ErrorUtilities.VerifyThrow(taskHostParameters.Architecture != null, "Should always have an explicit architecture when we call this method."); + + if (taskHostParameters.Runtime.Equals(XMakeAttributes.MSBuildRuntimeValues.clr2, StringComparison.OrdinalIgnoreCase)) + { + clrVersion = 2; + } + else if (taskHostParameters.Runtime.Equals(XMakeAttributes.MSBuildRuntimeValues.clr4, StringComparison.OrdinalIgnoreCase)) + { + clrVersion = 4; + } + else if (taskHostParameters.Runtime.Equals(XMakeAttributes.MSBuildRuntimeValues.net, StringComparison.OrdinalIgnoreCase)) + { + clrVersion = 5; + } + else + { + ErrorUtilities.ThrowInternalErrorUnreachable(); + } + + architectureFlagToSet = taskHostParameters.Architecture; + } + } + + if (!string.IsNullOrEmpty(architectureFlagToSet)) + { + if (architectureFlagToSet.Equals(XMakeAttributes.MSBuildArchitectureValues.x64, StringComparison.OrdinalIgnoreCase)) + { + context |= HandshakeOptions.X64; + } + else if (architectureFlagToSet.Equals(XMakeAttributes.MSBuildArchitectureValues.arm64, StringComparison.OrdinalIgnoreCase)) + { + context |= HandshakeOptions.Arm64; + } + } + + switch (clrVersion) + { + case 0: + // Not a taskhost, runtime must match + case 4: + // Default for MSBuild running on .NET Framework 4, + // not represented in handshake + break; + case 2: + context |= HandshakeOptions.CLR2; + break; + case >= 5: + context |= HandshakeOptions.NET; + break; + default: + ErrorUtilities.ThrowInternalErrorUnreachable(); + break; + } + + // Node reuse is not supported in CLR2 because it's a legacy runtime. + if (nodeReuse && clrVersion != 2) + { + context |= HandshakeOptions.NodeReuse; + } + + if (lowPriority) + { + context |= HandshakeOptions.LowPriority; + } + +#if FEATURE_SECURITY_PRINCIPAL_WINDOWS || RUNTIME_TYPE_NETCORE + // If we are running in elevated privs, we will only accept a handshake from an elevated process as well. + // Both the client and the host will calculate this separately, and the idea is that if they come out the same + // then we can be sufficiently confident that the other side has the same elevation level as us. This is complementary + // to the username check which is also done on connection. + if ( +#if RUNTIME_TYPE_NETCORE + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && +#endif + new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) + { + context |= HandshakeOptions.Administrator; + } +#endif + return context; + } + + /// + /// Gets the value of an integer environment variable, or returns the default if none is set or it cannot be converted. + /// + internal static int GetIntegerVariableOrDefault(string environmentVariable, int defaultValue) + { + string environmentValue = Environment.GetEnvironmentVariable(environmentVariable); + if (String.IsNullOrEmpty(environmentValue)) + { + return defaultValue; + } + + int localDefaultValue; + if (Int32.TryParse(environmentValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out localDefaultValue)) + { + defaultValue = localDefaultValue; + } + + return defaultValue; + } + + /// + /// Writes trace information to a log file + /// + internal static void Trace(string format, T arg0) + { + Trace(nodeId: -1, format, arg0); + } + + /// + /// Writes trace information to a log file + /// + internal static void Trace(int nodeId, string format, T arg0) + { + if (s_trace) + { + TraceCore(nodeId, string.Format(format, arg0)); + } + } + + /// + /// Writes trace information to a log file + /// + internal static void Trace(string format, T0 arg0, T1 arg1) + { + Trace(nodeId: -1, format, arg0, arg1); + } + + /// + /// Writes trace information to a log file + /// + internal static void Trace(int nodeId, string format, T0 arg0, T1 arg1) + { + if (s_trace) + { + TraceCore(nodeId, string.Format(format, arg0, arg1)); + } + } + + /// + /// Writes trace information to a log file + /// + internal static void Trace(string format, T0 arg0, T1 arg1, T2 arg2) + { + Trace(nodeId: -1, format, arg0, arg1, arg2); + } + + /// + /// Writes trace information to a log file + /// + internal static void Trace(int nodeId, string format, T0 arg0, T1 arg1, T2 arg2) + { + if (s_trace) + { + TraceCore(nodeId, string.Format(format, arg0, arg1, arg2)); + } + } + + /// + /// Writes trace information to a log file + /// + internal static void Trace(string format, params object[] args) + { + Trace(nodeId: -1, format, args); + } + + /// + /// Writes trace information to a log file + /// + internal static void Trace(int nodeId, string format, params object[] args) + { + if (s_trace) + { + string message = string.Format(CultureInfo.CurrentCulture, format, args); + TraceCore(nodeId, message); + } + } + + internal static void Trace(int nodeId, string message) + { + if (s_trace) + { + TraceCore(nodeId, message); + } + } + + /// + /// Writes trace information to a log file + /// + private static void TraceCore(int nodeId, string message) + { + lock (s_traceLock) + { + s_debugDumpPath ??= +#if CLR2COMPATIBILITY + Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); +#else + DebugUtils.DebugPath; +#endif + + if (String.IsNullOrEmpty(s_debugDumpPath)) + { + s_debugDumpPath = FileUtilities.TempFileDirectory; + } + else + { + Directory.CreateDirectory(s_debugDumpPath); + } + + try + { + string fileName = @"MSBuild_CommTrace_PID_{0}"; + if (nodeId != -1) + { + fileName += "_node_" + nodeId; + } + + fileName += ".txt"; + + using (StreamWriter file = FileUtilities.OpenWrite( + string.Format(CultureInfo.CurrentCulture, Path.Combine(s_debugDumpPath, fileName), EnvironmentUtilities.CurrentProcessId, nodeId), append: true)) + { + long now = DateTime.UtcNow.Ticks; + float millisecondsSinceLastLog = (float)(now - s_lastLoggedTicks) / 10000L; + s_lastLoggedTicks = now; + file.WriteLine("{0} (TID {1}) {2,15} +{3,10}ms: {4}", Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId, now, millisecondsSinceLastLog, message); + } + } + catch (IOException) + { + // Ignore + } + } + } + + /// + /// Gets a hash code for this string. If strings A and B are such that A.Equals(B), then + /// they will return the same hash code. + /// This is as implemented in CLR String.GetHashCode() [ndp\clr\src\BCL\system\String.cs] + /// but stripped out architecture specific defines + /// that causes the hashcode to be different and this causes problem in cross-architecture handshaking. + /// + internal static int GetHashCode(string fileVersion) + { + unsafe + { + fixed (char* src = fileVersion) + { + int hash1 = (5381 << 16) + 5381; + int hash2 = hash1; + + int* pint = (int*)src; + int len = fileVersion.Length; + while (len > 0) + { + hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0]; + if (len <= 2) + { + break; + } + + hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ pint[1]; + pint += 2; + len -= 4; + } + + return hash1 + (hash2 * 1566083941); + } + } + } + + internal static int AvoidEndOfHandshakeSignal(int x) => x == EndOfHandshakeSignal ? ~x : x; + } + + /// + /// Represents the components of a handshake in a structured format with named fields. + /// + internal readonly struct HandshakeComponents + { + private readonly int options; + private readonly int salt; + private readonly int fileVersionMajor; + private readonly int fileVersionMinor; + private readonly int fileVersionBuild; + private readonly int fileVersionPrivate; + private readonly int sessionId; + + public HandshakeComponents(int options, int salt, int fileVersionMajor, int fileVersionMinor, int fileVersionBuild, int fileVersionPrivate, int sessionId) + { + this.options = options; + this.salt = salt; + this.fileVersionMajor = fileVersionMajor; + this.fileVersionMinor = fileVersionMinor; + this.fileVersionBuild = fileVersionBuild; + this.fileVersionPrivate = fileVersionPrivate; + this.sessionId = sessionId; + } + + public HandshakeComponents(int options, int salt, int fileVersionMajor, int fileVersionMinor, int fileVersionBuild, int fileVersionPrivate) + : this(options, salt, fileVersionMajor, fileVersionMinor, fileVersionBuild, fileVersionPrivate, 0) + { + } + + public int Options => options; + + public int Salt => salt; + + public int FileVersionMajor => fileVersionMajor; + + public int FileVersionMinor => fileVersionMinor; + + public int FileVersionBuild => fileVersionBuild; + + public int FileVersionPrivate => fileVersionPrivate; + + public int SessionId => sessionId; + + public IEnumerable> EnumerateComponents() + { + yield return new KeyValuePair(nameof(Options), Options); + yield return new KeyValuePair(nameof(Salt), Salt); + yield return new KeyValuePair(nameof(FileVersionMajor), FileVersionMajor); + yield return new KeyValuePair(nameof(FileVersionMinor), FileVersionMinor); + yield return new KeyValuePair(nameof(FileVersionBuild), FileVersionBuild); + yield return new KeyValuePair(nameof(FileVersionPrivate), FileVersionPrivate); + yield return new KeyValuePair(nameof(SessionId), SessionId); + } + + public override string ToString() => $"{options} {salt} {fileVersionMajor} {fileVersionMinor} {fileVersionBuild} {fileVersionPrivate} {sessionId}"; + } +} diff --git a/src/MSBuildTaskHost/Constants.cs b/src/MSBuildTaskHost/Constants.cs new file mode 100644 index 00000000000..0e5afa5b465 --- /dev/null +++ b/src/MSBuildTaskHost/Constants.cs @@ -0,0 +1,270 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +#if NET +using System.Buffers; +#endif +using System.IO; + +#nullable disable + +namespace Microsoft.Build.Shared +{ + /// + /// Constants that we want to be shareable across all our assemblies. + /// + internal static class MSBuildConstants + { + /// + /// The name of the property that indicates the tools path + /// + internal const string ToolsPath = "MSBuildToolsPath"; + + /// + /// Name of the property that indicates the X64 tools path + /// + internal const string ToolsPath64 = "MSBuildToolsPath64"; + + /// + /// Name of the property that indicates the root of the SDKs folder + /// + internal const string SdksPath = "MSBuildSDKsPath"; + + /// + /// Name of the property that indicates that all warnings should be treated as errors. + /// + internal const string TreatWarningsAsErrors = "MSBuildTreatWarningsAsErrors"; + + /// + /// Name of the property that indicates a list of warnings to treat as errors. + /// + internal const string WarningsAsErrors = "MSBuildWarningsAsErrors"; + + /// + /// Name of the property that indicates a list of warnings to not treat as errors. + /// + internal const string WarningsNotAsErrors = "MSBuildWarningsNotAsErrors"; + + /// + /// Name of the property that indicates the list of warnings to treat as messages. + /// + internal const string WarningsAsMessages = "MSBuildWarningsAsMessages"; + + /// + /// The name of the environment variable that users can specify to override where NuGet assemblies are loaded from in the NuGetSdkResolver. + /// + internal const string NuGetAssemblyPathEnvironmentVariableName = "MSBUILD_NUGET_PATH"; + + /// + /// The name of the target to run when a user specifies the /restore command-line argument. + /// + internal const string RestoreTargetName = "Restore"; + /// + /// The most current Visual Studio Version known to this version of MSBuild. + /// + internal const string CurrentVisualStudioVersion = "18.0"; + + /// + /// The most current ToolsVersion known to this version of MSBuild. + /// + internal const string CurrentToolsVersion = "Current"; + + internal const string MSBuildDummyGlobalPropertyHeader = "MSBuildProjectInstance"; + + /// + /// A property set during an implicit restore (/restore) or explicit restore (/t:restore) to ensure that the evaluations are not re-used during build + /// + internal const string MSBuildRestoreSessionId = nameof(MSBuildRestoreSessionId); + + /// + /// A property set during an implicit restore (/restore) or explicit restore (/t:restore) to indicate that a restore is executing. + /// + internal const string MSBuildIsRestoring = nameof(MSBuildIsRestoring); + + + /// + /// The most current VSGeneralAssemblyVersion known to this version of MSBuild. + /// + internal const string CurrentAssemblyVersion = "15.1.0.0"; + + /// + /// Current version of this MSBuild Engine assembly in the form, e.g, "12.0" + /// + internal const string CurrentProductVersion = "18.0"; + + /// + /// Symbol used in ProjectReferenceTarget items to represent default targets + /// + internal const string DefaultTargetsMarker = ".default"; + + /// + /// Framework version against which our test projects should be built. + /// + /// + /// The targeting pack for this version of .NET Framework must be installed + /// on any machine that wants to run tests successfully, so this can be + /// periodically updated. + /// + internal const string StandardTestTargetFrameworkVersion = "v4.8"; + + /// + /// Symbol used in ProjectReferenceTarget items to represent targets specified on the ProjectReference item + /// with fallback to default targets if the ProjectReference item has no targets specified. + /// + internal const string ProjectReferenceTargetsOrDefaultTargetsMarker = ".projectReferenceTargetsOrDefaultTargets"; + + // One-time allocations to avoid implicit allocations for Split(), Trim(). + internal static readonly char[] SemicolonChar = [';']; + internal static readonly char[] SpaceChar = [' ']; + internal static readonly char[] SingleQuoteChar = ['\'']; + internal static readonly char[] EqualsChar = ['=']; + internal static readonly char[] ColonChar = [':']; + internal static readonly char[] BackslashChar = ['\\']; + internal static readonly char[] NewlineChar = ['\n']; + internal static readonly char[] CrLf = ['\r', '\n']; + internal static readonly char[] ForwardSlash = ['/']; + internal static readonly char[] ForwardSlashBackslash = ['/', '\\']; + internal static readonly char[] WildcardChars = ['*', '?']; + internal static readonly string[] CharactersForExpansion = ["*", "?", "$(", "@(", "%"]; + internal static readonly char[] CommaChar = [',']; + internal static readonly char[] HyphenChar = ['-']; + internal static readonly char[] DirectorySeparatorChar = [Path.DirectorySeparatorChar]; + internal static readonly char[] DotChar = ['.']; + internal static readonly string[] EnvironmentNewLine = [Environment.NewLine]; + internal static readonly char[] PipeChar = ['|']; + internal static readonly char[] PathSeparatorChar = [Path.PathSeparator]; + +#if NET + internal static readonly SearchValues InvalidPathChars = SearchValues.Create(Path.GetInvalidPathChars()); +#else + internal static readonly char[] InvalidPathChars = Path.GetInvalidPathChars(); +#endif + } + + internal static class PropertyNames + { + /// + /// Specifies whether the current evaluation / build is happening during a graph build + /// + internal const string IsGraphBuild = nameof(IsGraphBuild); + + internal const string InnerBuildProperty = nameof(InnerBuildProperty); + internal const string InnerBuildPropertyValues = nameof(InnerBuildPropertyValues); + internal const string TargetFrameworks = nameof(TargetFrameworks); + internal const string TargetFramework = nameof(TargetFramework); + internal const string UsingMicrosoftNETSdk = nameof(UsingMicrosoftNETSdk); + + /// + /// When true, `SkipNonexistentProjects=Build` becomes the default setting of MSBuild tasks. + /// + internal const string BuildNonexistentProjectsByDefault = "_" + nameof(BuildNonexistentProjectsByDefault); + } + + // TODO: Remove these when VS gets updated to setup project cache plugins. + internal static class DesignTimeProperties + { + internal const string DesignTimeBuild = nameof(DesignTimeBuild); + internal const string BuildingProject = nameof(BuildingProject); + } + + internal static class ItemTypeNames + { + /// + /// References to other msbuild projects + /// + internal const string ProjectReference = nameof(ProjectReference); + + /// + /// Statically specifies what targets a project calls on its references + /// + internal const string ProjectReferenceTargets = nameof(ProjectReferenceTargets); + + internal const string GraphIsolationExemptReference = nameof(GraphIsolationExemptReference); + + /// + /// Declares a project cache plugin and its configuration. + /// + internal const string ProjectCachePlugin = nameof(ProjectCachePlugin); + + /// + /// Embed specified files in the binary log + /// + internal const string EmbedInBinlog = nameof(EmbedInBinlog); + } + + /// + /// Constants naming well-known item metadata. + /// + internal static class ItemMetadataNames + { + internal const string fusionName = "FusionName"; + internal const string hintPath = "HintPath"; + internal const string assemblyFolderKey = "AssemblyFolderKey"; + internal const string alias = "Alias"; + internal const string aliases = "Aliases"; + internal const string parentFile = "ParentFile"; + internal const string privateMetadata = "Private"; + internal const string copyLocal = "CopyLocal"; + internal const string isRedistRoot = "IsRedistRoot"; + internal const string redist = "Redist"; + internal const string resolvedFrom = "ResolvedFrom"; + internal const string destinationSubDirectory = "DestinationSubDirectory"; + internal const string destinationSubPath = "DestinationSubPath"; + internal const string specificVersion = "SpecificVersion"; + internal const string link = "Link"; + internal const string subType = "SubType"; + internal const string executableExtension = "ExecutableExtension"; + internal const string embedInteropTypes = "EmbedInteropTypes"; + internal const string frameworkReferenceName = "FrameworkReferenceName"; + internal const string assemblyName = "AssemblyName"; + internal const string assemblyVersion = "AssemblyVersion"; + internal const string publicKeyToken = "PublicKeyToken"; + internal const string culture = "Culture"; + internal const string withCulture = "WithCulture"; + internal const string copyToOutputDirectory = "CopyToOutputDirectory"; + internal const string copyAlways = "Always"; + internal const string managed = "Managed"; + + /// + /// The output path for a given item. + /// + internal const string targetPath = "TargetPath"; + internal const string dependentUpon = "DependentUpon"; + internal const string msbuildSourceProjectFile = "MSBuildSourceProjectFile"; + internal const string msbuildSourceTargetName = "MSBuildSourceTargetName"; + internal const string isPrimary = "IsPrimary"; + internal const string targetFramework = "RequiredTargetFramework"; + internal const string frameworkDirectory = "FrameworkDirectory"; + internal const string version = "Version"; + internal const string imageRuntime = "ImageRuntime"; + internal const string winMDFile = "WinMDFile"; + internal const string winMDFileType = "WinMDFileType"; + internal const string msbuildReferenceSourceTarget = "ReferenceSourceTarget"; + internal const string msbuildReferenceGrouping = "ReferenceGrouping"; + internal const string msbuildReferenceGroupingDisplayName = "ReferenceGroupingDisplayName"; + internal const string msbuildReferenceFromSDK = "ReferenceFromSDK"; + internal const string winmdImplmentationFile = "Implementation"; + internal const string projectReferenceOriginalItemSpec = "ProjectReferenceOriginalItemSpec"; + internal const string IgnoreVersionForFrameworkReference = "IgnoreVersionForFrameworkReference"; + internal const string frameworkFile = "FrameworkFile"; + internal const string ProjectReferenceTargetsMetadataName = "Targets"; + internal const string PropertiesMetadataName = "Properties"; + internal const string UndefinePropertiesMetadataName = "UndefineProperties"; + internal const string AdditionalPropertiesMetadataName = "AdditionalProperties"; + internal const string ProjectConfigurationDescription = "ProjectConfigurationDescription"; + } + + /// + /// Constants naming well-known items. + /// + internal static class ItemNames + { + internal const string Compile = "Compile"; + internal const string Content = "Content"; + internal const string EmbeddedResource = "EmbeddedResource"; + internal const string None = "None"; + internal const string Reference = "Reference"; + internal const string ProjectCapability = "ProjectCapability"; + } +} diff --git a/src/MSBuildTaskHost/CopyOnWriteDictionary.cs b/src/MSBuildTaskHost/CopyOnWriteDictionary.cs new file mode 100644 index 00000000000..b0948ed17a2 --- /dev/null +++ b/src/MSBuildTaskHost/CopyOnWriteDictionary.cs @@ -0,0 +1,413 @@ +// 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; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.Serialization; + +namespace Microsoft.Build.Collections +{ + /// + /// A dictionary that has copy-on-write semantics. + /// KEYS AND VALUES MUST BE IMMUTABLE OR COPY-ON-WRITE FOR THIS TO WORK. + /// + /// The value type. + /// + /// Thread safety: for all users, this class is as thread safe as the underlying Dictionary implementation, that is, + /// safe for concurrent readers or one writer from EACH user. It achieves this by locking itself and cloning before + /// any write, if it is being shared - i.e., stopping sharing before any writes occur. + /// + /// + /// This class must be serializable as it is used for metadata passed to tasks, which may + /// be run in a separate appdomain. + /// + [Serializable] + internal class CopyOnWriteDictionary : IDictionary, IDictionary, ISerializable + { +#if !NET35 // MSBuildNameIgnoreCaseComparer not compiled into MSBuildTaskHost but also allocations not interesting there. + /// + /// Empty dictionary with a , + /// used as the basis of new dictionaries with that comparer to avoid + /// allocating new comparers objects. + /// + private static readonly ImmutableDictionary NameComparerDictionaryPrototype = ImmutableDictionary.Create(MSBuildNameIgnoreCaseComparer.Default); + + /// + /// Empty dictionary with , + /// used as the basis of new dictionaries with that comparer to avoid + /// allocating new comparers objects. + /// + private static readonly ImmutableDictionary OrdinalIgnoreCaseComparerDictionaryPrototype = ImmutableDictionary.Create(StringComparer.OrdinalIgnoreCase); +#endif + + + /// + /// The backing dictionary. + /// Lazily created. + /// + private ImmutableDictionary _backing; + + /// + /// Constructor. Consider supplying a comparer instead. + /// + internal CopyOnWriteDictionary() + { + _backing = ImmutableDictionary.Empty; + } + + /// + /// Constructor taking a specified comparer for the keys + /// + internal CopyOnWriteDictionary(IEqualityComparer? keyComparer) + { + _backing = GetInitialDictionary(keyComparer); + } + + /// + /// Serialization constructor, for crossing appdomain boundaries + /// + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "context", Justification = "Not needed")] + protected CopyOnWriteDictionary(SerializationInfo info, StreamingContext context) + { + object v = info.GetValue(nameof(_backing), typeof(KeyValuePair[]))!; + + object comparer = info.GetValue(nameof(Comparer), typeof(IEqualityComparer))!; + + var b = GetInitialDictionary((IEqualityComparer?)comparer); + + _backing = b.AddRange((KeyValuePair[])v); + } + + private static ImmutableDictionary GetInitialDictionary(IEqualityComparer? keyComparer) + { +#if NET35 + return ImmutableDictionary.Create(keyComparer); +#else + return keyComparer is MSBuildNameIgnoreCaseComparer + ? NameComparerDictionaryPrototype + : keyComparer == StringComparer.OrdinalIgnoreCase + ? OrdinalIgnoreCaseComparerDictionaryPrototype + : ImmutableDictionary.Create(keyComparer); +#endif + } + + /// + /// Cloning constructor. Defers the actual clone. + /// + private CopyOnWriteDictionary(CopyOnWriteDictionary that) + { + _backing = that._backing; + } + + public CopyOnWriteDictionary(IDictionary dictionary) + { + _backing = dictionary.GetType() == typeof(ImmutableDictionary) + ? (ImmutableDictionary)dictionary + : dictionary.ToImmutableDictionary(); + } + + /// + /// Returns the collection of keys in the dictionary. + /// + public ICollection Keys => ((IDictionary)_backing).Keys; + + /// + /// Returns the collection of values in the dictionary. + /// + public ICollection Values => ((IDictionary)_backing).Values; + + /// + /// Returns the number of items in the collection. + /// + public int Count => _backing.Count; + + /// + /// Returns true if the collection is read-only. + /// + public bool IsReadOnly => ((IDictionary)_backing).IsReadOnly; + + /// + /// IDictionary implementation + /// + bool IDictionary.IsFixedSize => false; + + /// + /// IDictionary implementation + /// + bool IDictionary.IsReadOnly => IsReadOnly; + + /// + /// IDictionary implementation + /// + ICollection IDictionary.Keys => (ICollection)Keys; + + /// + /// IDictionary implementation + /// + ICollection IDictionary.Values => (ICollection)Values; + + /// + /// IDictionary implementation + /// + int ICollection.Count => Count; + + /// + /// IDictionary implementation + /// + bool ICollection.IsSynchronized => false; + + /// + /// IDictionary implementation + /// + object ICollection.SyncRoot => this; + + /// + /// Comparer used for keys + /// + internal IEqualityComparer Comparer + { + get => _backing.KeyComparer; + private set => _backing = _backing.WithComparers(keyComparer: value); + } + + /// + /// The backing copy-on-write dictionary, safe to reuse. + /// + internal ImmutableDictionary BackingDictionary => _backing; + + /// + /// Accesses the value for the specified key. + /// + public V this[string key] + { + get => _backing[key]; + + set + { + _backing = _backing.SetItem(key, value); + } + } + + /// + /// IDictionary implementation + /// + object? IDictionary.this[object key] + { + get + { + TryGetValue((string)key, out V? val); + return val; + } +#nullable disable + set => this[(string)key] = (V)value; +#nullable enable + } + + /// + /// Adds a value to the dictionary. + /// + public void Add(string key, V value) + { + _backing = _backing.SetItem(key, value); + } + + /// + /// Adds several value to the dictionary. + /// + public void SetItems(IEnumerable> items) + { + _backing = _backing.SetItems(items); + } + + public IEnumerable> Where(Func, bool> predicate) + { + return _backing.Where(predicate); + } + /// + /// Returns true if the dictionary contains the specified key. + /// + public bool ContainsKey(string key) + { + return _backing.ContainsKey(key); + } + + /// + /// Removes the entry for the specified key from the dictionary. + /// + public bool Remove(string key) + { + ImmutableDictionary initial = _backing; + + _backing = _backing.Remove(key); + + return initial != _backing; // whether the removal occured + } + +#nullable disable + /// + /// Attempts to find the value for the specified key in the dictionary. + /// + public bool TryGetValue(string key, out V value) + { + return _backing.TryGetValue(key, out value); + } +#nullable restore + + /// + /// Adds an item to the collection. + /// + public void Add(KeyValuePair item) + { + _backing = _backing.SetItem(item.Key, item.Value); + } + + /// + /// Clears the collection. + /// + public void Clear() + { + _backing = _backing.Clear(); + } + + /// + /// Returns true ff the collection contains the specified item. + /// + public bool Contains(KeyValuePair item) + { + return _backing.Contains(item); + } + + /// + /// Copies all of the elements of the collection to the specified array. + /// + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((IDictionary)_backing).CopyTo(array, arrayIndex); + } + + /// + /// Remove an item from the dictionary. + /// + public bool Remove(KeyValuePair item) + { + ImmutableDictionary initial = _backing; + + _backing = _backing.Remove(item.Key); + + return initial != _backing; // whether the removal occured + } + +#if NET472_OR_GREATER || NETCOREAPP + /// + /// Implementation of generic IEnumerable.GetEnumerator() + /// + public ImmutableDictionary.Enumerator GetEnumerator() + { + return _backing.GetEnumerator(); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + ImmutableDictionary.Enumerator enumerator = _backing.GetEnumerator(); + return _backing.GetEnumerator(); + } +#else + public IEnumerator> GetEnumerator() + { + return _backing.GetEnumerator(); + } +#endif + + /// + /// Implementation of IEnumerable.GetEnumerator() + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable>)this).GetEnumerator(); + } + +#nullable disable + /// + /// IDictionary implementation. + /// + void IDictionary.Add(object key, object value) + { + Add((string)key, (V)value); + } +#nullable enable + + /// + /// IDictionary implementation. + /// + void IDictionary.Clear() + { + Clear(); + } + + /// + /// IDictionary implementation. + /// + bool IDictionary.Contains(object key) + { + return ContainsKey((string)key); + } + + /// + /// IDictionary implementation. + /// + IDictionaryEnumerator IDictionary.GetEnumerator() + { + return ((IDictionary)_backing).GetEnumerator(); + } + + /// + /// IDictionary implementation. + /// + void IDictionary.Remove(object key) + { + Remove((string)key); + } + + /// + /// IDictionary implementation. + /// + void ICollection.CopyTo(Array array, int index) + { + int i = 0; + foreach (KeyValuePair entry in this) + { + array.SetValue(new DictionaryEntry(entry.Key, entry.Value), index + i); + i++; + } + } + + /// + /// Clone, with the actual clone deferred + /// + internal CopyOnWriteDictionary Clone() + { + return new CopyOnWriteDictionary(this); + } + + /// + /// Returns true if these dictionaries have the same backing. + /// + internal bool HasSameBacking(CopyOnWriteDictionary other) + { + return ReferenceEquals(other._backing, _backing); + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + ImmutableDictionary snapshot = _backing; + KeyValuePair[] array = snapshot.ToArray(); + + info.AddValue(nameof(_backing), array); + info.AddValue(nameof(Comparer), Comparer); + } + } +} diff --git a/src/MSBuildTaskHost/EnvironmentUtilities.cs b/src/MSBuildTaskHost/EnvironmentUtilities.cs new file mode 100644 index 00000000000..b64e792b53d --- /dev/null +++ b/src/MSBuildTaskHost/EnvironmentUtilities.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Microsoft.Build.Shared +{ + internal static partial class EnvironmentUtilities + { +#if NET472_OR_GREATER || NETCOREAPP + public static bool Is64BitProcess => Marshal.SizeOf() == 8; + + public static bool Is64BitOperatingSystem => + Environment.Is64BitOperatingSystem; +#endif + +#if !NETCOREAPP + private static volatile int s_processId; + private static volatile string? s_processPath; +#endif + private static volatile string? s_processName; + + /// Gets the unique identifier for the current process. + public static int CurrentProcessId + { + get + { +#if NETCOREAPP + return Environment.ProcessId; +#else + // copied from Environment.ProcessId + int processId = s_processId; + if (processId == 0) + { + using Process currentProcess = Process.GetCurrentProcess(); + s_processId = processId = currentProcess.Id; + + // Assume that process Id zero is invalid for user processes. It holds for all mainstream operating systems. + Debug.Assert(processId != 0); + } + + return processId; +#endif + } + } + + /// + /// Returns the path of the executable that started the currently executing process. Returns null when the path is not available. + /// + /// Path of the executable that started the currently executing process + /// + /// If the executable is renamed or deleted before this property is first accessed, the return value is undefined and depends on the operating system. + /// + public static string? ProcessPath + { + get + { +#if NETCOREAPP + return Environment.ProcessPath; +#else + // copied from Environment.ProcessPath + string? processPath = s_processPath; + if (processPath == null) + { + // The value is cached both as a performance optimization and to ensure that the API always returns + // the same path in a given process. + using Process currentProcess = Process.GetCurrentProcess(); + Interlocked.CompareExchange(ref s_processPath, currentProcess.MainModule.FileName ?? "", null); + processPath = s_processPath; + Debug.Assert(processPath != null); + } + + return (processPath?.Length != 0) ? processPath : null; +#endif + } + } + + public static string ProcessName + { + get + { + string? processName = s_processName; + if (processName == null) + { + using Process currentProcess = Process.GetCurrentProcess(); + Interlocked.CompareExchange(ref s_processName, currentProcess.ProcessName, null); + processName = s_processName; + } + + return processName; + } + } + + public static bool IsWellKnownEnvironmentDerivedProperty(string propertyName) + { + return propertyName.StartsWith("MSBUILD", StringComparison.OrdinalIgnoreCase) || + propertyName.StartsWith("COMPLUS_", StringComparison.OrdinalIgnoreCase) || + propertyName.StartsWith("DOTNET_", StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/MSBuildTaskHost/ErrorUtilities.cs b/src/MSBuildTaskHost/ErrorUtilities.cs new file mode 100644 index 00000000000..ed4a38b1650 --- /dev/null +++ b/src/MSBuildTaskHost/ErrorUtilities.cs @@ -0,0 +1,651 @@ +// 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; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading; +using Microsoft.Build.Framework; + +#if BUILDINGAPPXTASKS +namespace Microsoft.Build.AppxPackage.Shared +#else +namespace Microsoft.Build.Shared +#endif +{ + /// + /// This class contains methods that are useful for error checking and validation. + /// + internal static class ErrorUtilities + { + private static readonly bool s_enableMSBuildDebugTracing = !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDENABLEDEBUGTRACING")); + + public static void DebugTraceMessage(string category, string formatstring, params object[]? parameters) + { + if (s_enableMSBuildDebugTracing) + { + if (parameters != null) + { + Trace.WriteLine(String.Format(CultureInfo.CurrentCulture, formatstring, parameters), category); + } + else + { + Trace.WriteLine(formatstring, category); + } + } + } + +#if !BUILDINGAPPXTASKS + + internal static void VerifyThrowInternalError([DoesNotReturnIf(false)] bool condition, string message, params object?[]? args) + { + if (!condition) + { + ThrowInternalError(message, args); + } + } + + /// + /// Throws InternalErrorException. + /// This is only for situations that would mean that there is a bug in MSBuild itself. + /// + [DoesNotReturn] + internal static void ThrowInternalError(string message, params object?[]? args) + { + throw new InternalErrorException(ResourceUtilities.FormatString(message, args)); + } + + /// + /// Throws InternalErrorException. + /// This is only for situations that would mean that there is a bug in MSBuild itself. + /// + [DoesNotReturn] + internal static void ThrowInternalError(string message, Exception? innerException, params object?[]? args) + { + throw new InternalErrorException(ResourceUtilities.FormatString(message, args), innerException); + } + + /// + /// Throws InternalErrorException. + /// Indicates the code path followed should not have been possible. + /// This is only for situations that would mean that there is a bug in MSBuild itself. + /// + [DoesNotReturn] + internal static void ThrowInternalErrorUnreachable() + { + throw new InternalErrorException("Unreachable?"); + } + + /// + /// Throws InternalErrorException. + /// Indicates the code path followed should not have been possible. + /// This is only for situations that would mean that there is a bug in MSBuild itself. + /// + internal static void VerifyThrowInternalErrorUnreachable([DoesNotReturnIf(false)] bool condition) + { + if (!condition) + { + ThrowInternalErrorUnreachable(); + } + } + + /// + /// Throws InternalErrorException. + /// Indicates the code path followed should not have been possible. + /// This is only for situations that would mean that there is a bug in MSBuild itself. + /// + internal static void ThrowIfTypeDoesNotImplementToString(object param) + { +#if DEBUG + // Check it has a real implementation of ToString() + if (String.Equals(param.GetType().ToString(), param.ToString(), StringComparison.Ordinal)) + { + ThrowInternalError("This type does not implement ToString() properly {0}", param.GetType().FullName!); + } +#endif + } + + /// + /// Helper to throw an InternalErrorException when the specified parameter is null. + /// This should be used ONLY if this would indicate a bug in MSBuild rather than + /// anything caused by user action. + /// + /// The value of the argument. + /// Parameter that should not be null + internal static void VerifyThrowInternalNull([NotNull] object? parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) + { + if (parameter is null) + { + ThrowInternalError("{0} unexpectedly null", parameterName); + } + } + + /// + /// Helper to throw an InternalErrorException when a lock on the specified object is not already held. + /// This should be used ONLY if this would indicate a bug in MSBuild rather than + /// anything caused by user action. + /// + /// The object that should already have been used as a lock. + internal static void VerifyThrowInternalLockHeld(object locker) + { +#if !CLR2COMPATIBILITY + if (!Monitor.IsEntered(locker)) + { + ThrowInternalError("Lock should already have been taken"); + } +#endif + } + + /// + /// Helper to throw an InternalErrorException when the specified parameter is null or zero length. + /// This should be used ONLY if this would indicate a bug in MSBuild rather than + /// anything caused by user action. + /// + /// The value of the argument. + /// Parameter that should not be null or zero length + internal static void VerifyThrowInternalLength([NotNull] string? parameterValue, [CallerArgumentExpression(nameof(parameterValue))] string? parameterName = null) + { + VerifyThrowInternalNull(parameterValue, parameterName); + + if (parameterValue.Length == 0) + { + ThrowInternalError("{0} unexpectedly empty", parameterName); + } + } + + public static void VerifyThrowInternalLength([NotNull] T[]? parameterValue, [CallerArgumentExpression(nameof(parameterValue))] string? parameterName = null) + { + VerifyThrowInternalNull(parameterValue, parameterName); + + if (parameterValue.Length == 0) + { + ThrowInternalError("{0} unexpectedly empty", parameterName); + } + } + + /// + /// Helper to throw an InternalErrorException when the specified parameter is not a rooted path. + /// This should be used ONLY if this would indicate a bug in MSBuild rather than + /// anything caused by user action. + /// + /// Parameter that should be a rooted path. + internal static void VerifyThrowInternalRooted(string value) + { + if (!Path.IsPathRooted(value)) + { + ThrowInternalError("{0} unexpectedly not a rooted path", value); + } + } + + /// + /// This method should be used in places where one would normally put + /// an "assert". It should be used to validate that our assumptions are + /// true, where false would indicate that there must be a bug in our + /// code somewhere. This should not be used to throw errors based on bad + /// user input or anything that the user did wrong. + /// + internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string unformattedMessage) + { + if (!condition) + { + ThrowInternalError(unformattedMessage, null, null); + } + } + + /// + /// Overload for one string format argument. + /// + internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string unformattedMessage, int arg0) + { + if (!condition) + { + ThrowInternalError(unformattedMessage, arg0); + } + } + + /// + /// Overload for one string format argument. + /// + internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string unformattedMessage, object arg0) + { + if (!condition) + { + ThrowInternalError(unformattedMessage, arg0); + } + } + + /// + /// Overload for two string format arguments. + /// + internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string unformattedMessage, int arg0, int arg1) + { + if (!condition) + { + ThrowInternalError(unformattedMessage, arg0, arg1); + } + } + + /// + /// Overload for two string format arguments. + /// + internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string unformattedMessage, object arg0, object arg1) + { + if (!condition) + { + ThrowInternalError(unformattedMessage, arg0, arg1); + } + } + + /// + /// Overload for three string format arguments. + /// + internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string unformattedMessage, object arg0, object arg1, object arg2) + { + if (!condition) + { + ThrowInternalError(unformattedMessage, arg0, arg1, arg2); + } + } + + /// + /// Overload for four string format arguments. + /// + internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string unformattedMessage, object arg0, object arg1, object arg2, object arg3) + { + if (!condition) + { + ThrowInternalError(unformattedMessage, arg0, arg1, arg2, arg3); + } + } + + /// + /// Throws an InvalidOperationException with the specified resource string + /// + /// Resource to use in the exception + /// Formatting args. + [DoesNotReturn] + internal static void ThrowInvalidOperation(string resourceName, params object?[]? args) + { + throw new InvalidOperationException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword(resourceName, args)); + } + + /// + /// Throws an InvalidOperationException if the given condition is false. + /// + internal static void VerifyThrowInvalidOperation([DoesNotReturnIf(false)] bool condition, string resourceName) + { + ResourceUtilities.VerifyResourceStringExists(resourceName); + if (!condition) + { + ThrowInvalidOperation(resourceName, null); + } + } + + /// + /// Overload for one string format argument. + /// + internal static void VerifyThrowInvalidOperation([DoesNotReturnIf(false)] bool condition, string resourceName, object arg0) + { + ResourceUtilities.VerifyResourceStringExists(resourceName); + // PERF NOTE: check the condition here instead of pushing it into + // the ThrowInvalidOperation() method, because that method always + // allocates memory for its variable array of arguments + if (!condition) + { + ThrowInvalidOperation(resourceName, arg0); + } + } + + /// + /// Overload for two string format arguments. + /// + internal static void VerifyThrowInvalidOperation([DoesNotReturnIf(false)] bool condition, string resourceName, object arg0, object arg1) + { + ResourceUtilities.VerifyResourceStringExists(resourceName); + // PERF NOTE: check the condition here instead of pushing it into + // the ThrowInvalidOperation() method, because that method always + // allocates memory for its variable array of arguments + if (!condition) + { + ThrowInvalidOperation(resourceName, arg0, arg1); + } + } + + /// + /// Overload for three string format arguments. + /// + internal static void VerifyThrowInvalidOperation([DoesNotReturnIf(false)] bool condition, string resourceName, object arg0, object arg1, object arg2) + { + ResourceUtilities.VerifyResourceStringExists(resourceName); + // PERF NOTE: check the condition here instead of pushing it into + // the ThrowInvalidOperation() method, because that method always + // allocates memory for its variable array of arguments + if (!condition) + { + ThrowInvalidOperation(resourceName, arg0, arg1, arg2); + } + } + + /// + /// Overload for four string format arguments. + /// + internal static void VerifyThrowInvalidOperation([DoesNotReturnIf(false)] bool condition, string resourceName, object arg0, object arg1, object arg2, object arg3) + { + ResourceUtilities.VerifyResourceStringExists(resourceName); + + // PERF NOTE: check the condition here instead of pushing it into + // the ThrowInvalidOperation() method, because that method always + // allocates memory for its variable array of arguments + if (!condition) + { + ThrowInvalidOperation(resourceName, arg0, arg1, arg2, arg3); + } + } + + /// + /// Throws an ArgumentException that can include an inner exception. + /// + /// PERF WARNING: calling a method that takes a variable number of arguments + /// is expensive, because memory is allocated for the array of arguments -- do + /// not call this method repeatedly in performance-critical scenarios + /// + [DoesNotReturn] + internal static void ThrowArgument(string resourceName, params object?[]? args) + { + ThrowArgument(null, resourceName, args); + } + + /// + /// Throws an ArgumentException that can include an inner exception. + /// + /// PERF WARNING: calling a method that takes a variable number of arguments + /// is expensive, because memory is allocated for the array of arguments -- do + /// not call this method repeatedly in performance-critical scenarios + /// + /// + /// This method is thread-safe. + /// + /// Can be null. + /// + /// + [DoesNotReturn] + internal static void ThrowArgument(Exception? innerException, string resourceName, params object?[]? args) + { + throw new ArgumentException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword(resourceName, args), innerException); + } + + /// + /// Throws an ArgumentException if the given condition is false. + /// + internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, string resourceName) + { + VerifyThrowArgument(condition, null, resourceName); + } + + /// + /// Overload for one string format argument. + /// + internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, string resourceName, object arg0) + { + VerifyThrowArgument(condition, null, resourceName, arg0); + } + + /// + /// Overload for two string format arguments. + /// + internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, string resourceName, object arg0, object arg1) + { + VerifyThrowArgument(condition, null, resourceName, arg0, arg1); + } + + /// + /// Overload for three string format arguments. + /// + internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, string resourceName, object arg0, object arg1, object arg2) + { + VerifyThrowArgument(condition, null, resourceName, arg0, arg1, arg2); + } + + /// + /// Overload for four string format arguments. + /// + internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, string resourceName, object arg0, object arg1, object arg2, object arg3) + { + VerifyThrowArgument(condition, null, resourceName, arg0, arg1, arg2, arg3); + } + + /// + /// Throws an ArgumentException that includes an inner exception, if + /// the given condition is false. + /// + /// + /// Can be null. + /// + internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, Exception? innerException, string resourceName) + { + ResourceUtilities.VerifyResourceStringExists(resourceName); + if (!condition) + { + ThrowArgument(innerException, resourceName, null); + } + } + + /// + /// Overload for one string format argument. + /// + internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, Exception? innerException, string resourceName, object arg0) + { + ResourceUtilities.VerifyResourceStringExists(resourceName); + + if (!condition) + { + ThrowArgument(innerException, resourceName, arg0); + } + } + + /// + /// Overload for two string format arguments. + /// + internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, Exception? innerException, string resourceName, object arg0, object arg1) + { + ResourceUtilities.VerifyResourceStringExists(resourceName); + + if (!condition) + { + ThrowArgument(innerException, resourceName, arg0, arg1); + } + } + + /// + /// Overload for three string format arguments. + /// + internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, Exception? innerException, string resourceName, object arg0, object arg1, object arg2) + { + ResourceUtilities.VerifyResourceStringExists(resourceName); + + if (!condition) + { + ThrowArgument(innerException, resourceName, arg0, arg1, arg2); + } + } + + /// + /// Overload for four string format arguments. + /// + internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, Exception? innerException, string resourceName, object arg0, object arg1, object arg2, object arg3) + { + ResourceUtilities.VerifyResourceStringExists(resourceName); + + if (!condition) + { + ThrowArgument(innerException, resourceName, arg0, arg1, arg2, arg3); + } + } + + /// + /// Throws an argument out of range exception. + /// + [DoesNotReturn] + internal static void ThrowArgumentOutOfRange(string? parameterName) + { + throw new ArgumentOutOfRangeException(parameterName); + } + + /// + /// Throws an ArgumentOutOfRangeException using the given parameter name + /// if the condition is false. + /// + internal static void VerifyThrowArgumentOutOfRange([DoesNotReturnIf(false)] bool condition, [CallerArgumentExpression(nameof(condition))] string? parameterName = null) + { + if (!condition) + { + ThrowArgumentOutOfRange(parameterName); + } + } + + /// + /// Throws an ArgumentNullException if the given string parameter is null + /// and ArgumentException if it has zero length. + /// + internal static void VerifyThrowArgumentLength([NotNull] string? parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) + { + VerifyThrowArgumentNull(parameter, parameterName); + + if (parameter.Length == 0) + { + ThrowArgumentLength(parameterName); + } + } + +#if !CLR2COMPATIBILITY + /// + /// Throws an ArgumentNullException if the given collection is null + /// and ArgumentException if it has zero length. + /// + internal static void VerifyThrowArgumentLength([NotNull] IReadOnlyCollection parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) + { + VerifyThrowArgumentNull(parameter, parameterName); + + if (parameter.Count == 0) + { + ThrowArgumentLength(parameterName); + } + } + + /// + /// Throws an ArgumentException if the given collection is not null but of zero length. + /// + internal static void VerifyThrowArgumentLengthIfNotNull([MaybeNull] IReadOnlyCollection? parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) + { + if (parameter?.Count == 0) + { + ThrowArgumentLength(parameterName); + } + } +#endif + + [DoesNotReturn] + private static void ThrowArgumentLength(string? parameterName) + { + throw new ArgumentException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("Shared.ParameterCannotHaveZeroLength", parameterName)); + } + + /// + /// Throws an ArgumentNullException if the given string parameter is null + /// and ArgumentException if it has zero length. + /// + internal static void VerifyThrowArgumentInvalidPath([NotNull] string parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) + { + VerifyThrowArgumentNull(parameter, parameterName); + + if (FileUtilities.PathIsInvalid(parameter)) + { + ThrowArgument("Shared.ParameterCannotHaveInvalidPathChars", parameterName, parameter); + } + } + + /// + /// Throws an ArgumentException if the string has zero length, unless it is + /// null, in which case no exception is thrown. + /// + internal static void VerifyThrowArgumentLengthIfNotNull(string? parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) + { + if (parameter?.Length == 0) + { + ThrowArgumentLength(parameterName); + } + } + + /// + /// Throws an ArgumentNullException if the given parameter is null. + /// + internal static void VerifyThrowArgumentNull([NotNull] object? parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) + { + VerifyThrowArgumentNull(parameter, parameterName, "Shared.ParameterCannotBeNull"); + } + + /// + /// Throws an ArgumentNullException if the given parameter is null. + /// + internal static void VerifyThrowArgumentNull([NotNull] object? parameter, string? parameterName, string resourceName) + { + ResourceUtilities.VerifyResourceStringExists(resourceName); + if (parameter is null) + { + ThrowArgumentNull(parameterName, resourceName); + } + } + + [DoesNotReturn] + internal static void ThrowArgumentNull(string? parameterName, string resourceName) + { + // Most ArgumentNullException overloads append its own rather clunky multi-line message. So use the one overload that doesn't. + throw new ArgumentNullException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword(resourceName, parameterName), (Exception?)null); + } + + internal static void VerifyThrowObjectDisposed([DoesNotReturnIf(false)] bool condition, string objectName) + { + if (!condition) + { + ThrowObjectDisposed(objectName); + } + } + + [DoesNotReturn] + internal static void ThrowObjectDisposed(string objectName) + { + throw new ObjectDisposedException(objectName); + } + + /// + /// A utility that verifies the parameters provided to a standard ICollection.CopyTo call. + /// + /// If is null. + /// If falls outside of the bounds . + /// If there is insufficient capacity to copy the collection contents into + /// when starting at . + internal static void VerifyCollectionCopyToArguments( + [NotNull] T[]? array, + string arrayParameterName, + int arrayIndex, + string arrayIndexParameterName, + int requiredCapacity) + { + VerifyThrowArgumentNull(array, arrayParameterName); + VerifyThrowArgumentOutOfRange(arrayIndex >= 0 && arrayIndex < array.Length, arrayIndexParameterName); + + int arrayCapacity = array.Length - arrayIndex; + if (requiredCapacity > arrayCapacity) + { + throw new ArgumentException( + ResourceUtilities.GetResourceString("Shared.CollectionCopyToFailureProvidedArrayIsTooSmall"), + arrayParameterName); + } + } +#endif + } +} diff --git a/src/MSBuildTaskHost/EscapingUtilities.cs b/src/MSBuildTaskHost/EscapingUtilities.cs new file mode 100644 index 00000000000..8bde0027840 --- /dev/null +++ b/src/MSBuildTaskHost/EscapingUtilities.cs @@ -0,0 +1,312 @@ +// 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; +using System.Text; + +using Microsoft.Build.Framework; +using Microsoft.NET.StringTools; + +#nullable disable + +namespace Microsoft.Build.Shared +{ + /// + /// This class implements static methods to assist with unescaping of %XX codes + /// in the MSBuild file format. + /// + /// + /// PERF: since we escape and unescape relatively frequently, it may be worth caching + /// the last N strings that were (un)escaped + /// + internal static class EscapingUtilities + { + /// + /// Optional cache of escaped strings for use when needing to escape in performance-critical scenarios with significant + /// expected string reuse. + /// + private static readonly Dictionary s_unescapedToEscapedStrings = new Dictionary(StringComparer.Ordinal); + + private static bool TryDecodeHexDigit(char character, out int value) + { + if (character >= '0' && character <= '9') + { + value = character - '0'; + return true; + } + if (character >= 'A' && character <= 'F') + { + value = character - 'A' + 10; + return true; + } + if (character >= 'a' && character <= 'f') + { + value = character - 'a' + 10; + return true; + } + value = default; + return false; + } + + /// + /// Replaces all instances of %XX in the input string with the character represented + /// by the hexadecimal number XX. + /// + /// The string to unescape. + /// If the string should be trimmed before being unescaped. + /// unescaped string + internal static string UnescapeAll(string escapedString, bool trim = false) + { + // If the string doesn't contain anything, then by definition it doesn't + // need unescaping. + if (String.IsNullOrEmpty(escapedString)) + { + return escapedString; + } + + // If there are no percent signs, just return the original string immediately. + // Don't even instantiate the StringBuilder. + int indexOfPercent = escapedString.IndexOf('%'); + if (indexOfPercent == -1) + { + return trim ? escapedString.Trim() : escapedString; + } + + // This is where we're going to build up the final string to return to the caller. + StringBuilder unescapedString = StringBuilderCache.Acquire(escapedString.Length); + + int currentPosition = 0; + int escapedStringLength = escapedString.Length; + if (trim) + { + while (currentPosition < escapedString.Length && Char.IsWhiteSpace(escapedString[currentPosition])) + { + currentPosition++; + } + if (currentPosition == escapedString.Length) + { + return String.Empty; + } + while (Char.IsWhiteSpace(escapedString[escapedStringLength - 1])) + { + escapedStringLength--; + } + } + + // Loop until there are no more percent signs in the input string. + while (indexOfPercent != -1) + { + // There must be two hex characters following the percent sign + // for us to even consider doing anything with this. + if ( + (indexOfPercent <= (escapedStringLength - 3)) && + TryDecodeHexDigit(escapedString[indexOfPercent + 1], out int digit1) && + TryDecodeHexDigit(escapedString[indexOfPercent + 2], out int digit2)) + { + // First copy all the characters up to the current percent sign into + // the destination. + unescapedString.Append(escapedString, currentPosition, indexOfPercent - currentPosition); + + // Convert the %XX to an actual real character. + char unescapedCharacter = (char)((digit1 << 4) + digit2); + + // if the unescaped character is not on the exception list, append it + unescapedString.Append(unescapedCharacter); + + // Advance the current pointer to reflect the fact that the destination string + // is up to date with everything up to and including this escape code we just found. + currentPosition = indexOfPercent + 3; + } + + // Find the next percent sign. + indexOfPercent = escapedString.IndexOf('%', indexOfPercent + 1); + } + + // Okay, there are no more percent signs in the input string, so just copy the remaining + // characters into the destination. + unescapedString.Append(escapedString, currentPosition, escapedStringLength - currentPosition); + + return StringBuilderCache.GetStringAndRelease(unescapedString); + } + + + /// + /// Adds instances of %XX in the input string where the char to be escaped appears + /// XX is the hex value of the ASCII code for the char. Interns and caches the result. + /// + /// + /// NOTE: Only recommended for use in scenarios where there's expected to be significant + /// repetition of the escaped string. Cache currently grows unbounded. + /// + internal static string EscapeWithCaching(string unescapedString) + { + return EscapeWithOptionalCaching(unescapedString, cache: true); + } + + /// + /// Adds instances of %XX in the input string where the char to be escaped appears + /// XX is the hex value of the ASCII code for the char. + /// + /// The string to escape. + /// escaped string + internal static string Escape(string unescapedString) + { + return EscapeWithOptionalCaching(unescapedString, cache: false); + } + + /// + /// Adds instances of %XX in the input string where the char to be escaped appears + /// XX is the hex value of the ASCII code for the char. Caches if requested. + /// + /// The string to escape. + /// + /// True if the cache should be checked, and if the resultant string + /// should be cached. + /// + private static string EscapeWithOptionalCaching(string unescapedString, bool cache) + { + // If there are no special chars, just return the original string immediately. + // Don't even instantiate the StringBuilder. + if (String.IsNullOrEmpty(unescapedString) || !ContainsReservedCharacters(unescapedString)) + { + return unescapedString; + } + + // next, if we're caching, check to see if it's already there. + if (cache) + { + lock (s_unescapedToEscapedStrings) + { + string cachedEscapedString; + if (s_unescapedToEscapedStrings.TryGetValue(unescapedString, out cachedEscapedString)) + { + return cachedEscapedString; + } + } + } + + // This is where we're going to build up the final string to return to the caller. + StringBuilder escapedStringBuilder = StringBuilderCache.Acquire(unescapedString.Length * 2); + + AppendEscapedString(escapedStringBuilder, unescapedString); + + if (!cache) + { + return StringBuilderCache.GetStringAndRelease(escapedStringBuilder); + } + + string escapedString = Strings.WeakIntern(escapedStringBuilder.ToString()); + StringBuilderCache.Release(escapedStringBuilder); + + lock (s_unescapedToEscapedStrings) + { + s_unescapedToEscapedStrings[unescapedString] = escapedString; + } + + return escapedString; + } + + /// + /// Before trying to actually escape the string, it can be useful to call this method to determine + /// if escaping is necessary at all. This can save lots of calls to copy around item metadata + /// that is really the same whether escaped or not. + /// + /// + /// + private static bool ContainsReservedCharacters( + string unescapedString) + { + return -1 != unescapedString.IndexOfAny(s_charsToEscape); + } + + /// + /// Determines whether the string contains the escaped form of '*' or '?'. + /// + /// + /// + internal static bool ContainsEscapedWildcards(string escapedString) + { + if (escapedString.Length < 3) + { + return false; + } + // Look for the first %. We know that it has to be followed by at least two more characters so we subtract 2 + // from the length to search. + int index = escapedString.IndexOf('%', 0, escapedString.Length - 2); + while (index != -1) + { + if (escapedString[index + 1] == '2' && (escapedString[index + 2] == 'a' || escapedString[index + 2] == 'A')) + { + // %2a or %2A + return true; + } + if (escapedString[index + 1] == '3' && (escapedString[index + 2] == 'f' || escapedString[index + 2] == 'F')) + { + // %3f or %3F + return true; + } + // Continue searching for % starting at (index + 1). We know that it has to be followed by at least two + // more characters so we subtract 2 from the length of the substring to search. + index = escapedString.IndexOf('%', index + 1, escapedString.Length - (index + 1) - 2); + } + return false; + } + + /// + /// Convert the given integer into its hexadecimal representation. + /// + /// The number to convert, which must be non-negative and less than 16 + /// The character which is the hexadecimal representation of . + private static char HexDigitChar(int x) + { + return (char)(x + (x < 10 ? '0' : ('a' - 10))); + } + + /// + /// Append the escaped version of the given character to a . + /// + /// The to which to append. + /// The character to escape. + private static void AppendEscapedChar(StringBuilder sb, char ch) + { + // Append the escaped version which is a percent sign followed by two hexadecimal digits + sb.Append('%'); + sb.Append(HexDigitChar(ch / 0x10)); + sb.Append(HexDigitChar(ch & 0x0F)); + } + + /// + /// Append the escaped version of the given string to a . + /// + /// The to which to append. + /// The unescaped string. + private static void AppendEscapedString(StringBuilder sb, string unescapedString) + { + // Replace each unescaped special character with an escape sequence one + for (int idx = 0; ;) + { + int nextIdx = unescapedString.IndexOfAny(s_charsToEscape, idx); + if (nextIdx == -1) + { + sb.Append(unescapedString, idx, unescapedString.Length - idx); + break; + } + + sb.Append(unescapedString, idx, nextIdx - idx); + AppendEscapedChar(sb, unescapedString[nextIdx]); + idx = nextIdx + 1; + } + } + + /// + /// Special characters that need escaping. + /// It's VERY important that the percent character is the FIRST on the list - since it's both a character + /// we escape and use in escape sequences, we can unintentionally escape other escape sequences if we + /// don't process it first. Of course we'll have a similar problem if we ever decide to escape hex digits + /// (that would require rewriting the algorithm) but since it seems unlikely that we ever do, this should + /// be good enough to avoid complicating the algorithm at this point. + /// + private static readonly char[] s_charsToEscape = { '%', '*', '?', '@', '$', '(', ')', ';', '\'' }; + } +} diff --git a/src/MSBuildTaskHost/ExceptionHandling.cs b/src/MSBuildTaskHost/ExceptionHandling.cs new file mode 100644 index 00000000000..6ed43000558 --- /dev/null +++ b/src/MSBuildTaskHost/ExceptionHandling.cs @@ -0,0 +1,441 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +#if BUILDINGAPPXTASKS +namespace Microsoft.Build.AppxPackage.Shared +#else +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Text; +using System.Threading; +using System.Xml; +using Microsoft.Build.Shared.FileSystem; +using System.Xml.Schema; +using System.Runtime.Serialization; +#if !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS +using Microsoft.Build.Shared.Debugging; +#endif +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Shared +#endif +{ + /// + /// Utility methods for classifying and handling exceptions. + /// + internal static class ExceptionHandling + { + private static readonly string s_debugDumpPath = GetDebugDumpPath(); + + /// + /// Gets the location of the directory used for diagnostic log files. + /// + /// + private static string GetDebugDumpPath() + { + string debugPath = + + /* Unmerged change from project 'Microsoft.Build.Engine.OM.UnitTests (net7.0)' + Before: + // Cannot access change wave logic from these assemblies (https://github.com/dotnet/msbuild/issues/6707) + After: + // Cannot access change wave logic from these assemblies (https://github.com/dotnet/msbuild/issues/6707) + */ + /* Unmerged change from project 'Microsoft.Build.Engine.OM.UnitTests (net472)' + Before: + // Cannot access change wave logic from these assemblies (https://github.com/dotnet/msbuild/issues/6707) + After: + // Cannot access change wave logic from these assemblies (https://github.com/dotnet/msbuild/issues/6707) + */ + /* Unmerged change from project 'MSBuildTaskHost' + Before: + // Cannot access change wave logic from these assemblies (https://github.com/dotnet/msbuild/issues/6707) + After: + // Cannot access change wave logic from these assemblies (https://github.com/dotnet/msbuild/issues/6707) + */ + // Cannot access change wave logic from these assemblies (https://github.com/dotnet/msbuild/issues/6707) +#if CLR2COMPATIBILITY || MICROSOFT_BUILD_ENGINE_OM_UNITTESTS + Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); +#else + DebugUtils.DebugPath; +#endif + + return !string.IsNullOrEmpty(debugPath) + ? debugPath + : FileUtilities.TempFileDirectory; + } + + private static string s_debugDumpPathInRunningTests = GetDebugDumpPath(); + internal static bool ResetDebugDumpPathInRunningTests = false; + + /// + /// The directory used for diagnostic log files. + /// + internal static string DebugDumpPath + { + get + { + if (BuildEnvironmentHelper.Instance.RunningTests) + { + if (ResetDebugDumpPathInRunningTests) + { + s_debugDumpPathInRunningTests = GetDebugDumpPath(); + // reset dump file name so new one is created in new path + s_dumpFileName = null; + ResetDebugDumpPathInRunningTests = false; + } + + return s_debugDumpPathInRunningTests; + } + + return s_debugDumpPath; + } + } + + /// + /// The file used for diagnostic log files. + /// + internal static string DumpFilePath => s_dumpFileName; + +#if !BUILDINGAPPXTASKS + /// + /// The filename that exceptions will be dumped to + /// + private static string s_dumpFileName; +#endif + /// + /// If the given exception is "ignorable under some circumstances" return false. + /// Otherwise it's "really bad", and return true. + /// This makes it possible to catch(Exception ex) without catching disasters. + /// + /// The exception to check. + /// True if exception is critical. + internal static bool IsCriticalException(Exception e) + { + if (e is OutOfMemoryException + || e is StackOverflowException + || e is ThreadAbortException + || e is ThreadInterruptedException + || e is AccessViolationException +#if !TASKHOST + || e is CriticalTaskException +#endif +#if !BUILDINGAPPXTASKS + || e is InternalErrorException +#endif + ) + { + // Ideally we would include NullReferenceException, because it should only ever be thrown by CLR (use ArgumentNullException for arguments) + // but we should handle it if tasks and loggers throw it. + + // ExecutionEngineException has been deprecated by the CLR + return true; + } + +#if !CLR2COMPATIBILITY + // Check if any critical exceptions + var aggregateException = e as AggregateException; + + if (aggregateException != null) + { + // If the aggregate exception contains a critical exception it is considered a critical exception + if (aggregateException.InnerExceptions.Any(innerException => IsCriticalException(innerException))) + { + return true; + } + } +#endif + + return false; + } + + /// + /// If the given exception is file IO related or expected return false. + /// Otherwise, return true. + /// + /// The exception to check. + /// True if exception is not IO related or expected otherwise false. + internal static bool NotExpectedException(Exception e) + { + return !IsIoRelatedException(e); + } + + /// + /// Determine whether the exception is file-IO related. + /// + /// The exception to check. + /// True if exception is IO related. + internal static bool IsIoRelatedException(Exception e) + { + // These all derive from IOException + // DirectoryNotFoundException + // DriveNotFoundException + // EndOfStreamException + // FileLoadException + // FileNotFoundException + // PathTooLongException + // PipeException + return e is UnauthorizedAccessException + || e is NotSupportedException + || (e is ArgumentException && !(e is ArgumentNullException)) + || e is SecurityException + || e is IOException; + } + + /// Checks if the exception is an XML one. + /// Exception to check. + /// True if exception is related to XML parsing. + internal static bool IsXmlException(Exception e) + { + return e is XmlException +#if FEATURE_SECURITY_PERMISSIONS + || e is System.Security.XmlSyntaxException +#endif + || e is XmlSchemaException + || e is UriFormatException; // XmlTextReader for example uses this under the covers + } + + /// Extracts line and column numbers from the exception if it is XML-related one. + /// XML-related exception. + /// Line and column numbers if available, (0,0) if not. + /// This function works around the fact that XmlException and XmlSchemaException are not directly related. + internal static LineAndColumn GetXmlLineAndColumn(Exception e) + { + var line = 0; + var column = 0; + + var xmlException = e as XmlException; + if (xmlException != null) + { + line = xmlException.LineNumber; + column = xmlException.LinePosition; + } + else + { + var schemaException = e as XmlSchemaException; + if (schemaException != null) + { + line = schemaException.LineNumber; + column = schemaException.LinePosition; + } + } + + return new LineAndColumn + { + Line = line, + Column = column + }; + } + +#if !BUILDINGAPPXTASKS + + /// + /// If the given exception is file IO related or Xml related return false. + /// Otherwise, return true. + /// + /// The exception to check. + internal static bool NotExpectedIoOrXmlException(Exception e) + { + if + ( + IsXmlException(e) + || !NotExpectedException(e)) + { + return false; + } + + return true; + } + + /// + /// If the given exception is reflection-related return false. + /// Otherwise, return true. + /// + /// The exception to check. + internal static bool NotExpectedReflectionException(Exception e) + { + // We are explicitly not handling TargetInvocationException. Those are just wrappers around + // exceptions thrown by the called code (such as a task or logger) which callers will typically + // want to treat differently. + if + ( + e is TypeLoadException // thrown when the common language runtime cannot find the assembly, the type within the assembly, or cannot load the type + || e is MethodAccessException // thrown when a class member is not found or access to the member is not permitted + || e is MissingMethodException // thrown when code in a dependent assembly attempts to access a missing method in an assembly that was modified + || e is MemberAccessException // thrown when a class member is not found or access to the member is not permitted + || e is BadImageFormatException // thrown when the file image of a DLL or an executable program is invalid + || e is ReflectionTypeLoadException // thrown by the Module.GetTypes method if any of the classes in a module cannot be loaded + || e is TargetParameterCountException // thrown when the number of parameters for an invocation does not match the number expected + || e is InvalidCastException + || e is AmbiguousMatchException // thrown when binding to a member results in more than one member matching the binding criteria + || e is CustomAttributeFormatException // thrown if a custom attribute on a data type is formatted incorrectly + || e is InvalidFilterCriteriaException // thrown in FindMembers when the filter criteria is not valid for the type of filter you are using + || e is TargetException // thrown when an attempt is made to invoke a non-static method on a null object. This may occur because the caller does not + // have access to the member, or because the target does not define the member, and so on. + || e is MissingFieldException // thrown when code in a dependent assembly attempts to access a missing field in an assembly that was modified. + || !NotExpectedException(e)) // Reflection can throw IO exceptions if the assembly cannot be opened + { + return false; + } + + return true; + } + + /// + /// Serialization has been observed to throw TypeLoadException as + /// well as SerializationException and IO exceptions. (Obviously + /// it has to do reflection but it ought to be wrapping the exceptions.) + /// + internal static bool NotExpectedSerializationException(Exception e) + { + if + ( + e is SerializationException || + !NotExpectedReflectionException(e)) + { + return false; + } + + return true; + } + + /// + /// Returns false if this is a known exception thrown by the registry API. + /// + internal static bool NotExpectedRegistryException(Exception e) + { + if (e is SecurityException + || e is UnauthorizedAccessException + || e is IOException + || e is ObjectDisposedException + || e is ArgumentException) + { + return false; + } + + return true; + } + + /// + /// Returns false if this is a known exception thrown by function evaluation + /// + internal static bool NotExpectedFunctionException(Exception e) + { + if (e is InvalidCastException + || e is ArgumentNullException + || e is FormatException + || e is InvalidOperationException + || !NotExpectedReflectionException(e)) + { + return false; + } + + return true; + } + + /// + /// Dump any unhandled exceptions to a file so they can be diagnosed + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "It is called by the CLR")] + internal static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e) + { + Exception ex = (Exception)e.ExceptionObject; + DumpExceptionToFile(ex); + } + + /// + /// Dump the exception information to a file + /// + internal static void DumpExceptionToFile(Exception ex) + { + try + { + // Locking on a type is not recommended. However, we are doing it here to be extra cautious about compatibility because + // this method previously had a [MethodImpl(MethodImplOptions.Synchronized)] attribute, which does lock on the type when + // applied to a static method. + lock (typeof(ExceptionHandling)) + { + if (s_dumpFileName == null) + { + Guid guid = Guid.NewGuid(); + + // For some reason we get Watson buckets because GetTempPath gives us a folder here that doesn't exist. + // Either because %TMP% is misdefined, or because they deleted the temp folder during the build. + // If this throws, no sense catching it, we can't log it now, and we're here + // because we're a child node with no console to log to, so die + Directory.CreateDirectory(DebugDumpPath); + + var pid = EnvironmentUtilities.CurrentProcessId; + // This naming pattern is assumed in ReadAnyExceptionFromFile + s_dumpFileName = Path.Combine(DebugDumpPath, $"MSBuild_pid-{pid}_{guid:n}.failure.txt"); + + using (StreamWriter writer = FileUtilities.OpenWrite(s_dumpFileName, append: true)) + { + writer.WriteLine("UNHANDLED EXCEPTIONS FROM PROCESS {0}:", pid); + writer.WriteLine("====================="); + } + } + + using (StreamWriter writer = FileUtilities.OpenWrite(s_dumpFileName, append: true)) + { + // "G" format is, e.g., 6/15/2008 9:15:07 PM + writer.WriteLine(DateTime.Now.ToString("G", CultureInfo.CurrentCulture)); + writer.WriteLine(ex.ToString()); + writer.WriteLine("==================="); + } + } + } + + // Some customers experience exceptions such as 'OutOfMemory' errors when msbuild attempts to log errors to a local file. + // This catch helps to prevent the application from crashing in this best-effort dump-diagnostics path, + // but doesn't prevent the overall crash from going to Watson. + catch + { + } + } + + /// + /// Returns the content of any exception dump files modified + /// since the provided time, otherwise returns an empty string. + /// + internal static string ReadAnyExceptionFromFile(DateTime fromTimeUtc) + { + var builder = new StringBuilder(); + IEnumerable files = FileSystems.Default.EnumerateFiles(DebugDumpPath, "MSBuild*failure.txt"); + + foreach (string file in files) + { + if (FileSystems.Default.GetLastWriteTimeUtc(file) >= fromTimeUtc) + { + builder.Append(Environment.NewLine); + builder.Append(file); + builder.Append(':'); + builder.Append(Environment.NewLine); + builder.Append(FileSystems.Default.ReadFileAllText(file)); + builder.Append(Environment.NewLine); + } + } + + return builder.ToString(); + } +#endif + + /// Line and column pair. + internal struct LineAndColumn + { + /// Gets or sets line number. + internal int Line { get; set; } + + /// Gets or sets column position. + internal int Column { get; set; } + } + } +} diff --git a/src/MSBuildTaskHost/FileSystem/FileSystems.cs b/src/MSBuildTaskHost/FileSystem/FileSystems.cs new file mode 100644 index 00000000000..c876af6bc77 --- /dev/null +++ b/src/MSBuildTaskHost/FileSystem/FileSystems.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +namespace Microsoft.Build.Shared.FileSystem +{ + /// + /// Factory for + /// + internal static class FileSystems + { + public static IFileSystem Default = GetFileSystem(); + + private static IFileSystem GetFileSystem() + { +#if CLR2COMPATIBILITY + return MSBuildTaskHostFileSystem.Singleton(); +#else + if (NativeMethodsShared.IsWindows) + { + return MSBuildOnWindowsFileSystem.Singleton(); + } + else + { + return ManagedFileSystem.Singleton(); + } +#endif + } + } +} diff --git a/src/MSBuildTaskHost/FileSystem/IFileSystem.cs b/src/MSBuildTaskHost/FileSystem/IFileSystem.cs new file mode 100644 index 00000000000..8bfcb130067 --- /dev/null +++ b/src/MSBuildTaskHost/FileSystem/IFileSystem.cs @@ -0,0 +1,49 @@ +// 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; +using System.IO; + +#nullable disable + +namespace Microsoft.Build.Shared.FileSystem +{ + + /* + * This is a clone of Microsoft.Build.FileSystem.MSBuildFileSystemBase. + * MSBuildFileSystemBase is the public, reference interface. Changes should be made to MSBuildFileSystemBase and cloned in IFileSystem. + * Any new code should depend on MSBuildFileSystemBase instead of IFileSystem, if possible. + * + * MSBuild uses IFileSystem internally and adapts MSBuildFileSystemBase instances received from the outside to IFileSystem. + * Ideally there should be only one, public interface. However, such an interface would need to be put into the + * Microsoft.Build.Framework assembly, but that assembly cannot take new types because it breaks some old version of Nuget.exe. + * IFileSystem cannot be deleted for the same reason. + */ + internal interface IFileSystem + { + TextReader ReadFile(string path); + + Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share); + + string ReadFileAllText(string path); + + byte[] ReadFileAllBytes(string path); + + IEnumerable EnumerateFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly); + + IEnumerable EnumerateDirectories(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly); + + IEnumerable EnumerateFileSystemEntries(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly); + + FileAttributes GetAttributes(string path); + + DateTime GetLastWriteTimeUtc(string path); + + bool DirectoryExists(string path); + + bool FileExists(string path); + + bool FileOrDirectoryExists(string path); + } +} diff --git a/src/MSBuildTaskHost/FileUtilities.cs b/src/MSBuildTaskHost/FileUtilities.cs new file mode 100644 index 00000000000..0ca435b0137 --- /dev/null +++ b/src/MSBuildTaskHost/FileUtilities.cs @@ -0,0 +1,1604 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +#if !CLR2COMPATIBILITY +using System.Collections.Concurrent; +#else +using Microsoft.Build.Shared.Concurrent; +#endif +#if NET +using System.Buffers; +#endif +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using Microsoft.Build.Framework; +using Microsoft.Build.Shared.FileSystem; + +#nullable disable + +namespace Microsoft.Build.Shared +{ + /// + /// This class contains utility methods for file IO. + /// PERF\COVERAGE NOTE: Try to keep classes in 'shared' as granular as possible. All the methods in + /// each class get pulled into the resulting assembly. + /// + internal static partial class FileUtilities + { + // A list of possible test runners. If the program running has one of these substrings in the name, we assume + // this is a test harness. + + // This flag, when set, indicates that we are running tests. Initially assume it's true. It also implies that + // the currentExecutableOverride is set to a path (that is non-null). Assume this is not initialized when we + // have the impossible combination of runningTests = false and currentExecutableOverride = null. + + // This is the fake current executable we use in case we are running tests. + + /// + /// The directory where MSBuild stores cache information used during the build. + /// + internal static string cacheDirectory = null; + +#if !CLR2COMPATIBILITY + /// + /// AsyncLocal working directory for use during property/item expansion in multithreaded mode. + /// Set by MultiThreadedTaskEnvironmentDriver when building projects. null in multiprocess mode. + /// Using AsyncLocal ensures the value flows to child threads/tasks spawned during execution of tasks. + /// + private static readonly AsyncLocal s_currentThreadWorkingDirectory = new(); + internal static string CurrentThreadWorkingDirectory + { + get => s_currentThreadWorkingDirectory.Value; + set => s_currentThreadWorkingDirectory.Value = value; + } +#else + // net35 taskhost does not support AsyncLocal, and the scenario is not relevant there. + internal static string CurrentThreadWorkingDirectory = null; +#endif + +#if CLR2COMPATIBILITY + internal static string TempFileDirectory => Path.GetTempPath(); +#endif + + /// + /// FOR UNIT TESTS ONLY + /// Clear out the static variable used for the cache directory so that tests that + /// modify it can validate their modifications. + /// + internal static void ClearCacheDirectoryPath() + { + cacheDirectory = null; + } + + internal static readonly StringComparison PathComparison = GetIsFileSystemCaseSensitive() ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + + internal static readonly StringComparer PathComparer = GetIsFileSystemCaseSensitive() ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + + /// + /// Determines whether the file system is case sensitive. + /// Copied from https://github.com/dotnet/runtime/blob/73ba11f3015216b39cb866d9fb7d3d25e93489f2/src/libraries/Common/src/System/IO/PathInternal.CaseSensitivity.cs#L41-L59 + /// + public static bool GetIsFileSystemCaseSensitive() + { + try + { + string pathWithUpperCase = Path.Combine(Path.GetTempPath(), $"CASESENSITIVETEST{Guid.NewGuid():N}"); + using (new FileStream(pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose)) + { + string lowerCased = pathWithUpperCase.ToLowerInvariant(); + return !FileSystems.Default.FileExists(lowerCased); + } + } + catch (Exception exc) + { + // In case something goes terribly wrong, we don't want to fail just because + // of a casing test, so we assume case-insensitive-but-preserving. + Debug.Fail("Casing test failed: " + exc); + return false; + } + } + + /// + /// Copied from https://github.com/dotnet/corefx/blob/056715ff70e14712419d82d51c8c50c54b9ea795/src/Common/src/System/IO/PathInternal.Windows.cs#L61 + /// MSBuild should support the union of invalid path chars across the supported OSes, so builds can have the same behaviour crossplatform: https://github.com/dotnet/msbuild/issues/781#issuecomment-243942514 + /// +#if NET + internal static readonly SearchValues InvalidPathChars = SearchValues.Create( +#else + internal static readonly char[] InvalidPathChars = ( +#endif + [ + '|', '\0', + (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, + (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, + (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, + (char)31 + ]); + + /// + /// Copied from https://github.com/dotnet/corefx/blob/387cf98c410bdca8fd195b28cbe53af578698f94/src/System.Runtime.Extensions/src/System/IO/Path.Windows.cs#L18 + /// MSBuild should support the union of invalid path chars across the supported OSes, so builds can have the same behaviour crossplatform: https://github.com/dotnet/msbuild/issues/781#issuecomment-243942514 + /// + internal static readonly char[] InvalidFileNameCharsArray = + [ + '\"', '<', '>', '|', '\0', + (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, + (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, + (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, + (char)31, ':', '*', '?', '\\', '/' + ]; + +#if NET + internal static readonly SearchValues InvalidFileNameChars = SearchValues.Create(InvalidFileNameCharsArray); +#else + internal static char[] InvalidFileNameChars => InvalidFileNameCharsArray; +#endif + + internal static readonly string DirectorySeparatorString = Path.DirectorySeparatorChar.ToString(); + + private static readonly ConcurrentDictionary FileExistenceCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + private static readonly IFileSystem DefaultFileSystem = FileSystems.Default; + + /// + /// Retrieves the MSBuild runtime cache directory + /// + internal static string GetCacheDirectory() + { + if (cacheDirectory == null) + { + cacheDirectory = Path.Combine(TempFileDirectory, string.Format(CultureInfo.CurrentUICulture, "MSBuild{0}-{1}", EnvironmentUtilities.CurrentProcessId, AppDomain.CurrentDomain.Id)); + } + + return cacheDirectory; + } + + /// + /// Get the hex hash string for the string + /// + internal static string GetHexHash(string stringToHash) + { + return stringToHash.GetHashCode().ToString("X", CultureInfo.InvariantCulture); + } + + /// + /// Get the hash for the assemblyPaths + /// + internal static int GetPathsHash(IEnumerable assemblyPaths) + { + StringBuilder builder = new StringBuilder(); + + foreach (string path in assemblyPaths) + { + if (path != null) + { + string directoryPath = path.Trim(); + if (directoryPath.Length > 0) + { + DateTime lastModifiedTime; + if (NativeMethodsShared.GetLastWriteDirectoryUtcTime(directoryPath, out lastModifiedTime)) + { + builder.Append(lastModifiedTime.Ticks); + builder.Append('|'); + builder.Append(directoryPath.ToUpperInvariant()); + builder.Append('|'); + } + } + } + } + + return builder.ToString().GetHashCode(); + } + + /// + /// Returns whether MSBuild can write to the given directory. Throws for PathTooLongExceptions + /// but not other exceptions. + /// + internal static bool CanWriteToDirectory(string directory) + { + try + { + string testFilePath = Path.Combine(directory, $"MSBuild_{Guid.NewGuid():N}_testFile.txt"); + FileInfo file = new(testFilePath); + file.Directory.Create(); // If the directory already exists, this method does nothing. + File.WriteAllText(testFilePath, $"MSBuild process {EnvironmentUtilities.CurrentProcessId} successfully wrote to file."); + File.Delete(testFilePath); + return true; + } + catch (PathTooLongException) + { + ErrorUtilities.ThrowArgument("DebugPathTooLong", directory); + return false; // Should never reach here. + } + catch (Exception) + { + return false; + } + } + + /// + /// Clears the MSBuild runtime cache + /// + internal static void ClearCacheDirectory() + { + string cacheDirectory = GetCacheDirectory(); + + if (DefaultFileSystem.DirectoryExists(cacheDirectory)) + { + DeleteDirectoryNoThrow(cacheDirectory, true); + } + } + + /// + /// Ensures the path does not have a leading or trailing slash after removing the first 'start' characters. + /// + internal static string EnsureNoLeadingOrTrailingSlash(string path, int start) + { + int stop = path.Length; + while (start < stop && FrameworkFileUtilities.IsSlash(path[start])) + { + start++; + } + while (start < stop && FrameworkFileUtilities.IsSlash(path[stop - 1])) + { + stop--; + } + + return FrameworkFileUtilities.FixFilePath(path.Substring(start, stop - start)); + } + + /// + /// Ensures the path does not have a leading slash after removing the first 'start' characters but does end in a slash. + /// + internal static string EnsureTrailingNoLeadingSlash(string path, int start) + { + int stop = path.Length; + while (start < stop && FrameworkFileUtilities.IsSlash(path[start])) + { + start++; + } + + return FrameworkFileUtilities.FixFilePath(start < stop && FrameworkFileUtilities.IsSlash(path[stop - 1]) ? + path.Substring(start) : +#if NET + string.Concat(path.AsSpan(start), new(in Path.DirectorySeparatorChar))); +#else + path.Substring(start) + Path.DirectorySeparatorChar); +#endif + } + + /// + /// Ensures the path is enclosed within single quotes. + /// + /// The path to check. + /// The path enclosed by quotes. + internal static string EnsureSingleQuotes(string path) + { + return EnsureQuotes(path); + } + + /// + /// Ensures the path is enclosed within double quotes. + /// + /// The path to check. + /// The path enclosed by quotes. + internal static string EnsureDoubleQuotes(string path) + { + return EnsureQuotes(path, isSingleQuote: false); + } + + /// + /// Ensures the path is enclosed within quotes. + /// + /// The path to check. + /// Indicates if single or double quotes should be used + /// The path enclosed by quotes. + internal static string EnsureQuotes(string path, bool isSingleQuote = true) + { + path = FrameworkFileUtilities.FixFilePath(path); + + const char singleQuote = '\''; + const char doubleQuote = '\"'; + var targetQuote = isSingleQuote ? singleQuote : doubleQuote; + var convertQuote = isSingleQuote ? doubleQuote : singleQuote; + + if (!string.IsNullOrEmpty(path)) + { + // Special case: convert the quotes. + if (path.Length > 1 && path[0] == convertQuote && path[path.Length - 1] == convertQuote) + { +#if NET + path = $"{targetQuote}{path.AsSpan(1, path.Length - 2)}{targetQuote}"; +#else + path = $"{targetQuote}{path.Substring(1, path.Length - 2)}{targetQuote}"; +#endif + } + // Enclose the path in a set of the 'target' quote unless the string is already quoted with the 'target' quotes. + else if (path.Length == 1 || path[0] != targetQuote || path[path.Length - 1] != targetQuote) + { + path = $"{targetQuote}{path}{targetQuote}"; + } + } + + return path; + } + + /// + /// Trims the string and removes any double quotes around it. + /// + internal static string TrimAndStripAnyQuotes(string path) + { + if (path is null) + { + return path; + } + + // Trim returns the same string if trimming isn't needed + path = path.Trim(); + path = path.Trim(['"']); + + return path; + } + + /// + /// Get the directory name of a rooted full path + /// + /// + /// + internal static String GetDirectoryNameOfFullPath(String fullPath) + { + if (fullPath != null) + { + int i = fullPath.Length; + while (i > 0 && fullPath[--i] != Path.DirectorySeparatorChar && fullPath[i] != Path.AltDirectorySeparatorChar) + { + ; + } + + return FrameworkFileUtilities.FixFilePath(fullPath.Substring(0, i)); + } + return null; + } + + internal static string TruncatePathToTrailingSegments(string path, int trailingSegmentsToKeep) + { +#if !CLR2COMPATIBILITY + ErrorUtilities.VerifyThrowInternalLength(path, nameof(path)); + ErrorUtilities.VerifyThrow(trailingSegmentsToKeep >= 0, "trailing segments must be positive"); + + var segments = path.Split(FrameworkFileUtilities.Slashes, StringSplitOptions.RemoveEmptyEntries); + + var headingSegmentsToRemove = Math.Max(0, segments.Length - trailingSegmentsToKeep); + + return string.Join(DirectorySeparatorString, segments.Skip(headingSegmentsToRemove)); +#else + return path; +#endif + } + + internal static bool ContainsRelativePathSegments(string path) + { + for (int i = 0; i < path.Length; i++) + { + if (i + 1 < path.Length && path[i] == '.' && path[i + 1] == '.') + { + if (RelativePathBoundsAreValid(path, i, i + 1)) + { + return true; + } + else + { + i += 2; + continue; + } + } + + if (path[i] == '.' && RelativePathBoundsAreValid(path, i, i)) + { + return true; + } + } + + return false; + } + +#if !CLR2COMPATIBILITY + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private static bool RelativePathBoundsAreValid(string path, int leftIndex, int rightIndex) + { + var leftBound = leftIndex - 1 >= 0 + ? path[leftIndex - 1] + : (char?)null; + + var rightBound = rightIndex + 1 < path.Length + ? path[rightIndex + 1] + : (char?)null; + + return IsValidRelativePathBound(leftBound) && IsValidRelativePathBound(rightBound); + } + +#if !CLR2COMPATIBILITY + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private static bool IsValidRelativePathBound(char? c) + { + return c == null || IsAnySlash(c.Value); + } + + /// + /// Gets the canonicalized full path of the provided path. + /// Guidance for use: call this on all paths accepted through public entry + /// points that need normalization. After that point, only verify the path + /// is rooted, using ErrorUtilities.VerifyThrowPathRooted. + /// ASSUMES INPUT IS ALREADY UNESCAPED. + /// + internal static string NormalizePath(string path) + { + ErrorUtilities.VerifyThrowArgumentLength(path); + string fullPath = GetFullPath(path); + return FrameworkFileUtilities.FixFilePath(fullPath); + } + + internal static string NormalizePath(string directory, string file) + { + return NormalizePath(Path.Combine(directory, file)); + } + +#if !CLR2COMPATIBILITY + internal static string NormalizePath(params string[] paths) + { + return NormalizePath(Path.Combine(paths)); + } +#endif + + private static string GetFullPath(string path) + { +#if FEATURE_LEGACY_GETFULLPATH + if (NativeMethodsShared.IsWindows) + { + string uncheckedFullPath = NativeMethodsShared.GetFullPath(path); + + if (IsPathTooLong(uncheckedFullPath)) + { + string message = ResourceUtilities.FormatString(AssemblyResources.GetString("Shared.PathTooLong"), path, NativeMethodsShared.MaxPath); + throw new PathTooLongException(message); + } + + // We really don't care about extensions here, but Path.HasExtension provides a great way to + // invoke the CLR's invalid path checks (these are independent of path length) + Path.HasExtension(uncheckedFullPath); + + // If we detect we are a UNC path then we need to use the regular get full path in order to do the correct checks for UNC formatting + // and security checks for strings like \\?\GlobalRoot + return IsUNCPath(uncheckedFullPath) ? Path.GetFullPath(uncheckedFullPath) : uncheckedFullPath; + } +#endif + return Path.GetFullPath(path); + } + +#if FEATURE_LEGACY_GETFULLPATH + private static bool IsUNCPath(string path) + { + if (!NativeMethodsShared.IsWindows || !path.StartsWith(@"\\", StringComparison.Ordinal)) + { + return false; + } + bool isUNC = true; + for (int i = 2; i < path.Length - 1; i++) + { + if (path[i] == '\\') + { + isUNC = false; + break; + } + } + + /* + From Path.cs in the CLR + + Throw an ArgumentException for paths like \\, \\server, \\server\ + This check can only be properly done after normalizing, so + \\foo\.. will be properly rejected. Also, reject \\?\GLOBALROOT\ + (an internal kernel path) because it provides aliases for drives. + + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC")); + + // Check for \\?\Globalroot, an internal mechanism to the kernel + // that provides aliases for drives and other undocumented stuff. + // The kernel team won't even describe the full set of what + // is available here - we don't want managed apps mucking + // with this for security reasons. + */ + return isUNC || path.IndexOf(@"\\?\globalroot", StringComparison.OrdinalIgnoreCase) != -1; + } +#endif // FEATURE_LEGACY_GETFULLPATH + + /// + /// Normalizes all path separators (both forward and back slashes) to forward slashes. + /// This is platform-independent, unlike FrameworkFileUtilities.FixFilePath which only normalizes on non-Windows platforms. + /// Use this when you need consistent path comparison regardless of which separator style is used. + /// + /// The path to normalize + /// The path with all backslashes replaced by forward slashes, or the original path if null/empty + internal static string NormalizePathSeparatorsToForwardSlash(string path) + { + return string.IsNullOrEmpty(path) ? path : path.Replace('\\', '/'); + } + +#if !CLR2COMPATIBILITY + /// + /// If on Unix, convert backslashes to slashes for strings that resemble paths. + /// The heuristic is if something resembles paths (contains slashes) check if the + /// first segment exists and is a directory. + /// Use a native shared method to massage file path. If the file is adjusted, + /// that qualifies is as a path. + /// + /// @baseDirectory is just passed to LooksLikeUnixFilePath, to help with the check + /// + internal static string MaybeAdjustFilePath(string value, string baseDirectory = "") + { + var comparisonType = StringComparison.Ordinal; + + // Don't bother with arrays or properties or network paths, or those that + // have no slashes. + if (NativeMethodsShared.IsWindows || string.IsNullOrEmpty(value) + || value.StartsWith("$(", comparisonType) || value.StartsWith("@(", comparisonType) + || value.StartsWith("\\\\", comparisonType)) + { + return value; + } + + // For Unix-like systems, we may want to convert backslashes to slashes + Span newValue = ConvertToUnixSlashes(value.ToCharArray()); + + // Find the part of the name we want to check, that is remove quotes, if present + bool shouldAdjust = newValue.IndexOf('/') != -1 && LooksLikeUnixFilePath(RemoveQuotes(newValue), baseDirectory); + return shouldAdjust ? newValue.ToString() : value; + } + + /// + /// If on Unix, convert backslashes to slashes for strings that resemble paths. + /// This overload takes and returns ReadOnlyMemory of characters. + /// + internal static ReadOnlyMemory MaybeAdjustFilePath(ReadOnlyMemory value, string baseDirectory = "") + { + if (NativeMethodsShared.IsWindows || value.IsEmpty) + { + return value; + } + + // Don't bother with arrays or properties or network paths. + if (value.Length >= 2) + { + var span = value.Span; + + // The condition is equivalent to span.StartsWith("$(") || span.StartsWith("@(") || span.StartsWith("\\\\") + if ((span[1] == '(' && (span[0] == '$' || span[0] == '@')) || + (span[1] == '\\' && span[0] == '\\')) + { + return value; + } + } + + // For Unix-like systems, we may want to convert backslashes to slashes + Span newValue = ConvertToUnixSlashes(value.ToArray()); + + // Find the part of the name we want to check, that is remove quotes, if present + bool shouldAdjust = newValue.IndexOf('/') != -1 && LooksLikeUnixFilePath(RemoveQuotes(newValue), baseDirectory); + return shouldAdjust ? newValue.ToString().AsMemory() : value; + } + + private static Span ConvertToUnixSlashes(Span path) + { + return path.IndexOf('\\') == -1 ? path : CollapseSlashes(path); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Span CollapseSlashes(Span str) + { + int sliceLength = 0; + + // Performs Regex.Replace(str, @"[\\/]+", "/") + for (int i = 0; i < str.Length; i++) + { + bool isCurSlash = IsAnySlash(str[i]); + bool isPrevSlash = i > 0 && IsAnySlash(str[i - 1]); + + if (!isCurSlash || !isPrevSlash) + { + str[sliceLength] = str[i] == '\\' ? '/' : str[i]; + sliceLength++; + } + } + + return str.Slice(0, sliceLength); + } + + private static Span RemoveQuotes(Span path) + { + int endId = path.Length - 1; + char singleQuote = '\''; + char doubleQuote = '\"'; + + bool hasQuotes = path.Length > 2 + && ((path[0] == singleQuote && path[endId] == singleQuote) + || (path[0] == doubleQuote && path[endId] == doubleQuote)); + + return hasQuotes ? path.Slice(1, endId - 1) : path; + } +#endif + +#if !CLR2COMPATIBILITY + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + internal static bool IsAnySlash(char c) => c == '/' || c == '\\'; + +#if !CLR2COMPATIBILITY + /// + /// If on Unix, check if the string looks like a file path. + /// The heuristic is if something resembles paths (contains slashes) check if the + /// first segment exists and is a directory. + /// + /// If @baseDirectory is not null, then look for the first segment exists under + /// that + /// + internal static bool LooksLikeUnixFilePath(string value, string baseDirectory = "") + => LooksLikeUnixFilePath(value.AsSpan(), baseDirectory); + + internal static bool LooksLikeUnixFilePath(ReadOnlySpan value, string baseDirectory = "") + { + if (NativeMethodsShared.IsWindows) + { + return false; + } + + // The first slash will either be at the beginning of the string or after the first directory name + int directoryLength = value.Slice(1).IndexOf('/') + 1; + bool shouldCheckDirectory = directoryLength != 0; + + // Check for actual files or directories under / that get missed by the above logic + bool shouldCheckFileOrDirectory = !shouldCheckDirectory && value.Length > 0 && value[0] == '/'; + ReadOnlySpan directory = value.Slice(0, directoryLength); + + return (shouldCheckDirectory && DefaultFileSystem.DirectoryExists(Path.Combine(baseDirectory, directory.ToString()))) + || (shouldCheckFileOrDirectory && DefaultFileSystem.FileOrDirectoryExists(value.ToString())); + } +#endif + + /// + /// Extracts the directory from the given file-spec. + /// + /// The filespec. + /// directory path + internal static string GetDirectory(string fileSpec) + { + string directory = Path.GetDirectoryName(FrameworkFileUtilities.FixFilePath(fileSpec)); + + // if file-spec is a root directory e.g. c:, c:\, \, \\server\share + // NOTE: Path.GetDirectoryName also treats invalid UNC file-specs as root directories e.g. \\, \\server + if (directory == null) + { + // just use the file-spec as-is + directory = fileSpec; + } + else if ((directory.Length > 0) && !FrameworkFileUtilities.EndsWithSlash(directory)) + { + // restore trailing slash if Path.GetDirectoryName has removed it (this happens with non-root directories) + directory += Path.DirectorySeparatorChar; + } + + return directory; + } + +#if !CLR2COMPATIBILITY + /// + /// Deletes all subdirectories within the specified directory without throwing exceptions. + /// This method enumerates all subdirectories in the given directory and attempts to delete + /// each one recursively. If any IO-related exceptions occur during enumeration or deletion, + /// they are silently ignored. + /// + /// The directory whose subdirectories should be deleted. + /// + /// This method is useful for cleanup operations where partial failure is acceptable. + /// It will not delete the root directory itself, only its subdirectories. + /// IO exceptions during directory enumeration or deletion are caught and ignored. + /// + internal static void DeleteSubdirectoriesNoThrow(string directory) + { + try + { + foreach (string dir in FileSystems.Default.EnumerateDirectories(directory)) + { + DeleteDirectoryNoThrow(dir, recursive: true, retryCount: 1); + } + } + catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) + { + // If we can't enumerate the directories, ignore. Other cases should be handled by DeleteDirectoryNoThrow. + } + } +#endif + + /// + /// Determines whether the given assembly file name has one of the listed extensions. + /// + /// The name of the file + /// Array of extensions to consider. + /// + internal static bool HasExtension(string fileName, string[] allowedExtensions) + { + Debug.Assert(allowedExtensions?.Length > 0); + + // Easiest way to invoke invalid path chars + // check, which callers are relying on. + if (Path.HasExtension(fileName)) + { + foreach (string extension in allowedExtensions) + { + Debug.Assert(!String.IsNullOrEmpty(extension) && extension[0] == '.'); + + if (fileName.EndsWith(extension, PathComparison)) + { + return true; + } + } + } + + return false; + } + + // ISO 8601 Universal time with sortable format + internal const string FileTimeFormat = "yyyy'-'MM'-'dd HH':'mm':'ss'.'fffffff"; + + /// + /// Get the currently executing assembly path + /// + internal static string ExecutingAssemblyPath => Path.GetFullPath(AssemblyUtilities.GetAssemblyLocation(typeof(FileUtilities).GetTypeInfo().Assembly)); + + /// + /// Determines the full path for the given file-spec. + /// ASSUMES INPUT IS STILL ESCAPED + /// + /// The file spec to get the full path of. + /// + /// Whether to escape the path after getting the full path. + /// Full path to the file, escaped if not specified otherwise. + internal static string GetFullPath(string fileSpec, string currentDirectory, bool escape = true) + { + // Sending data out of the engine into the filesystem, so time to unescape. + fileSpec = FrameworkFileUtilities.FixFilePath(EscapingUtilities.UnescapeAll(fileSpec)); + + string fullPath = NormalizePath(Path.Combine(currentDirectory, fileSpec)); + // In some cases we might want to NOT escape in order to preserve symbols like @, %, $ etc. + if (escape) + { + // Data coming back from the filesystem into the engine, so time to escape it back. + fullPath = EscapingUtilities.Escape(fullPath); + } + + if (NativeMethodsShared.IsWindows && !FrameworkFileUtilities.EndsWithSlash(fullPath)) + { + if (FileUtilitiesRegex.IsDrivePattern(fileSpec) || + FileUtilitiesRegex.IsUncPattern(fullPath)) + { + // append trailing slash if Path.GetFullPath failed to (this happens with drive-specs and UNC shares) + fullPath += Path.DirectorySeparatorChar; + } + } + + return fullPath; + } + + /// + /// A variation of Path.GetFullPath that will return the input value + /// instead of throwing any IO exception. + /// Useful to get a better path for an error message, without the risk of throwing + /// if the error message was itself caused by the path being invalid! + /// + internal static string GetFullPathNoThrow(string path) + { + try + { + path = NormalizePath(path); + } + catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) + { + } + + return path; + } + + /// + /// Compare if two paths, relative to the given currentDirectory are equal. + /// Does not throw IO exceptions. See + /// + /// + /// + /// + /// + /// + internal static bool ComparePathsNoThrow(string first, string second, string currentDirectory, bool alwaysIgnoreCase = false) + { + StringComparison pathComparison = alwaysIgnoreCase ? StringComparison.OrdinalIgnoreCase : PathComparison; + // perf: try comparing the bare strings first + if (string.Equals(first, second, pathComparison)) + { + return true; + } + + var firstFullPath = NormalizePathForComparisonNoThrow(first, currentDirectory); + var secondFullPath = NormalizePathForComparisonNoThrow(second, currentDirectory); + + return string.Equals(firstFullPath, secondFullPath, pathComparison); + } + + /// + /// Normalizes a path for path comparison + /// Does not throw IO exceptions. See + /// + /// + internal static string NormalizePathForComparisonNoThrow(string path, string currentDirectory) + { + // file is invalid, return early to avoid triggering an exception + if (PathIsInvalid(path)) + { + return path; + } + + var normalizedPath = path.NormalizeForPathComparison(); + var fullPath = GetFullPathNoThrow(Path.Combine(currentDirectory, normalizedPath)); + + return fullPath; + } + + internal static bool PathIsInvalid(string path) + { + // Path.GetFileName does not react well to malformed filenames. + // For example, Path.GetFileName("a/b/foo:bar") returns bar instead of foo:bar + // It also throws exceptions on illegal path characters +#if NET + if (!path.AsSpan().ContainsAny(InvalidPathChars)) + { + int lastDirectorySeparator = path.LastIndexOfAny(FrameworkFileUtilities.Slashes); + return path.AsSpan(lastDirectorySeparator >= 0 ? lastDirectorySeparator + 1 : 0).ContainsAny(InvalidFileNameChars); + } +#else + if (path.IndexOfAny(InvalidPathChars) < 0) + { + int lastDirectorySeparator = path.LastIndexOfAny(FrameworkFileUtilities.Slashes); + return path.IndexOfAny(InvalidFileNameChars, lastDirectorySeparator >= 0 ? lastDirectorySeparator + 1 : 0) >= 0; + } +#endif + return true; + } + + /// + /// A variation on File.Delete that will throw ExceptionHandling.NotExpectedException exceptions + /// + internal static void DeleteNoThrow(string path) + { + try + { + File.Delete(FrameworkFileUtilities.FixFilePath(path)); + } + catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) + { + } + } + + /// + /// A variation on Directory.Delete that will throw ExceptionHandling.NotExpectedException exceptions + /// + [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Int32.TryParse(System.String,System.Int32@)", Justification = "We expect the out value to be 0 if the parse fails and compensate accordingly")] + internal static void DeleteDirectoryNoThrow(string path, bool recursive, int retryCount = 0, int retryTimeOut = 0) + { + // Try parse will set the out parameter to 0 if the string passed in is null, or is outside the range of an int. + if (!int.TryParse(Environment.GetEnvironmentVariable("MSBUILDDIRECTORYDELETERETRYCOUNT"), out retryCount)) + { + retryCount = 0; + } + + if (!int.TryParse(Environment.GetEnvironmentVariable("MSBUILDDIRECTORYDELETRETRYTIMEOUT"), out retryTimeOut)) + { + retryTimeOut = 0; + } + + retryCount = retryCount < 1 ? 2 : retryCount; + retryTimeOut = retryTimeOut < 1 ? 500 : retryTimeOut; + + path = FrameworkFileUtilities.FixFilePath(path); + + for (int i = 0; i < retryCount; i++) + { + try + { + if (DefaultFileSystem.DirectoryExists(path)) + { + Directory.Delete(path, recursive); + break; + } + } + catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) + { + } + + if (i + 1 < retryCount) // should not wait for the final iteration since we not gonna check anyway + { + Thread.Sleep(retryTimeOut); + } + } + } + + /// + /// Deletes a directory, ensuring that Directory.Delete does not get a path ending in a slash. + /// + /// + /// This is a workaround for https://github.com/dotnet/corefx/issues/3780, which clashed with a common + /// pattern in our tests. + /// + internal static void DeleteWithoutTrailingBackslash(string path, bool recursive = false) + { + // Some tests (such as FileMatcher and Evaluation tests) were failing with an UnauthorizedAccessException or directory not empty. + // This retry logic works around that issue. + const int NUM_TRIES = 3; + for (int i = 0; i < NUM_TRIES; i++) + { + try + { + Directory.Delete(FrameworkFileUtilities.EnsureNoTrailingSlash(path), recursive); + + // If we got here, the directory was successfully deleted + return; + } + catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) + { + if (i == NUM_TRIES - 1) + { + // var files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories); + // string fileString = string.Join(Environment.NewLine, files); + // string message = $"Unable to delete directory '{path}'. Contents:" + Environment.NewLine + fileString; + // throw new IOException(message, ex); + throw; + } + } + + Thread.Sleep(10); + } + } + + /// + /// Gets a file info object for the specified file path. If the file path + /// is invalid, or is a directory, or cannot be accessed, or does not exist, + /// it returns null rather than throwing or returning a FileInfo around a non-existent file. + /// This allows it to be called where File.Exists() (which never throws, and returns false + /// for directories) was called - but with the advantage that a FileInfo object is returned + /// that can be queried (e.g., for LastWriteTime) without hitting the disk again. + /// + /// + /// FileInfo around path if it is an existing /file/, else null + internal static FileInfo GetFileInfoNoThrow(string filePath) + { + filePath = AttemptToShortenPath(filePath); + + FileInfo fileInfo; + + try + { + fileInfo = new FileInfo(filePath); + } + catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) + { + // Invalid or inaccessible path: treat as if nonexistent file, just as File.Exists does + return null; + } + + if (fileInfo.Exists) + { + // It's an existing file + return fileInfo; + } + else + { + // Nonexistent, or existing but a directory, just as File.Exists behaves + return null; + } + } + + /// + /// Returns if the directory exists + /// + /// Full path to the directory in the filesystem + /// The file system + /// + internal static bool DirectoryExistsNoThrow(string fullPath, IFileSystem fileSystem = null) + { + fullPath = AttemptToShortenPath(fullPath); + + try + { + fileSystem ??= DefaultFileSystem; + + return Traits.Instance.CacheFileExistence + ? FileExistenceCache.GetOrAdd(fullPath, fullPath => fileSystem.DirectoryExists(fullPath)) + : fileSystem.DirectoryExists(fullPath); + } + catch + { + return false; + } + } + + /// + /// Returns if the directory exists + /// + /// Full path to the file in the filesystem + /// The file system + /// + internal static bool FileExistsNoThrow(string fullPath, IFileSystem fileSystem = null) + { + fullPath = AttemptToShortenPath(fullPath); + + try + { + fileSystem ??= DefaultFileSystem; + + return Traits.Instance.CacheFileExistence + ? FileExistenceCache.GetOrAdd(fullPath, fullPath => fileSystem.FileExists(fullPath)) + : fileSystem.FileExists(fullPath); + } + catch + { + return false; + } + } + + /// + /// If there is a directory or file at the specified path, returns true. + /// Otherwise, returns false. + /// Does not throw IO exceptions, to match Directory.Exists and File.Exists. + /// Unlike calling each of those in turn it only accesses the disk once, which is faster. + /// + internal static bool FileOrDirectoryExistsNoThrow(string fullPath, IFileSystem fileSystem = null) + { + fullPath = AttemptToShortenPath(fullPath); + + try + { + fileSystem ??= DefaultFileSystem; + + return Traits.Instance.CacheFileExistence + ? FileExistenceCache.GetOrAdd(fullPath, fullPath => fileSystem.FileOrDirectoryExists(fullPath)) + : fileSystem.FileOrDirectoryExists(fullPath); + } + catch + { + return false; + } + } + + /// + /// This method returns true if the specified filename is a solution file (.sln) or + /// solution filter file (.slnf); otherwise, it returns false. + /// + /// + /// Solution filters are included because they are a thin veneer over solutions, just + /// with a more limited set of projects to build, and should be treated the same way. + /// + internal static bool IsSolutionFilename(string filename) + { + return HasExtension(filename, ".sln") || + HasExtension(filename, ".slnf") || + HasExtension(filename, ".slnx"); + } + + internal static bool IsSolutionFilterFilename(string filename) + { + return HasExtension(filename, ".slnf"); + } + + internal static bool IsSolutionXFilename(string filename) + { + return HasExtension(filename, ".slnx"); + } + + /// + /// Returns true if the specified filename is a VC++ project file, otherwise returns false + /// + internal static bool IsVCProjFilename(string filename) + { + return HasExtension(filename, ".vcproj"); + } + + internal static bool IsDspFilename(string filename) + { + return HasExtension(filename, ".dsp"); + } + + /// + /// Returns true if the specified filename is a metaproject file (.metaproj), otherwise false. + /// + internal static bool IsMetaprojectFilename(string filename) + { + return HasExtension(filename, ".metaproj"); + } + + internal static bool IsBinaryLogFilename(string filename) + { + return HasExtension(filename, ".binlog"); + } + + private static bool HasExtension(string filename, string extension) + { + if (String.IsNullOrEmpty(filename)) + { + return false; + } + + return filename.EndsWith(extension, PathComparison); + } + + /// + /// Given the absolute location of a file, and a disc location, returns relative file path to that disk location. + /// Throws UriFormatException. + /// + /// + /// The base path we want to be relative to. Must be absolute. + /// Should not include a filename as the last segment will be interpreted as a directory. + /// + /// + /// The path we need to make relative to basePath. The path can be either absolute path or a relative path in which case it is relative to the base path. + /// If the path cannot be made relative to the base path (for example, it is on another drive), it is returned verbatim. + /// If the basePath is an empty string, returns the path. + /// + /// relative path (can be the full path) + internal static string MakeRelative(string basePath, string path) + { + ErrorUtilities.VerifyThrowArgumentNull(basePath); + ErrorUtilities.VerifyThrowArgumentLength(path); + + string fullBase = Path.GetFullPath(basePath); + string fullPath = Path.GetFullPath(path); + + string[] splitBase = fullBase.Split(MSBuildConstants.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); + string[] splitPath = fullPath.Split(MSBuildConstants.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); + + ErrorUtilities.VerifyThrow(splitPath.Length > 0, "Cannot call MakeRelative on a path of only slashes."); + + // On a mac, the path could start with any number of slashes and still be valid. We have to check them all. + int indexOfFirstNonSlashChar = 0; + while (path[indexOfFirstNonSlashChar] == Path.DirectorySeparatorChar) + { + indexOfFirstNonSlashChar++; + } + if (path.IndexOf(splitPath[0]) != indexOfFirstNonSlashChar) + { + // path was already relative so just return it + return FrameworkFileUtilities.FixFilePath(path); + } + + int index = 0; + while (index < splitBase.Length && index < splitPath.Length && splitBase[index].Equals(splitPath[index], PathComparison)) + { + index++; + } + + if (index == splitBase.Length && index == splitPath.Length) + { + return "."; + } + + // If the paths have no component in common, the only valid relative path is the full path. + if (index == 0) + { + return fullPath; + } + + StringBuilder sb = StringBuilderCache.Acquire(); + + for (int i = index; i < splitBase.Length; i++) + { + sb.Append("..").Append(Path.DirectorySeparatorChar); + } + for (int i = index; i < splitPath.Length; i++) + { + sb.Append(splitPath[i]).Append(Path.DirectorySeparatorChar); + } + + if (fullPath[fullPath.Length - 1] != Path.DirectorySeparatorChar) + { + sb.Length--; + } + + return StringBuilderCache.GetStringAndRelease(sb); + } + + /// + /// Normalizes the path if and only if it is longer than max path, + /// or would be if rooted by the current directory. + /// This may make it shorter by removing ".."'s. + /// + internal static string AttemptToShortenPath(string path) + { + if (IsPathTooLong(path) || IsPathTooLongIfRooted(path)) + { + // Attempt to make it shorter -- perhaps there are some \..\ elements + path = GetFullPathNoThrow(path); + } + return FrameworkFileUtilities.FixFilePath(path); + } + + public static bool IsPathTooLong(string path) + { + // >= not > because MAX_PATH assumes a trailing null + return path.Length >= NativeMethodsShared.MaxPath; + } + + private static bool IsPathTooLongIfRooted(string path) + { + bool hasMaxPath = NativeMethodsShared.HasMaxPath; + int maxPath = NativeMethodsShared.MaxPath; + // >= not > because MAX_PATH assumes a trailing null + return hasMaxPath && !IsRootedNoThrow(path) && NativeMethodsShared.GetCurrentDirectory().Length + path.Length + 1 /* slash */ >= maxPath; + } + + /// + /// A variation of Path.IsRooted that not throw any IO exception. + /// + private static bool IsRootedNoThrow(string path) + { + try + { + return Path.IsPathRooted(FrameworkFileUtilities.FixFilePath(path)); + } + catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) + { + return false; + } + } + + /// + /// Get the folder N levels above the given. Will stop and return current path when rooted. + /// + /// Path to get the folder above. + /// Number of levels up to walk. + /// Full path to the folder N levels above the path. + internal static string GetFolderAbove(string path, int count = 1) + { + if (count < 1) + { + return path; + } + + var parent = Directory.GetParent(path); + + while (count > 1 && parent?.Parent != null) + { + parent = parent.Parent; + count--; + } + + return parent?.FullName ?? path; + } + + /// + /// Combine multiple paths. Should only be used when compiling against .NET 2.0. + /// + /// Only use in .NET 2.0. Otherwise, use System.IO.Path.Combine(...) + /// + /// + /// Root path. + /// Paths to concatenate. + /// Combined path. + internal static string CombinePaths(string root, params string[] paths) + { + ErrorUtilities.VerifyThrowArgumentNull(root); + ErrorUtilities.VerifyThrowArgumentNull(paths); + + return paths.Aggregate(root, Path.Combine); + } + + internal static string TrimTrailingSlashes(this string s) + { + return s.TrimEnd(FrameworkFileUtilities.Slashes); + } + + /// + /// Replace all backward slashes to forward slashes + /// + internal static string ToSlash(this string s) + { + return s.Replace('\\', '/'); + } + + internal static string ToBackslash(this string s) + { + return s.Replace('/', '\\'); + } + + /// + /// Ensure all slashes are the current platform's slash + /// + /// + /// + internal static string ToPlatformSlash(this string s) + { + var separator = Path.DirectorySeparatorChar; + + return s.Replace(separator == '/' ? '\\' : '/', separator); + } + + internal static string WithTrailingSlash(this string s) + { + return FrameworkFileUtilities.EnsureTrailingSlash(s); + } + + internal static string NormalizeForPathComparison(this string s) => s.ToPlatformSlash().TrimTrailingSlashes(); + + // TODO: assumption on file system case sensitivity: https://github.com/dotnet/msbuild/issues/781 + internal static bool PathsEqual(string path1, string path2) + { + if (path1 == null && path2 == null) + { + return true; + } + if (path1 == null || path2 == null) + { + return false; + } + + var endA = path1.Length - 1; + var endB = path2.Length - 1; + + // Trim trailing slashes + for (var i = endA; i >= 0; i--) + { + var c = path1[i]; + if (c == '/' || c == '\\') + { + endA--; + } + else + { + break; + } + } + + for (var i = endB; i >= 0; i--) + { + var c = path2[i]; + if (c == '/' || c == '\\') + { + endB--; + } + else + { + break; + } + } + + if (endA != endB) + { + // Lengths not the same + return false; + } + + for (var i = 0; i <= endA; i++) + { + var charA = (uint)path1[i]; + var charB = (uint)path2[i]; + + if ((charA | charB) > 0x7F) + { + // Non-ascii chars move to non fast path + return PathsEqualNonAscii(path1, path2, i, endA - i + 1); + } + + // uppercase both chars - notice that we need just one compare per char + if ((uint)(charA - 'a') <= (uint)('z' - 'a')) + { + charA -= 0x20; + } + + if ((uint)(charB - 'a') <= (uint)('z' - 'a')) + { + charB -= 0x20; + } + + // Set path delimiters the same + if (charA == '\\') + { + charA = '/'; + } + if (charB == '\\') + { + charB = '/'; + } + + if (charA != charB) + { + return false; + } + } + + return true; + } + + internal static StreamWriter OpenWrite(string path, bool append, Encoding encoding = null) + { + const int DefaultFileStreamBufferSize = 4096; + FileMode mode = append ? FileMode.Append : FileMode.Create; + Stream fileStream = new FileStream(path, mode, FileAccess.Write, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan); + if (encoding == null) + { + return new StreamWriter(fileStream); + } + else + { + return new StreamWriter(fileStream, encoding); + } + } + + internal static StreamReader OpenRead(string path, Encoding encoding = null, bool detectEncodingFromByteOrderMarks = true) + { + const int DefaultFileStreamBufferSize = 4096; + Stream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan); + if (encoding == null) + { + return new StreamReader(fileStream); + } + else + { + return new StreamReader(fileStream, encoding, detectEncodingFromByteOrderMarks); + } + } + + /// + /// Locate a file in either the directory specified or a location in the + /// directory structure above that directory. + /// + internal static string GetDirectoryNameOfFileAbove(string startingDirectory, string fileName, IFileSystem fileSystem = null) + { + fileSystem ??= DefaultFileSystem; + + // Canonicalize our starting location + string lookInDirectory = GetFullPath(startingDirectory); + + do + { + // Construct the path that we will use to test against + string possibleFileDirectory = Path.Combine(lookInDirectory, fileName); + + // If we successfully locate the file in the directory that we're + // looking in, simply return that location. Otherwise we'll + // keep moving up the tree. + if (fileSystem.FileExists(possibleFileDirectory)) + { + // We've found the file, return the directory we found it in + return lookInDirectory; + } + else + { + // GetDirectoryName will return null when we reach the root + // terminating our search + lookInDirectory = Path.GetDirectoryName(lookInDirectory); + } + } + while (lookInDirectory != null); + + // When we didn't find the location, then return an empty string + return string.Empty; + } + + /// + /// Searches for a file based on the specified starting directory. + /// + /// The file to search for. + /// An optional directory to start the search in. The default location is the directory + /// of the file containing the property function. + /// The filesystem + /// The full path of the file if it is found, otherwise an empty string. + internal static string GetPathOfFileAbove(string file, string startingDirectory, IFileSystem fileSystem = null) + { + // This method does not accept a path, only a file name + if (file.Any(i => i.Equals(Path.DirectorySeparatorChar) || i.Equals(Path.AltDirectorySeparatorChar))) + { + ErrorUtilities.ThrowArgument("InvalidGetPathOfFileAboveParameter", file); + } + + // Search for a directory that contains that file + string directoryName = GetDirectoryNameOfFileAbove(startingDirectory, file, fileSystem); + + return String.IsNullOrEmpty(directoryName) ? String.Empty : NormalizePath(directoryName, file); + } + + internal static void EnsureDirectoryExists(string directoryPath) + { + if (!string.IsNullOrEmpty(directoryPath) && !DefaultFileSystem.DirectoryExists(directoryPath)) + { + Directory.CreateDirectory(directoryPath); + } + } + + // Method is simple set of function calls and may inline; + // we don't want it inlining into the tight loop that calls it as an exit case, + // so mark as non-inlining + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool PathsEqualNonAscii(string strA, string strB, int i, int length) + { + if (string.Compare(strA, i, strB, i, length, StringComparison.OrdinalIgnoreCase) == 0) + { + return true; + } + + var slash1 = strA.ToSlash(); + var slash2 = strB.ToSlash(); + + if (string.Compare(slash1, i, slash2, i, length, StringComparison.OrdinalIgnoreCase) == 0) + { + return true; + } + + return false; + } + +#if !CLR2COMPATIBILITY + /// + /// Clears the file existence cache. + /// + internal static void ClearFileExistenceCache() + { + FileExistenceCache.Clear(); + } +#endif + + internal static void ReadFromStream(this Stream stream, byte[] content, int startIndex, int length) + { + stream.ReadExactly(content, startIndex, length); + } + } +} + +#if !NET +namespace System.IO +{ + internal static class StreamExtensions + { + internal static void ReadExactly(this Stream stream, byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + if ((uint)count > buffer.Length - offset) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + while (count > 0) + { + int read = stream.Read(buffer, offset, count); + if (read <= 0) + { + throw new EndOfStreamException(); + } + offset +=read; + count -= read; + } + } + } +} +#endif diff --git a/src/MSBuildTaskHost/FileUtilitiesRegex.cs b/src/MSBuildTaskHost/FileUtilitiesRegex.cs new file mode 100644 index 00000000000..c35f1f9ed5f --- /dev/null +++ b/src/MSBuildTaskHost/FileUtilitiesRegex.cs @@ -0,0 +1,171 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +#nullable disable + +namespace Microsoft.Build.Shared +{ + /// + /// This class contains utility methods for file IO. + /// Separate from FileUtilities because some assemblies may only need the patterns. + /// PERF\COVERAGE NOTE: Try to keep classes in 'shared' as granular as possible. All the methods in + /// each class get pulled into the resulting assembly. + /// + internal static class FileUtilitiesRegex + { + private const char _backSlash = '\\'; + private const char _forwardSlash = '/'; + + /// + /// Indicates whether the specified string follows the pattern drive pattern (for example "C:", "D:"). + /// + /// Input to check for drive pattern. + /// true if follows the drive pattern, false otherwise. + internal static bool IsDrivePattern(string pattern) + { + // Format must be two characters long: ":" + return pattern.Length == 2 && + StartsWithDrivePattern(pattern); + } + + /// + /// Indicates whether the specified string follows the pattern drive pattern (for example "C:/" or "C:\"). + /// + /// Input to check for drive pattern with slash. + /// true if follows the drive pattern with slash, false otherwise. + internal static bool IsDrivePatternWithSlash(string pattern) + { + return pattern.Length == 3 && + StartsWithDrivePatternWithSlash(pattern); + } + + /// + /// Indicates whether the specified string starts with the drive pattern (for example "C:"). + /// + /// Input to check for drive pattern. + /// true if starts with drive pattern, false otherwise. + internal static bool StartsWithDrivePattern(string pattern) + { + // Format dictates a length of at least 2, + // first character must be a letter, + // second character must be a ":" + return pattern.Length >= 2 && +#if NET + char.IsAsciiLetter(pattern[0]) && +#else + ((pattern[0] >= 'A' && pattern[0] <= 'Z') || (pattern[0] >= 'a' && pattern[0] <= 'z')) && +#endif + pattern[1] == ':'; + } + + /// + /// Indicates whether the specified string starts with the drive pattern (for example "C:/" or "C:\"). + /// + /// Input to check for drive pattern. + /// true if starts with drive pattern with slash, false otherwise. + internal static bool StartsWithDrivePatternWithSlash(string pattern) + { + // Format dictates a length of at least 3, + // first character must be a letter, + // second character must be a ":" + // third character must be a slash. + return pattern.Length >= 3 && + StartsWithDrivePattern(pattern) && + (pattern[2] == _backSlash || pattern[2] == _forwardSlash); + } + + /// + /// Indicates whether the specified file-spec comprises exactly "\\server\share" (with no trailing characters). + /// + /// Input to check for UNC pattern. + /// true if comprises UNC pattern. + internal static bool IsUncPattern(string pattern) + { + // Return value == pattern.length means: + // meets minimum unc requirements + // pattern does not end in a '/' or '\' + // if a subfolder were found the value returned would be length up to that subfolder, therefore no subfolder exists + return StartsWithUncPatternMatchLength(pattern) == pattern.Length; + } + + /// + /// Indicates whether the specified file-spec begins with "\\server\share". + /// + /// Input to check for UNC pattern. + /// true if starts with UNC pattern. + internal static bool StartsWithUncPattern(string pattern) + { + // Any non -1 value returned means there was a match, therefore is begins with the pattern. + return StartsWithUncPatternMatchLength(pattern) != -1; + } + + /// + /// Indicates whether the file-spec begins with a UNC pattern and how long the match is. + /// + /// Input to check for UNC pattern. + /// length of the match, -1 if no match. + internal static int StartsWithUncPatternMatchLength(string pattern) + { + if (!MeetsUncPatternMinimumRequirements(pattern)) + { + return -1; + } + + bool prevCharWasSlash = true; + bool hasShare = false; + + for (int i = 2; i < pattern.Length; i++) + { + // Real UNC paths should only contain backslashes. However, the previous + // regex pattern accepted both so functionality will be retained. + if (pattern[i] == _backSlash || + pattern[i] == _forwardSlash) + { + if (prevCharWasSlash) + { + // We get here in the case of an extra slash. + return -1; + } + else if (hasShare) + { + return i; + } + + hasShare = true; + prevCharWasSlash = true; + } + else + { + prevCharWasSlash = false; + } + } + + if (!hasShare) + { + // no subfolder means no unc pattern. string is something like "\\abc" in this case + return -1; + } + + return pattern.Length; + } + + /// + /// Indicates whether or not the file-spec meets the minimum requirements of a UNC pattern. + /// + /// Input to check for UNC pattern minimum requirements. + /// true if the UNC pattern is a minimum length of 5 and the first two characters are be a slash, false otherwise. +#if !NET35 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + internal static bool MeetsUncPatternMinimumRequirements(string pattern) + { + return pattern.Length >= 5 && + (pattern[0] == _backSlash || + pattern[0] == _forwardSlash) && + (pattern[1] == _backSlash || + pattern[1] == _forwardSlash); + } + } +} diff --git a/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs b/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs new file mode 100644 index 00000000000..6b21b7d22eb --- /dev/null +++ b/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs @@ -0,0 +1,171 @@ +// 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.Globalization; +using System.Reflection; + +#if !FEATURE_CULTUREINFO_GETCULTURES +using System.Linq; +using Microsoft.Build.Framework; +#endif + +#nullable disable + +namespace Microsoft.Build.Shared +{ + /// + /// This class contains common reflection tasks + /// + internal static class AssemblyUtilities + { +#if !FEATURE_CULTUREINFO_GETCULTURES + // True when the cached method info objects have been set. + private static bool s_initialized; + + // Cached method info + private static PropertyInfo s_assemblylocationProperty; + private static MethodInfo s_cultureInfoGetCultureMethod; + + private static Lazy s_validCultures = new Lazy(() => GetValidCultures(), true); +#endif + +#if !CLR2COMPATIBILITY + private static Lazy s_entryAssembly = new Lazy(() => GetEntryAssembly()); + public static Assembly EntryAssembly => s_entryAssembly.Value; +#else + public static Assembly EntryAssembly = GetEntryAssembly(); +#endif + + public static string GetAssemblyLocation(Assembly assembly) + { +#if FEATURE_ASSEMBLY_LOCATION + return assembly.Location; +#else + // Assembly.Location is only available in .netstandard1.5, but MSBuild needs to target 1.3. + // use reflection to access the property + Initialize(); + + if (s_assemblylocationProperty == null) + { + throw new NotSupportedException("Type Assembly does not have the Location property"); + } + + return (string)s_assemblylocationProperty.GetValue(assembly); +#endif + } + +#if CLR2COMPATIBILITY + /// + /// Shim for the lack of in .NET 3.5. + /// + public static Type GetTypeInfo(this Type t) + { + return t; + } +#endif + + public static AssemblyName CloneIfPossible(this AssemblyName assemblyNameToClone) + { +#if CLR2COMPATIBILITY + return (AssemblyName)assemblyNameToClone.Clone(); +#else + + // NOTE: In large projects, this is called a lot. Avoid calling AssemblyName.Clone + // because it clones the Version property (which is immutable) and the PublicKey property + // and the PublicKeyToken property. + // + // While the array themselves are mutable - throughout MSBuild they are only ever + // read from. + AssemblyName name = new AssemblyName(); + name.Name = assemblyNameToClone.Name; + name.SetPublicKey(assemblyNameToClone.GetPublicKey()); + name.SetPublicKeyToken(assemblyNameToClone.GetPublicKeyToken()); + name.Version = assemblyNameToClone.Version; + name.Flags = assemblyNameToClone.Flags; + name.ProcessorArchitecture = assemblyNameToClone.ProcessorArchitecture; + +#if !RUNTIME_TYPE_NETCORE + name.CultureInfo = assemblyNameToClone.CultureInfo; + name.HashAlgorithm = assemblyNameToClone.HashAlgorithm; + name.VersionCompatibility = assemblyNameToClone.VersionCompatibility; + name.CodeBase = assemblyNameToClone.CodeBase; + name.KeyPair = assemblyNameToClone.KeyPair; + name.VersionCompatibility = assemblyNameToClone.VersionCompatibility; +#else + // Setting the culture name creates a new CultureInfo, leading to many allocations. Only set CultureName when the CultureInfo member is not available. + name.CultureName = assemblyNameToClone.CultureName; +#endif + + return name; +#endif + + } + +#if !FEATURE_CULTUREINFO_GETCULTURES + public static bool CultureInfoHasGetCultures() + { + return s_cultureInfoGetCultureMethod != null; + } +#endif // !FEATURE_CULTUREINFO_GETCULTURES + + public static CultureInfo[] GetAllCultures() + { +#if FEATURE_CULTUREINFO_GETCULTURES + return CultureInfo.GetCultures(CultureTypes.AllCultures); +#else + Initialize(); + + if (!CultureInfoHasGetCultures()) + { + throw new NotSupportedException("CultureInfo does not have the method GetCultures"); + } + + return s_validCultures.Value; +#endif + } + +#if !FEATURE_CULTUREINFO_GETCULTURES + /// + /// Initialize static fields. Doesn't need to be thread safe. + /// + private static void Initialize() + { + if (s_initialized) + { + return; + } + + s_assemblylocationProperty = typeof(Assembly).GetProperty("Location", typeof(string)); + s_cultureInfoGetCultureMethod = typeof(CultureInfo).GetMethod("GetCultures"); + + s_initialized = true; + } +#endif // !FEATURE_CULTUREINFO_GETCULTURES + + private static Assembly GetEntryAssembly() + { + return System.Reflection.Assembly.GetEntryAssembly(); + } + +#if !FEATURE_CULTUREINFO_GETCULTURES + private static CultureInfo[] GetValidCultures() + { + var cultureTypesType = s_cultureInfoGetCultureMethod?.GetParameters().FirstOrDefault()?.ParameterType; + + FrameworkErrorUtilities.VerifyThrow(cultureTypesType?.Name == "CultureTypes" && + Enum.IsDefined(cultureTypesType, "AllCultures"), + "GetCulture is expected to accept CultureTypes.AllCultures"); + + var allCulturesEnumValue = Enum.Parse(cultureTypesType, "AllCultures", true); + + var cultures = s_cultureInfoGetCultureMethod.Invoke(null, [allCulturesEnumValue]) as CultureInfo[]; + + // CultureInfo.GetCultures should work if all reflection checks pass + FrameworkErrorUtilities.VerifyThrowInternalNull(cultures); + + return cultures; + } +#endif + } +} diff --git a/src/MSBuildTaskHost/Framework/BinaryReaderFactory.cs b/src/MSBuildTaskHost/Framework/BinaryReaderFactory.cs new file mode 100644 index 00000000000..5cc76fe84ea --- /dev/null +++ b/src/MSBuildTaskHost/Framework/BinaryReaderFactory.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; + +namespace Microsoft.Build; + +/// +/// Opaque holder of shared buffer. +/// +internal abstract class BinaryReaderFactory +{ + public abstract BinaryReader Create(Stream stream); +} diff --git a/src/MSBuildTaskHost/Framework/BinaryTranslator.cs b/src/MSBuildTaskHost/Framework/BinaryTranslator.cs new file mode 100644 index 00000000000..f25c79df899 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/BinaryTranslator.cs @@ -0,0 +1,1817 @@ +// 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; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Framework.BuildException; + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + /// + /// This class is responsible for serializing and deserializing simple types to and + /// from the byte streams used to communicate INodePacket-implementing classes. + /// Each class implements a Translate method on INodePacket which takes this class + /// as a parameter, and uses it to store and retrieve fields to the stream. + /// + internal static class BinaryTranslator + { + /// + /// Presence of this key in the dictionary indicates that it was null. + /// + /// + /// This constant is needed for a workaround concerning serializing BuildResult with a version. + /// + private const string SpecialKeyForDictionaryBeingNull = "=MSBUILDDICTIONARYWASNULL="; + +#nullable enable + /// + /// Returns a read-only serializer. + /// + /// The serializer. + internal static ITranslator GetReadTranslator(Stream stream, BinaryReaderFactory buffer) + { + return new BinaryReadTranslator(stream, buffer); + } +#nullable disable + + /// + /// Returns a write-only serializer. + /// + /// The stream containing data to serialize. + /// The serializer. + internal static ITranslator GetWriteTranslator(Stream stream) + { + return new BinaryWriteTranslator(stream); + } + + /// + /// Implementation of ITranslator for reading from a stream. + /// + private class BinaryReadTranslator : ITranslator + { + /// + /// The intern reader used in an intern scope. + /// + private InterningReadTranslator _interner; + + /// + /// The binary reader used in read mode. + /// + private BinaryReader _reader; + + /// + /// Whether the caller has entered an intern scope. + /// + private bool _isInterning; + +#nullable enable + /// + /// Constructs a serializer from the specified stream, operating in the designated mode. + /// + public BinaryReadTranslator(Stream packetStream, BinaryReaderFactory buffer) + { + _reader = buffer.Create(packetStream); + } +#nullable disable + + /// + /// Delegates the Dispose call the to the underlying BinaryReader. + /// + public void Dispose() + { + _reader.Close(); + } + + /// + /// Gets the reader, if any. + /// + public BinaryReader Reader + { + get { return _reader; } + } + + /// + /// Gets the writer, if any. + /// + public BinaryWriter Writer + { + get + { + EscapeHatches.ThrowInternalError("Cannot get writer from reader."); + return null; + } + } + + /// + /// Returns the current serialization mode. + /// + public TranslationDirection Mode + { + [DebuggerStepThrough] + get + { return TranslationDirection.ReadFromStream; } + } + + /// + public byte NegotiatedPacketVersion { get; set; } + + /// + /// Translates a boolean. + /// + /// The value to be translated. + public void Translate(ref bool value) + { + value = _reader.ReadBoolean(); + } + + /// + /// Translates an array. + /// + /// The array to be translated. + public void Translate(ref bool[] array) + { + if (!TranslateNullable(array)) + { + return; + } + + int count = _reader.ReadInt32(); + array = new bool[count]; + + for (int i = 0; i < count; i++) + { + array[i] = _reader.ReadBoolean(); + } + } + + /// + /// Translates a byte. + /// + /// The value to be translated. + public void Translate(ref byte value) + { + value = _reader.ReadByte(); + } + + /// + /// Translates a short. + /// + /// The value to be translated. + public void Translate(ref short value) + { + value = _reader.ReadInt16(); + } + + /// + /// Translates an unsigned short. + /// + /// The value to be translated. + public void Translate(ref ushort value) + { + value = _reader.ReadUInt16(); + } + + /// + /// Translates an integer. + /// + /// The value to be translated. + public void Translate(ref int value) + { + value = _reader.ReadInt32(); + } + + /// + public void Translate(ref uint unsignedInteger) => unsignedInteger = _reader.ReadUInt32(); + + /// + /// Translates a TaskHostParameters. + /// + /// The TaskHostParameters to be translated. + public void Translate(ref TaskHostParameters value) + { + string runtime = null; + string architecture = null; + string dotnetHostPath = null; + string msBuildAssemblyPath = null; + bool? isTaskHostFactory = null; + + Translate(ref runtime); + Translate(ref architecture); + Translate(ref dotnetHostPath); + Translate(ref msBuildAssemblyPath); + + bool hasTaskHostFactory = _reader.ReadBoolean(); + if (hasTaskHostFactory) + { + isTaskHostFactory = _reader.ReadBoolean(); + } + + value = new TaskHostParameters( + runtime: runtime, + architecture: architecture, + dotnetHostPath: dotnetHostPath, + msBuildAssemblyPath: msBuildAssemblyPath, + taskHostFactoryExplicitlyRequested: isTaskHostFactory); + } + + /// + /// Translates an array. + /// + /// The array to be translated. + public void Translate(ref int[] array) + { + if (!TranslateNullable(array)) + { + return; + } + + int count = _reader.ReadInt32(); + array = new int[count]; + + for (int i = 0; i < count; i++) + { + array[i] = _reader.ReadInt32(); + } + } + + /// + /// Translates a long. + /// + /// The value to be translated. + public void Translate(ref long value) + { + value = _reader.ReadInt64(); + } + + /// + /// Translates a double. + /// + /// The value to be translated. + public void Translate(ref double value) + { + value = _reader.ReadDouble(); + } + + /// + /// Translates a string. + /// + /// The value to be translated. + public void Translate(ref string value) + { + if (!TranslateNullable(value)) + { + return; + } + + value = _reader.ReadString(); + } + + /// + /// Translates a byte array + /// + /// The array to be translated + public void Translate(ref byte[] byteArray) + { + if (!TranslateNullable(byteArray)) + { + return; + } + + int count = _reader.ReadInt32(); + if (count > 0) + { + byteArray = _reader.ReadBytes(count); + } + else + { +#pragma warning disable CA1825 // Avoid zero-length array allocations + byteArray = new byte[0]; +#pragma warning restore CA1825 // Avoid zero-length array allocations + } + } + + /// + /// Translates a byte array + /// + /// The array to be translated. + /// The length of array which will be used in translation. This parameter is not used when reading + public void Translate(ref byte[] byteArray, ref int length) + { + Translate(ref byteArray); + length = byteArray.Length; + } + + /// + /// Translates a string array. + /// + /// The array to be translated. + public void Translate(ref string[] array) + { + if (!TranslateNullable(array)) + { + return; + } + + int count = _reader.ReadInt32(); + array = new string[count]; + + for (int i = 0; i < count; i++) + { + array[i] = _reader.ReadString(); + } + } + + /// + public void Translate(ref HashSet set) + { + if (!TranslateNullable(set)) + { + return; + } + + int count = _reader.ReadInt32(); +#if NET472_OR_GREATER || NET9_0_OR_GREATER + set = new HashSet(count); +#else + set = new HashSet(); +#endif + + for (int i = 0; i < count; i++) + { + set.Add(_reader.ReadString()); + } + } + + /// + /// Translates a list of strings + /// + /// The list to be translated. + public void Translate(ref List list) + { + if (!TranslateNullable(list)) + { + return; + } + + int count = _reader.ReadInt32(); + list = new List(count); + + for (int i = 0; i < count; i++) + { + list.Add(_reader.ReadString()); + } + } + + /// + /// Translates a list of T using an + /// + /// The list to be translated. + /// The translator to use for the items in the list + /// TaskItem type + public void Translate(ref List list, ObjectTranslator objectTranslator) + { + IList listAsInterface = list; + Translate(ref listAsInterface, objectTranslator, count => new List(count)); + list = (List)listAsInterface; + } + + /// + public void Translate(ref List list, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) + { + IList listAsInterface = list; + Translate(ref listAsInterface, objectTranslator, valueFactory, count => new List(count)); + list = (List)listAsInterface; + } + + public void Translate(ref IList list, ObjectTranslator objectTranslator, NodePacketCollectionCreator collectionFactory) where L : IList + { + if (!TranslateNullable(list)) + { + return; + } + + int count = _reader.ReadInt32(); + list = collectionFactory(count); + + for (int i = 0; i < count; i++) + { + T value = default(T); + + objectTranslator(this, ref value); + list.Add(value); + } + } + + public void Translate(ref IList list, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory, NodePacketCollectionCreator collectionFactory) where L : IList + { + if (!TranslateNullable(list)) + { + return; + } + + int count = _reader.ReadInt32(); + list = collectionFactory(count); + + for (int i = 0; i < count; i++) + { + T value = default(T); + + objectTranslator(this, valueFactory, ref value); + list.Add(value); + } + } + + /// + /// Translates a collection of T into the specified type using an and + /// + /// The collection to be translated. + /// The translator to use for the values in the collection. + /// The factory to create the ICollection. + /// The type contained in the collection. + /// The type of collection to be created. + public void Translate(ref ICollection collection, ObjectTranslator objectTranslator, NodePacketCollectionCreator collectionFactory) where L : ICollection + { + if (!TranslateNullable(collection)) + { + return; + } + + int count = _reader.ReadInt32(); + collection = collectionFactory(count); + + for (int i = 0; i < count; i++) + { + T value = default(T); + objectTranslator(this, ref value); + collection.Add(value); + } + } + + /// + /// Translates a DateTime. + /// + /// The value to be translated. + public void Translate(ref DateTime value) + { + DateTimeKind kind = DateTimeKind.Unspecified; + TranslateEnum(ref kind, 0); + value = new DateTime(_reader.ReadInt64(), kind); + } + + /// + /// Translates a TimeSpan. + /// + /// The value to be translated. + public void Translate(ref TimeSpan value) + { + long ticks = 0; + Translate(ref ticks); + value = new System.TimeSpan(ticks); + } + + // MSBuildTaskHost is based on CLR 3.5, which does not have the 6-parameter constructor for BuildEventContext. + // However, it also does not ever need to translate BuildEventContexts, so it should be perfectly safe to + // compile this method out of that assembly. +#if !CLR2COMPATIBILITY + + /// + /// Translates a BuildEventContext + /// + /// + /// This method exists only because there is no serialization method built into the BuildEventContext + /// class, and it lives in Framework and we don't want to add a public method to it. + /// + /// The context to be translated. + public void Translate(ref BuildEventContext value) + { + value = new BuildEventContext( + _reader.ReadInt32(), + _reader.ReadInt32(), + _reader.ReadInt32(), + _reader.ReadInt32(), + _reader.ReadInt32(), + _reader.ReadInt32(), + _reader.ReadInt32()); + } +#endif + + /// + /// Translates a CultureInfo + /// + /// The CultureInfo to translate + public void TranslateCulture(ref CultureInfo value) + { + string cultureName = _reader.ReadString(); + +#if CLR2COMPATIBILITY + // It may be that some culture codes are accepted on later .net framework versions + // but not on the older 3.5 or 2.0. Fallbacks are required in this case to prevent + // exceptions + value = LoadCultureWithFallback(cultureName); +#else + value = new CultureInfo(cultureName); +#endif + } + +#if CLR2COMPATIBILITY + private static CultureInfo LoadCultureWithFallback(string cultureName) + { + CultureInfo cultureInfo; + + return TryLoadCulture(cultureName, out cultureInfo) ? cultureInfo : CultureInfo.CurrentCulture; + } + + private static bool TryLoadCulture(string cultureName, out CultureInfo cultureInfo) + { + try + { + cultureInfo = new CultureInfo(cultureName); + return true; + } + catch + { + cultureInfo = null; + return false; + } + } +#endif + + /// + /// Translates an enumeration. + /// + /// The enumeration type. + /// The enumeration instance to be translated. + /// The enumeration value as an integer. + /// This is a bit ugly, but it doesn't seem like a nice method signature is possible because + /// you can't pass the enum type as a reference and constrain the generic parameter to Enum. Nor + /// can you simply pass as ref Enum, because an enum instance doesn't match that function signature. + /// Finally, converting the enum to an int assumes that we always want to transport enums as ints. This + /// works in all of our current cases, but certainly isn't perfectly generic. + public void TranslateEnum(ref T value, int numericValue) + where T : struct, Enum + { + numericValue = _reader.ReadInt32(); + Type enumType = value.GetType(); + value = (T)Enum.ToObject(enumType, numericValue); + } + + public void TranslateException(ref Exception value) + { + if (!TranslateNullable(value)) + { + return; + } + + value = BuildExceptionBase.ReadExceptionFromTranslator(this); + } + + + /// + /// Translates an object implementing INodePacketTranslatable. + /// + /// The reference type. + /// The value to be translated. + public void Translate(ref T value) + where T : ITranslatable, new() + { + if (!TranslateNullable(value)) + { + return; + } + + value = new T(); + value.Translate(this); + } + + /// + /// Translates an array of objects implementing INodePacketTranslatable. + /// + /// The reference type. + /// The array to be translated. + public void TranslateArray(ref T[] array) + where T : ITranslatable, new() + { + if (!TranslateNullable(array)) + { + return; + } + + int count = _reader.ReadInt32(); + array = new T[count]; + + for (int i = 0; i < count; i++) + { + array[i] = new T(); + array[i].Translate(this); + } + } + + /// + public void TranslateArray(ref T[] array, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) + { + if (!TranslateNullable(array)) + { + return; + } + + int count = _reader.ReadInt32(); + array = new T[count]; + + for (int i = 0; i < count; i++) + { + objectTranslator(this, valueFactory, ref array[i]); + } + } + + /// + /// Translates a dictionary of { string, string }. + /// + /// The dictionary to be translated. + /// The comparer used to instantiate the dictionary. + public void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer) + { + IDictionary copy = dictionary; + + TranslateDictionary( + ref copy, + count => new Dictionary(count, comparer)); + + dictionary = (Dictionary)copy; + } + + /// + /// Translates a dictionary of { string, string } with additional entries. The dictionary might be null despite being populated. + /// + /// The dictionary to be translated. + /// The comparer used to instantiate the dictionary. + /// Additional entries to be translated + /// Additional entries keys + /// + /// This overload is needed for a workaround concerning serializing BuildResult with a version. + /// It deserializes additional entries together with the main dictionary. + /// + public void TranslateDictionary(ref IDictionary dictionary, IEqualityComparer comparer, ref Dictionary additionalEntries, HashSet additionalEntriesKeys) + { + if (!TranslateNullable(dictionary)) + { + return; + } + + int count = _reader.ReadInt32(); + dictionary = new Dictionary(count, comparer); + additionalEntries = new(); + + for (int i = 0; i < count; i++) + { + string key = null; + Translate(ref key); + string value = null; + Translate(ref value); + if (additionalEntriesKeys.Contains(key)) + { + additionalEntries[key] = value; + } + else if (comparer.Equals(key, SpecialKeyForDictionaryBeingNull)) + { + // Presence of special key SpecialKeyForDictionaryBeingNull indicates that the dictionary was null. + dictionary = null; + + // If the dictionary is null, we should have only two keys: SpecialKeyForDictionaryBeingNull, SpecialKeyForVersion + Debug.Assert(count == 2); + } + else if (dictionary is not null) + { + dictionary[key] = value; + } + } + } + + public void TranslateDictionary(ref IDictionary dictionary, NodePacketCollectionCreator> dictionaryCreator) + { + if (!TranslateNullable(dictionary)) + { + return; + } + + int count = _reader.ReadInt32(); + dictionary = dictionaryCreator(count); + + for (int i = 0; i < count; i++) + { + string key = null; + Translate(ref key); + string value = null; + Translate(ref value); + dictionary[key] = value; + } + } + + public void TranslateDictionary( + ref IDictionary dictionary, + ObjectTranslator keyTranslator, + ObjectTranslator valueTranslator, + NodePacketCollectionCreator> dictionaryCreator) + { + if (!TranslateNullable(dictionary)) + { + return; + } + + int count = _reader.ReadInt32(); + dictionary = dictionaryCreator(count); + + for (int i = 0; i < count; i++) + { + K key = default(K); + keyTranslator(this, ref key); + V value = default(V); + valueTranslator(this, ref value); + dictionary[key] = value; + } + } + + /// + public void TranslateDictionary( + ref IDictionary dictionary, + ObjectTranslator keyTranslator, + ObjectTranslatorWithValueFactory valueTranslator, + NodePacketValueFactory valueFactory, + NodePacketCollectionCreator> dictionaryCreator) + { + if (!TranslateNullable(dictionary)) + { + return; + } + + int count = _reader.ReadInt32(); + dictionary = dictionaryCreator(count); + + for (int i = 0; i < count; i++) + { + K key = default(K); + keyTranslator(this, ref key); + V value = default(V); + valueTranslator(this, valueFactory, ref value); + dictionary[key] = value; + } + } + + /// + public void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) + where T : class + { + if (!TranslateNullable(dictionary)) + { + return; + } + + int count = _reader.ReadInt32(); + dictionary = new Dictionary(count, comparer); + + for (int i = 0; i < count; i++) + { + string key = null; + Translate(ref key); + T value = null; + objectTranslator(this, valueFactory, ref value); + dictionary[key] = value; + } + } + + /// + public void TranslateDictionary(ref D dictionary, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) + where D : IDictionary, new() + where T : class + { + if (!TranslateNullable(dictionary)) + { + return; + } + + int count = _reader.ReadInt32(); + dictionary = new D(); + + for (int i = 0; i < count; i++) + { + string key = null; + Translate(ref key); + T value = null; + objectTranslator(this, valueFactory, ref value); + dictionary[key] = value; + } + } + + /// + public void TranslateDictionary(ref D dictionary, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory, NodePacketCollectionCreator dictionaryCreator) + where D : IDictionary + where T : class + { + if (!TranslateNullable(dictionary)) + { + return; + } + + int count = _reader.ReadInt32(); + dictionary = dictionaryCreator(count); + + for (int i = 0; i < count; i++) + { + string key = null; + Translate(ref key); + T value = null; + objectTranslator(this, valueFactory, ref value); + dictionary[key] = value; + } + } + + public void TranslateDictionary(ref Dictionary dictionary, StringComparer comparer) + { + if (!TranslateNullable(dictionary)) + { + return; + } + + int count = _reader.ReadInt32(); + dictionary = new(count, comparer); + string key = string.Empty; + DateTime val = DateTime.MinValue; + for (int i = 0; i < count; i++) + { + Translate(ref key); + Translate(ref val); + dictionary.Add(key, val); + } + } + + /// + /// Reads in the boolean which says if this object is null or not. + /// + /// The type of object to test. + /// True if the object should be read, false otherwise. + public bool TranslateNullable(T value) + { + bool haveRef = _reader.ReadBoolean(); + return haveRef; + } + + public void WithInterning(IEqualityComparer comparer, int initialCapacity, Action internBlock) + { + if (_isInterning) + { + throw new InvalidOperationException("Cannot enter recursive intern block."); + } + + _isInterning = true; + + // Deserialize the intern header before entering the intern scope. + _interner ??= new InterningReadTranslator(this); + _interner.Translate(this); + + // No other setup is needed since we can parse the packet directly from the stream. + internBlock(this); + + _isInterning = false; + } + + public void Intern(ref string str, bool nullable = true) + { + if (!_isInterning) + { + Translate(ref str); + return; + } + + if (nullable && !TranslateNullable(string.Empty)) + { + str = null; + return; + } + + str = _interner.Read(); + } + + public void Intern(ref string[] array) + { + if (!_isInterning) + { + Translate(ref array); + return; + } + + if (!TranslateNullable(array)) + { + return; + } + + int count = _reader.ReadInt32(); + array = new string[count]; + + for (int i = 0; i < count; i++) + { + array[i] = _interner.Read(); + } + } + + public void InternPath(ref string str, bool nullable = true) + { + if (!_isInterning) + { + Translate(ref str); + return; + } + + if (nullable && !TranslateNullable(string.Empty)) + { + str = null; + return; + } + + str = _interner.ReadPath(); + } + } + + /// + /// Implementation of ITranslator for writing to a stream. + /// + private class BinaryWriteTranslator : ITranslator + { + /// + /// The binary writer used in write mode. + /// + private BinaryWriter _writer; + + /// + /// The intern writer used in an intern scope. + /// This must be lazily instantiated since the interner has its own internal write translator, and + /// would otherwise go into a recursive loop on initalization. + /// + private InterningWriteTranslator _interner; + + /// + /// Whether the caller has entered an intern scope. + /// + private bool _isInterning; + + /// + /// Constructs a serializer from the specified stream, operating in the designated mode. + /// + /// The stream serving as the source or destination of data. + public BinaryWriteTranslator(Stream packetStream) + { + _writer = new BinaryWriter(packetStream); + } + + /// + /// Delegates the Dispose call the to the underlying BinaryWriter. + /// + public void Dispose() + { + _writer.Close(); + } + + /// + /// Gets the reader, if any. + /// + public BinaryReader Reader + { + get + { + EscapeHatches.ThrowInternalError("Cannot get reader from writer."); + return null; + } + } + + /// + /// Gets the writer, if any. + /// + public BinaryWriter Writer + { + get { return _writer; } + } + + /// + /// Returns the current serialization mode. + /// + public TranslationDirection Mode + { + [DebuggerStepThrough] + get + { return TranslationDirection.WriteToStream; } + } + + /// + public byte NegotiatedPacketVersion { get; set; } + + /// + /// Translates a boolean. + /// + /// The value to be translated. + public void Translate(ref bool value) + { + _writer.Write(value); + } + + /// + /// Translates an array. + /// + /// The array to be translated. + public void Translate(ref bool[] array) + { + if (!TranslateNullable(array)) + { + return; + } + + int count = array.Length; + _writer.Write(count); + + for (int i = 0; i < count; i++) + { + _writer.Write(array[i]); + } + } + + /// + /// Translates a byte. + /// + /// The value to be translated. + public void Translate(ref byte value) + { + _writer.Write(value); + } + + /// + /// Translates a short. + /// + /// The value to be translated. + public void Translate(ref short value) + { + _writer.Write(value); + } + + /// + /// Translates an unsigned short. + /// + /// The value to be translated. + public void Translate(ref ushort value) + { + _writer.Write(value); + } + + /// + /// Translates an integer. + /// + /// The value to be translated. + public void Translate(ref int value) + { + _writer.Write(value); + } + + /// + public void Translate(ref uint unsignedInteger) => _writer.Write(unsignedInteger); + + /// + /// Translates an array. + /// + /// The array to be translated. + public void Translate(ref int[] array) + { + if (!TranslateNullable(array)) + { + return; + } + + int count = array.Length; + _writer.Write(count); + + for (int i = 0; i < count; i++) + { + _writer.Write(array[i]); + } + } + + /// + /// Translates a long. + /// + /// The value to be translated. + public void Translate(ref long value) + { + _writer.Write(value); + } + + /// + /// Translates a double. + /// + /// The value to be translated. + public void Translate(ref double value) + { + _writer.Write(value); + } + + /// + /// Translates a TaskHostParameters. + /// + /// The TaskHostParameters to be translated. + public void Translate(ref TaskHostParameters value) + { + string runtime = value.Runtime; + string architecture = value.Architecture; + string dotnetHostPath = value.DotnetHostPath; + string msBuildAssemblyPath = value.MSBuildAssemblyPath; + + Translate(ref runtime); + Translate(ref architecture); + Translate(ref dotnetHostPath); + Translate(ref msBuildAssemblyPath); + + bool hasTaskHostFactory = value.TaskHostFactoryExplicitlyRequested.HasValue; + _writer.Write(hasTaskHostFactory); + if (hasTaskHostFactory) + { + _writer.Write(value.TaskHostFactoryExplicitlyRequested.Value); + } + } + + /// + /// Translates a string. + /// + /// The value to be translated. + public void Translate(ref string value) + { + if (!TranslateNullable(value)) + { + return; + } + + _writer.Write(value); + } + + /// + /// Translates a string array. + /// + /// The array to be translated. + public void Translate(ref string[] array) + { + if (!TranslateNullable(array)) + { + return; + } + + int count = array.Length; + _writer.Write(count); + + for (int i = 0; i < count; i++) + { + _writer.Write(array[i]); + } + } + + /// + /// Translates a list of strings + /// + /// The list to be translated. + public void Translate(ref List list) + { + if (!TranslateNullable(list)) + { + return; + } + + int count = list.Count; + _writer.Write(count); + + for (int i = 0; i < count; i++) + { + _writer.Write(list[i]); + } + } + + /// + public void Translate(ref HashSet set) + { + if (!TranslateNullable(set)) + { + return; + } + + int count = set.Count; + _writer.Write(count); + + foreach (var item in set) + { + _writer.Write(item); + } + } + + /// + /// Translates a list of T using an + /// + /// The list to be translated. + /// The translator to use for the items in the list + /// A TaskItemType + public void Translate(ref List list, ObjectTranslator objectTranslator) + { + if (!TranslateNullable(list)) + { + return; + } + + int count = list.Count; + _writer.Write(count); + + for (int i = 0; i < count; i++) + { + T value = list[i]; + objectTranslator(this, ref value); + } + } + + /// + public void Translate(ref List list, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) + { + if (!TranslateNullable(list)) + { + return; + } + + int count = list.Count; + _writer.Write(count); + + for (int i = 0; i < count; i++) + { + T value = list[i]; + objectTranslator(this, valueFactory, ref value); + } + } + + /// + /// Translates a list of T using an + /// + /// The list to be translated. + /// The translator to use for the items in the list + /// factory to create the IList + /// A TaskItemType + /// IList subtype + public void Translate(ref IList list, ObjectTranslator objectTranslator, NodePacketCollectionCreator collectionFactory) where L : IList + { + if (!TranslateNullable(list)) + { + return; + } + + int count = list.Count; + _writer.Write(count); + + for (int i = 0; i < count; i++) + { + T value = list[i]; + objectTranslator(this, ref value); + } + } + + /// + public void Translate(ref IList list, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory, NodePacketCollectionCreator collectionFactory) where L : IList + { + if (!TranslateNullable(list)) + { + return; + } + + int count = list.Count; + _writer.Write(count); + + for (int i = 0; i < count; i++) + { + T value = list[i]; + objectTranslator(this, valueFactory, ref value); + } + } + + /// + /// Translates a collection of T into the specified type using an and + /// + /// The collection to be translated. + /// The translator to use for the values in the collection. + /// The factory to create the ICollection. + /// The type contained in the collection. + /// The type of collection to be created. + public void Translate(ref ICollection collection, ObjectTranslator objectTranslator, NodePacketCollectionCreator collectionFactory) where L : ICollection + { + if (!TranslateNullable(collection)) + { + return; + } + + _writer.Write(collection.Count); + + foreach (T item in collection) + { + T value = item; + objectTranslator(this, ref value); + } + } + + /// + /// Translates a DateTime. + /// + /// The value to be translated. + public void Translate(ref DateTime value) + { + DateTimeKind kind = value.Kind; + TranslateEnum(ref kind, (int)kind); + _writer.Write(value.Ticks); + } + + /// + /// Translates a TimeSpan. + /// + /// The value to be translated. + public void Translate(ref TimeSpan value) + { + _writer.Write(value.Ticks); + } + + // MSBuildTaskHost is based on CLR 3.5, which does not have the 6-parameter constructor for BuildEventContext. + // However, it also does not ever need to translate BuildEventContexts, so it should be perfectly safe to + // compile this method out of that assembly. +#if !CLR2COMPATIBILITY + + /// + /// Translates a BuildEventContext + /// + /// + /// This method exists only because there is no serialization method built into the BuildEventContext + /// class, and it lives in Framework and we don't want to add a public method to it. + /// + /// The context to be translated. + public void Translate(ref BuildEventContext value) + { + _writer.Write(value.SubmissionId); + _writer.Write(value.NodeId); + _writer.Write(value.EvaluationId); + _writer.Write(value.ProjectInstanceId); + _writer.Write(value.ProjectContextId); + _writer.Write(value.TargetId); + _writer.Write(value.TaskId); + } +#endif + + /// + /// Translates a CultureInfo + /// + /// The CultureInfo + public void TranslateCulture(ref CultureInfo value) + { + _writer.Write(value.Name); + } + + /// + /// Translates an enumeration. + /// + /// The enumeration type. + /// The enumeration instance to be translated. + /// The enumeration value as an integer. + /// This is a bit ugly, but it doesn't seem like a nice method signature is possible because + /// you can't pass the enum type as a reference and constrain the generic parameter to Enum. Nor + /// can you simply pass as ref Enum, because an enum instance doesn't match that function signature. + /// Finally, converting the enum to an int assumes that we always want to transport enums as ints. This + /// works in all of our current cases, but certainly isn't perfectly generic. + public void TranslateEnum(ref T value, int numericValue) + where T : struct, Enum + { + _writer.Write(numericValue); + } + + public void TranslateException(ref Exception value) + { + if (!TranslateNullable(value)) + { + return; + } + + BuildExceptionBase.WriteExceptionToTranslator(this, value); + } + + /// + /// Translates an object implementing INodePacketTranslatable. + /// + /// The reference type. + /// The value to be translated. + public void Translate(ref T value) + where T : ITranslatable, new() + { + if (!TranslateNullable(value)) + { + return; + } + + value.Translate(this); + } + + /// + /// Translates a byte array + /// + /// The byte array to be translated + public void Translate(ref byte[] byteArray) + { + var length = byteArray?.Length ?? 0; + Translate(ref byteArray, ref length); + } + + /// + /// Translates a byte array + /// + /// The array to be translated. + /// The length of array which will be used in translation + public void Translate(ref byte[] byteArray, ref int length) + { + if (!TranslateNullable(byteArray)) + { + return; + } + + _writer.Write(length); + if (length > 0) + { + _writer.Write(byteArray, 0, length); + } + } + + /// + /// Translates an array of objects implementing INodePacketTranslatable. + /// + /// The reference type. + /// The array to be translated. + public void TranslateArray(ref T[] array) + where T : ITranslatable, new() + { + if (!TranslateNullable(array)) + { + return; + } + + int count = array.Length; + _writer.Write(count); + + for (int i = 0; i < count; i++) + { + array[i].Translate(this); + } + } + + /// + public void TranslateArray(ref T[] array, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) + { + if (!TranslateNullable(array)) + { + return; + } + + int count = array.Length; + _writer.Write(count); + + for (int i = 0; i < count; i++) + { + objectTranslator(this, valueFactory, ref array[i]); + } + } + + /// + /// Translates a dictionary of { string, string }. + /// + /// The dictionary to be translated. + /// The comparer used to instantiate the dictionary. + public void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer) + { + IDictionary copy = dictionary; + TranslateDictionary(ref copy, (NodePacketCollectionCreator>)null); + } + + /// + /// Translates a dictionary of { string, string } adding additional entries. + /// + /// The dictionary to be translated. + /// The comparer used to instantiate the dictionary. + /// Additional entries to be translated. + /// Additional entries keys. + /// + /// This overload is needed for a workaround concerning serializing BuildResult with a version. + /// It serializes additional entries together with the main dictionary. + /// + public void TranslateDictionary(ref IDictionary dictionary, IEqualityComparer comparer, ref Dictionary additionalEntries, HashSet additionalEntriesKeys) + { + // Translate whether object is null + if ((dictionary is null) && ((additionalEntries is null) || (additionalEntries.Count == 0))) + { + _writer.Write(false); + return; + } + else + { + // Translate that object is not null + _writer.Write(true); + } + + // Writing a dictionary, additional entries and special key if dictionary was null. We need the special key for distinguishing whether the initial dictionary was null or empty. + int count = (dictionary is null ? 1 : 0) + + (additionalEntries is null ? 0 : additionalEntries.Count) + + (dictionary is null ? 0 : dictionary.Count); + + _writer.Write(count); + + // If the dictionary was null, serialize a special key SpecialKeyForDictionaryBeingNull. + if (dictionary is null) + { + string key = SpecialKeyForDictionaryBeingNull; + Translate(ref key); + string value = string.Empty; + Translate(ref value); + } + + // Serialize additional entries + if (additionalEntries is not null) + { + foreach (KeyValuePair pair in additionalEntries) + { + string key = pair.Key; + Translate(ref key); + string value = pair.Value; + Translate(ref value); + } + } + + // Serialize dictionary + if (dictionary is not null) + { + foreach (KeyValuePair pair in dictionary) + { + string key = pair.Key; + Translate(ref key); + string value = pair.Value; + Translate(ref value); + } + } + } + + public void TranslateDictionary(ref IDictionary dictionary, NodePacketCollectionCreator> dictionaryCreator) + { + if (!TranslateNullable(dictionary)) + { + return; + } + + int count = dictionary.Count; + _writer.Write(count); + + foreach (KeyValuePair pair in dictionary) + { + string key = pair.Key; + Translate(ref key); + string value = pair.Value; + Translate(ref value); + } + } + + public void TranslateDictionary( + ref IDictionary dictionary, + ObjectTranslator keyTranslator, + ObjectTranslator valueTranslator, + NodePacketCollectionCreator> collectionCreator) + { + if (!TranslateNullable(dictionary)) + { + return; + } + + int count = dictionary.Count; + _writer.Write(count); + + foreach (KeyValuePair pair in dictionary) + { + K key = pair.Key; + keyTranslator(this, ref key); + V value = pair.Value; + valueTranslator(this, ref value); + } + } + + /// + public void TranslateDictionary( + ref IDictionary dictionary, + ObjectTranslator keyTranslator, + ObjectTranslatorWithValueFactory valueTranslator, + NodePacketValueFactory valueFactory, + NodePacketCollectionCreator> collectionCreator) + { + if (!TranslateNullable(dictionary)) + { + return; + } + + int count = dictionary.Count; + _writer.Write(count); + + foreach (KeyValuePair pair in dictionary) + { + K key = pair.Key; + keyTranslator(this, ref key); + V value = pair.Value; + valueTranslator(this, valueFactory, ref value); + } + } + + /// + public void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) + where T : class + { + if (!TranslateNullable(dictionary)) + { + return; + } + + int count = dictionary.Count; + _writer.Write(count); + + foreach (KeyValuePair pair in dictionary) + { + string key = pair.Key; + Translate(ref key); + T value = pair.Value; + objectTranslator(this, valueFactory, ref value); + } + } + + /// + public void TranslateDictionary(ref D dictionary, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) + where D : IDictionary, new() + where T : class + { + if (!TranslateNullable(dictionary)) + { + return; + } + + int count = dictionary.Count; + _writer.Write(count); + + foreach (KeyValuePair pair in dictionary) + { + string key = pair.Key; + Translate(ref key); + T value = pair.Value; + objectTranslator(this, valueFactory, ref value); + } + } + + /// + public void TranslateDictionary(ref D dictionary, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory, NodePacketCollectionCreator dictionaryCreator) + where D : IDictionary + where T : class + { + if (!TranslateNullable(dictionary)) + { + return; + } + + int count = dictionary.Count; + _writer.Write(count); + + foreach (KeyValuePair pair in dictionary) + { + string key = pair.Key; + Translate(ref key); + T value = pair.Value; + objectTranslator(this, valueFactory, ref value); + } + } + + /// + /// Translates a dictionary of { string, DateTime }. + /// + /// The dictionary to be translated. + /// Key comparer + public void TranslateDictionary(ref Dictionary dictionary, StringComparer comparer) + { + if (!TranslateNullable(dictionary)) + { + return; + } + + int count = dictionary.Count; + _writer.Write(count); + foreach (KeyValuePair kvp in dictionary) + { + string key = kvp.Key; + DateTime val = kvp.Value; + Translate(ref key); + Translate(ref val); + } + } + + /// + /// Writes out the boolean which says if this object is null or not. + /// + /// The object to test. + /// The type of object to test. + /// True if the object should be written, false otherwise. + public bool TranslateNullable(T value) + { + bool haveRef = (value != null); + _writer.Write(haveRef); + return haveRef; + } + + public void WithInterning(IEqualityComparer comparer, int initialCapacity, Action internBlock) + { + if (_isInterning) + { + throw new InvalidOperationException("Cannot enter recursive intern block."); + } + + // Every new scope requires the interner's state to be reset. + _interner ??= new InterningWriteTranslator(); + _interner.Setup(comparer, initialCapacity); + + // Temporaily swap our writer with the interner. + // This forwards all writes to this translator into the interning buffer, so that any non-interned + // writes which are interleaved will be in the correct order. + BinaryWriter streamWriter = _writer; + _writer = _interner.Writer; + _isInterning = true; + + try + { + internBlock(this); + } + finally + { + _writer = streamWriter; + _isInterning = false; + } + + // Write the interned buffer into the real output stream. + _interner.Translate(this); + } + + public void Intern(ref string str, bool nullable = true) + { + if (!_isInterning) + { + Translate(ref str); + return; + } + + if (nullable && !TranslateNullable(str)) + { + return; + } + + _interner.Intern(str); + } + + public void Intern(ref string[] array) + { + if (!_isInterning) + { + Translate(ref array); + return; + } + + if (!TranslateNullable(array)) + { + return; + } + + int count = array.Length; + Translate(ref count); + + for (int i = 0; i < count; i++) + { + _interner.Intern(array[i]); + } + } + + public void InternPath(ref string str, bool nullable = true) + { + if (!_isInterning) + { + Translate(ref str); + return; + } + + if (nullable && !TranslateNullable(str)) + { + return; + } + + _interner.InternPath(str); + } + } + } +} diff --git a/src/MSBuildTaskHost/Framework/BuildEngineResult.cs b/src/MSBuildTaskHost/Framework/BuildEngineResult.cs new file mode 100644 index 00000000000..4371726a8a3 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/BuildEngineResult.cs @@ -0,0 +1,64 @@ +// 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; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Microsoft.Build.Framework +{ + /// + /// This structure is used to return the result of the build and the target outputs. + /// + [Serializable] + [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Would require a public API change -- currently we're trying to keep our surface area static.")] + public struct BuildEngineResult + { + /// + /// Did the build pass or fail + /// + private bool buildResult; + + /// + /// Target outputs by project + /// + private List> targetOutputsPerProject; + + /// + /// The constructor takes the result of the build and a list of the target outputs per project + /// + public BuildEngineResult(bool result, List> targetOutputsPerProject) + { + buildResult = result; + this.targetOutputsPerProject = targetOutputsPerProject; + if (this.targetOutputsPerProject == null) + { + this.targetOutputsPerProject = new List>(); + } + } + + /// + /// Did the build pass or fail. True means the build succeeded, False means the build failed. + /// + public readonly bool Result + { + get + { + return buildResult; + } + } + + /// + /// Outputs of the targets per project. + /// + public IList> TargetOutputsPerProject + { + get + { + return targetOutputsPerProject; + } + } + } +} diff --git a/src/MSBuildTaskHost/Framework/BuildEnvironmentState.cs b/src/MSBuildTaskHost/Framework/BuildEnvironmentState.cs new file mode 100644 index 00000000000..9743b2a5eab --- /dev/null +++ b/src/MSBuildTaskHost/Framework/BuildEnvironmentState.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +namespace Microsoft.Build.Framework +{ + /// + /// Class to encapsulate state that was stored in BuildEnvironmentHelper. + /// + /// + /// This should be deleted when BuildEnvironmentHelper can be moved into Framework. + /// + internal static class BuildEnvironmentState + { + internal static bool s_runningInVisualStudio = false; + internal static bool s_runningTests = false; + } +} diff --git a/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionBase.cs b/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionBase.cs new file mode 100644 index 00000000000..869b74070b6 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionBase.cs @@ -0,0 +1,157 @@ +// 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; +using System.Diagnostics; +using System.IO; +using System.Runtime.Serialization; +using Microsoft.Build.BackEnd; +using Microsoft.Build.Shared; + +namespace Microsoft.Build.Framework.BuildException; + +public abstract class BuildExceptionBase : Exception +{ + private string? _remoteTypeName; + private string? _remoteStackTrace; + + private protected BuildExceptionBase() + : base() + { } + + private protected BuildExceptionBase(string? message) + : base(message) + { } + + private protected BuildExceptionBase( + string? message, + Exception? inner) + : base(message, inner) + { } + + // This is needed to allow opting back in to BinaryFormatter serialization +#if NET8_0_OR_GREATER + [Obsolete(DiagnosticId = "SYSLIB0051")] +#endif + private protected BuildExceptionBase(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + + public override string? StackTrace => string.IsNullOrEmpty(_remoteStackTrace) ? base.StackTrace : _remoteStackTrace; + + public override string ToString() => string.IsNullOrEmpty(_remoteTypeName) ? base.ToString() : $"{_remoteTypeName}->{base.ToString()}"; + + /// + /// Override this method to recover subtype-specific state from the remote exception. + /// + protected virtual void InitializeCustomState(IDictionary? customKeyedSerializedData) + { } + + /// + /// Override this method to provide subtype-specific state to be serialized. + /// + /// + protected virtual IDictionary? FlushCustomState() + { + return null; + } + + private void InitializeFromRemoteState(BuildExceptionRemoteState remoteState) + { + _remoteTypeName = remoteState.RemoteTypeName; + _remoteStackTrace = remoteState.RemoteStackTrace; + base.Source = remoteState.Source; + base.HelpLink = remoteState.HelpLink; + base.HResult = remoteState.HResult; + if (remoteState.Source != null) + { + InitializeCustomState(remoteState.CustomKeyedSerializedData); + } + } + + internal static void WriteExceptionToTranslator(ITranslator translator, Exception exception) + { + BinaryWriter writer = translator.Writer; + writer.Write(exception.InnerException != null); + if (exception.InnerException != null) + { + WriteExceptionToTranslator(translator, exception.InnerException); + } + + string serializationType = BuildExceptionSerializationHelper.GetExceptionSerializationKey(exception.GetType()); + writer.Write(serializationType); + writer.Write(exception.Message); + writer.WriteOptionalString(exception.StackTrace); + writer.WriteOptionalString(exception.Source); + writer.WriteOptionalString(exception.HelpLink); + // HResult is completely protected up till net4.5 +#if NET || NET45_OR_GREATER + int? hresult = exception.HResult; +#else + int? hresult = null; +#endif + writer.WriteOptionalInt32(hresult); + + IDictionary? customKeyedSerializedData = (exception as BuildExceptionBase)?.FlushCustomState(); + if (customKeyedSerializedData == null) + { + writer.Write((byte)0); + } + else + { + writer.Write((byte)1); + writer.Write(customKeyedSerializedData.Count); + foreach (var pair in customKeyedSerializedData) + { + writer.Write(pair.Key); + writer.WriteOptionalString(pair.Value); + } + } + + Debug.Assert((exception.Data?.Count ?? 0) == 0, + "Exception Data is not supported in BuildTransferredException"); + } + + internal static Exception ReadExceptionFromTranslator(ITranslator translator) + { + BinaryReader reader = translator.Reader; + Exception? innerException = null; + if (reader.ReadBoolean()) + { + innerException = ReadExceptionFromTranslator(translator); + } + + string serializationType = reader.ReadString(); + string message = reader.ReadString(); + string? deserializedStackTrace = reader.ReadOptionalString(); + string? source = reader.ReadOptionalString(); + string? helpLink = reader.ReadOptionalString(); + int hResult = reader.ReadOptionalInt32() ?? 0; + + IDictionary? customKeyedSerializedData = null; + if (reader.ReadByte() == 1) + { + int count = reader.ReadInt32(); + customKeyedSerializedData = new Dictionary(count, StringComparer.CurrentCulture); + + for (int i = 0; i < count; i++) + { + customKeyedSerializedData[reader.ReadString()] = reader.ReadOptionalString(); + } + } + + BuildExceptionBase exception = BuildExceptionSerializationHelper.CreateExceptionFactory(serializationType)(message, innerException); + + exception.InitializeFromRemoteState( + new BuildExceptionRemoteState( + serializationType, + deserializedStackTrace, + source, + helpLink, + hResult, + customKeyedSerializedData)); + + return exception; + } +} diff --git a/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionRemoteState.cs b/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionRemoteState.cs new file mode 100644 index 00000000000..b4d8786f43d --- /dev/null +++ b/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionRemoteState.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Build.Framework.BuildException; + +/// +/// Remote exception internal data serving as the source for the exception deserialization. +/// +internal class BuildExceptionRemoteState +{ + public BuildExceptionRemoteState( + string remoteTypeName, + string? remoteStackTrace, + string? source, + string? helpLink, + int hResult, + IDictionary? customKeyedSerializedData) + { + RemoteTypeName = remoteTypeName; + RemoteStackTrace = remoteStackTrace; + Source = source; + HelpLink = helpLink; + HResult = hResult; + CustomKeyedSerializedData = customKeyedSerializedData; + } + + public string RemoteTypeName { get; init; } + public string? RemoteStackTrace { get; init; } + public string? Source { get; init; } + public string? HelpLink { get; init; } + public int HResult { get; init; } + public IDictionary? CustomKeyedSerializedData { get; init; } +} diff --git a/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionSerializationHelper.cs b/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionSerializationHelper.cs new file mode 100644 index 00000000000..030fd532e00 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionSerializationHelper.cs @@ -0,0 +1,91 @@ +// 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; +using System.Threading; + +namespace Microsoft.Build.Framework.BuildException +{ + internal static class BuildExceptionSerializationHelper + { + public class TypeConstructionTuple + { + public TypeConstructionTuple(Type type, Func factory) + { + Type = type; + Factory = factory; + } + + public Type Type { get; } + public Func Factory { get; } + } + + private static Dictionary>? s_exceptionFactories; + + private static readonly Func s_defaultFactory = + (message, innerException) => new GenericBuildTransferredException(message, innerException); + + internal static bool IsSupportedExceptionType(Type type) + { + return type.IsClass && + !type.IsAbstract && + type.IsSubclassOf(typeof(Exception)) && + type.IsSubclassOf(typeof(BuildExceptionBase)); + } + + internal static void InitializeSerializationContract(params TypeConstructionTuple[] exceptionsAllowlist) + { + InitializeSerializationContract((IEnumerable)exceptionsAllowlist); + } + + internal static void InitializeSerializationContract(IEnumerable exceptionsAllowlist) + { + if (s_exceptionFactories != null) + { + return; + } + + var exceptionFactories = new Dictionary>(); + + foreach (TypeConstructionTuple typeConstructionTuple in exceptionsAllowlist) + { + Type exceptionType = typeConstructionTuple.Type; + Func exceptionFactory = typeConstructionTuple.Factory; + + if (!IsSupportedExceptionType(exceptionType)) + { + EscapeHatches.ThrowInternalError($"Type {exceptionType.FullName} is not recognized as a build exception type."); + } + + string key = GetExceptionSerializationKey(exceptionType); + exceptionFactories[key] = exceptionFactory; + } + + if (Interlocked.Exchange(ref s_exceptionFactories, exceptionFactories) != null) + { + EscapeHatches.ThrowInternalError("Serialization contract was already initialized."); + } + } + + internal static string GetExceptionSerializationKey(Type exceptionType) + { + return exceptionType.FullName ?? exceptionType.ToString(); + } + + internal static Func CreateExceptionFactory(string serializationType) + { + Func? factory = null; + if (s_exceptionFactories == null) + { + EscapeHatches.ThrowInternalError("Serialization contract was not initialized."); + } + else + { + s_exceptionFactories.TryGetValue(serializationType, out factory); + } + + return factory ?? s_defaultFactory; + } + } +} diff --git a/src/MSBuildTaskHost/Framework/BuildException/GenericBuildTransferredException.cs b/src/MSBuildTaskHost/Framework/BuildException/GenericBuildTransferredException.cs new file mode 100644 index 00000000000..0c0261c80d5 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/BuildException/GenericBuildTransferredException.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Build.Framework.BuildException; + +/// +/// A catch-all type for remote exceptions that we don't know how to deserialize. +/// +internal sealed class GenericBuildTransferredException : BuildExceptionBase +{ + public GenericBuildTransferredException() + : base() + { } + + internal GenericBuildTransferredException( + string message, + Exception? inner) + : base(message, inner) + { } +} diff --git a/src/MSBuildTaskHost/Framework/ChangeWaves.cs b/src/MSBuildTaskHost/Framework/ChangeWaves.cs new file mode 100644 index 00000000000..d40cd86c8f3 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/ChangeWaves.cs @@ -0,0 +1,215 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +#if DEBUG +using System.Diagnostics; +#endif +using System.Linq; + +#nullable disable + +namespace Microsoft.Build.Framework +{ + internal enum ChangeWaveConversionState + { + NotConvertedYet, + Valid, + InvalidFormat, + OutOfRotation + } + + /// + /// Coupled together with the MSBUILDDISABLEFEATURESFROMVERSION environment variable, + /// this class acts as a way to make risky changes while giving customers an opt-out. + /// + /// See docs here: https://github.com/dotnet/msbuild/blob/main/documentation/wiki/ChangeWaves.md + /// For dev docs: https://github.com/dotnet/msbuild/blob/main/documentation/wiki/ChangeWaves-Dev.md + internal static class ChangeWaves + { + internal static readonly Version Wave17_10 = new Version(17, 10); + internal static readonly Version Wave17_12 = new Version(17, 12); + internal static readonly Version Wave17_14 = new Version(17, 14); + internal static readonly Version Wave18_3 = new Version(18, 3); + internal static readonly Version Wave18_4 = new Version(18, 4); + internal static readonly Version Wave18_5 = new Version(18, 5); + internal static readonly Version[] AllWaves = [Wave17_10, Wave17_12, Wave17_14, Wave18_3, Wave18_4, Wave18_5]; + + /// + /// Special value indicating that all features behind all Change Waves should be enabled. + /// + internal static readonly Version EnableAllFeatures = new Version(999, 999); + +#if DEBUG + /// + /// True if has been called. + /// + private static bool _runningTests = false; +#endif + + /// + /// The lowest wave in the current rotation of Change Waves. + /// + internal static Version LowestWave + { + get + { + return AllWaves[0]; + } + } + + /// + /// The highest wave in the current rotation of Change Waves. + /// + internal static Version HighestWave + { + get + { + return AllWaves[AllWaves.Length - 1]; + } + } + + /// + /// Checks the conditions for whether or not we want ApplyChangeWave to be called again. + /// + private static bool ShouldApplyChangeWave + { + get + { + return ConversionState == ChangeWaveConversionState.NotConvertedYet || _cachedWave == null; + } + } + + private static Version _cachedWave; + + /// + /// The current disabled wave. + /// + internal static Version DisabledWave + { + get + { + if (ShouldApplyChangeWave) + { + ApplyChangeWave(); + } + + return _cachedWave; + } + } + + private static ChangeWaveConversionState _state; + + /// + /// The status of how the disabled wave was set. + /// + internal static ChangeWaveConversionState ConversionState + { + get + { + return _state; + } + set + { + // Keep state persistent. + if (_state == ChangeWaveConversionState.NotConvertedYet) + { + _state = value; + } + } + } + + /// + /// Read from environment variable `MSBUILDDISABLEFEATURESFROMVERSION`, correct it if required, cache it and its ConversionState. + /// + internal static void ApplyChangeWave() + { + // Once set, change wave should not need to be set again. + if (!ShouldApplyChangeWave) + { + return; + } + + string msbuildDisableFeaturesFromVersion = Environment.GetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION"); + + // Most common case, `MSBUILDDISABLEFEATURESFROMVERSION` unset + if (string.IsNullOrEmpty(msbuildDisableFeaturesFromVersion)) + { + ConversionState = ChangeWaveConversionState.Valid; + _cachedWave = ChangeWaves.EnableAllFeatures; + } + else if (!TryParseVersion(msbuildDisableFeaturesFromVersion, out _cachedWave)) + { + ConversionState = ChangeWaveConversionState.InvalidFormat; + _cachedWave = ChangeWaves.EnableAllFeatures; + } + else if (_cachedWave == EnableAllFeatures || Array.IndexOf(AllWaves, _cachedWave) >= 0) + { + ConversionState = ChangeWaveConversionState.Valid; + } + else if (_cachedWave < LowestWave) + { + ConversionState = ChangeWaveConversionState.OutOfRotation; + _cachedWave = LowestWave; + } + else if (_cachedWave > HighestWave) + { + ConversionState = ChangeWaveConversionState.OutOfRotation; + _cachedWave = HighestWave; + } + // _cachedWave is somewhere between valid waves, find the next valid version. + else + { + _cachedWave = AllWaves.First((x) => x > _cachedWave); + ConversionState = ChangeWaveConversionState.Valid; + } + } + + /// + /// Determines whether features behind the given wave are enabled. + /// + /// The version to compare. + /// A bool indicating whether the change wave is enabled. + internal static bool AreFeaturesEnabled(Version wave) + { + ApplyChangeWave(); + +#if DEBUG + Debug.Assert(_runningTests || Array.IndexOf(AllWaves, wave) >= 0, $"Change wave version {wave} is invalid"); +#endif + + return wave < _cachedWave; + } + + /// + /// Resets the state and value of the currently disabled version. + /// Used for testing only. + /// + internal static void ResetStateForTests() + { +#if DEBUG + _runningTests = true; +#endif + _cachedWave = null; + _state = ChangeWaveConversionState.NotConvertedYet; + } + + private static bool TryParseVersion(string stringVersion, out Version version) + { +#if FEATURE_NET35_TASKHOST + try + { + version = new Version(stringVersion); + return true; + } + catch (Exception) + { + version = null; + return false; + } +#else + return Version.TryParse(stringVersion, out version); +#endif + } + } +} diff --git a/src/MSBuildTaskHost/Framework/ErrorUtilities.cs b/src/MSBuildTaskHost/Framework/ErrorUtilities.cs new file mode 100644 index 00000000000..e9b9275d7c8 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/ErrorUtilities.cs @@ -0,0 +1,62 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace Microsoft.Build.Framework +{ + // TODO: this should be unified with Shared\ErrorUtilities.cs, but it is hard to untangle everything + // because some of the errors there will use localized resources from different assemblies, + // which won't be referenceable in Framework. + + internal static class FrameworkErrorUtilities + { + /// + /// This method should be used in places where one would normally put + /// an "assert". It should be used to validate that our assumptions are + /// true, where false would indicate that there must be a bug in our + /// code somewhere. This should not be used to throw errors based on bad + /// user input or anything that the user did wrong. + /// + /// + /// + internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string unformattedMessage) + { + if (!condition) + { + ThrowInternalError(unformattedMessage, innerException: null, args: null); + } + } + + /// + /// Helper to throw an InternalErrorException when the specified parameter is null. + /// This should be used ONLY if this would indicate a bug in MSBuild rather than + /// anything caused by user action. + /// + /// The value of the argument. + /// Parameter that should not be null. + internal static void VerifyThrowInternalNull([NotNull] object? parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) + { + if (parameter is null) + { + ThrowInternalError("{0} unexpectedly null", innerException: null, args: parameterName); + } + } + + /// + /// Throws InternalErrorException. + /// This is only for situations that would mean that there is a bug in MSBuild itself. + /// + [DoesNotReturn] + internal static void ThrowInternalError(string message, Exception? innerException, params object?[]? args) + { + throw new InternalErrorException( + args is null ? + message : + string.Format(message, args), + innerException); + } + } +} diff --git a/src/MSBuildTaskHost/Framework/FileUtilities.cs b/src/MSBuildTaskHost/Framework/FileUtilities.cs new file mode 100644 index 00000000000..23cc707631b --- /dev/null +++ b/src/MSBuildTaskHost/Framework/FileUtilities.cs @@ -0,0 +1,133 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; + +namespace Microsoft.Build.Framework +{ + // TODO: this should be unified with Shared\FileUtilities, but it is hard to untangle everything in one go. + // Moved some of the methods here for now. + + /// + /// This class contains utility methods for file IO. + /// Functions from FileUtilities are transferred here as part of the effort to remove Shared files. + /// + internal static class FrameworkFileUtilities + { + internal static readonly char[] Slashes = ['/', '\\']; + + /// + /// Indicates if the given character is a slash. + /// + /// + /// true, if slash + internal static bool IsSlash(char c) + { + return (c == Path.DirectorySeparatorChar) || (c == Path.AltDirectorySeparatorChar); + } + + /// + /// Indicates if the given file-spec ends with a slash. + /// + /// The file spec. + /// true, if file-spec has trailing slash + internal static bool EndsWithSlash(string fileSpec) + { + return (fileSpec.Length > 0) + ? IsSlash(fileSpec[fileSpec.Length - 1]) + : false; + } + + internal static string FixFilePath(string path) + { + return string.IsNullOrEmpty(path) || Path.DirectorySeparatorChar == '\\' ? path : path.Replace('\\', '/'); + } + + /// + /// If the given path doesn't have a trailing slash then add one. + /// If the path is an empty string, does not modify it. + /// + /// The path to check. + /// A path with a slash. + internal static string EnsureTrailingSlash(string fileSpec) + { + fileSpec = FixFilePath(fileSpec); + if (fileSpec.Length > 0 && !IsSlash(fileSpec[fileSpec.Length - 1])) + { + fileSpec += Path.DirectorySeparatorChar; + } + + return fileSpec; + } + + /// + /// Ensures the path does not have a trailing slash. + /// + internal static string EnsureNoTrailingSlash(string path) + { + path = FixFilePath(path); + if (EndsWithSlash(path)) + { + path = path.Substring(0, path.Length - 1); + } + + return path; + } + +#if !TASKHOST + /// + /// If the given path doesn't have a trailing slash then add one. + /// + /// The absolute path to check. + /// An absolute path with a trailing slash. + internal static AbsolutePath EnsureTrailingSlash(AbsolutePath path) + { + return new AbsolutePath(EnsureTrailingSlash(path.Value), + original: path.OriginalValue, + ignoreRootedCheck: true); + } + + /// + /// Ensures the absolute path does not have a trailing slash. + /// + /// The absolute path to check. + /// An absolute path without a trailing slash. + internal static AbsolutePath EnsureNoTrailingSlash(AbsolutePath path) + { + return new AbsolutePath(EnsureNoTrailingSlash(path.Value), + original: path.OriginalValue, + ignoreRootedCheck: true); + } + + /// + /// Gets the canonicalized full path of the provided path. + /// Resolves relative segments like "." and "..". Fixes directory separators. + /// ASSUMES INPUT IS ALREADY UNESCAPED. + /// + internal static AbsolutePath NormalizePath(AbsolutePath path) + { + return new AbsolutePath(FixFilePath(Path.GetFullPath(path.Value)), + original: path.OriginalValue, + ignoreRootedCheck: true); + } + + /// + /// Resolves relative segments like "." and "..". + /// ASSUMES INPUT IS ALREADY UNESCAPED. + /// + internal static AbsolutePath RemoveRelativeSegments(AbsolutePath path) + { + return new AbsolutePath(Path.GetFullPath(path.Value), + original: path.OriginalValue, + ignoreRootedCheck: true); + } + + internal static AbsolutePath FixFilePath(AbsolutePath path) + { + return new AbsolutePath(FixFilePath(path.Value), + original: path.OriginalValue, + ignoreRootedCheck: true); + } +#endif + } +} \ No newline at end of file diff --git a/src/MSBuildTaskHost/Framework/IBuildEngine3.cs b/src/MSBuildTaskHost/Framework/IBuildEngine3.cs new file mode 100644 index 00000000000..21c0b1fc22d --- /dev/null +++ b/src/MSBuildTaskHost/Framework/IBuildEngine3.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Collections.Generic; + +#nullable disable + +namespace Microsoft.Build.Framework +{ + /// + /// This interface extends IBuildEngine to provide a method allowing building + /// project files in parallel. + /// + public interface IBuildEngine3 : IBuildEngine2 + { + /// + /// This method allows tasks to initiate a build on a + /// particular project file. If the build is successful, the outputs + /// (if any) of the specified targets are returned. + /// + /// + /// 1) it is acceptable to pass null for both targetNames and targetOutputs + /// 2) if no targets are specified, the default targets are built + /// + /// + /// The project to build. + /// The targets in the project to build (can be null). + /// An array of hashtables of additional global properties to apply + /// to the child project (array entries can be null). + /// The key and value in the hashtable should both be strings. + /// A list of global properties which should be removed. + /// A tools version recognized by the Engine that will be used during this build (can be null). + /// Should the target outputs be returned in the BuildEngineResult + /// Returns a structure containing the success or failure of the build and the target outputs by project. + BuildEngineResult BuildProjectFilesInParallel( + string[] projectFileNames, + string[] targetNames, + IDictionary[] globalProperties, + IList[] removeGlobalProperties, + string[] toolsVersion, + bool returnTargetOutputs); + + /// + /// Informs the system that this task has a long-running out-of-process component and other work can be done in the + /// build while that work completes. + /// + /// + /// After calling , global process state like environment variables and current working directory + /// can change arbitrarily until returns. As a result, if you are going to depend on any of + /// that state, for instance by opening files by relative path, rather than calling + /// ITaskItem.GetMetadata("FullPath"), you must do so before calling . + /// The recommended pattern is to figure out what all the long-running work is and start it before yielding. + /// + void Yield(); + + /// + /// Waits to reacquire control after yielding. + /// + /// + /// This method must be called to regain control after has been called. + /// + void Reacquire(); + } +} diff --git a/src/MSBuildTaskHost/Framework/IConstrainedEqualityComparer.cs b/src/MSBuildTaskHost/Framework/IConstrainedEqualityComparer.cs new file mode 100644 index 00000000000..3ecc49524e9 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/IConstrainedEqualityComparer.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +#nullable disable + +namespace Microsoft.Build.Collections +{ + /// + /// Defines methods to support the comparison of objects for + /// equality over constrained inputs. + /// +#if TASKHOST + internal interface IConstrainedEqualityComparer : IEqualityComparer +#else + internal interface IConstrainedEqualityComparer : IEqualityComparer +#endif + { + /// + /// Determines whether the specified objects are equal, factoring in the specified bounds when comparing . + /// + bool Equals(T x, T y, int indexY, int length); + + /// + /// Returns a hash code for the specified object factoring in the specified bounds. + /// + int GetHashCode(T obj, int index, int length); + } +} diff --git a/src/MSBuildTaskHost/Framework/IExtendedBuildEventArgs.cs b/src/MSBuildTaskHost/Framework/IExtendedBuildEventArgs.cs new file mode 100644 index 00000000000..0c73ddb914f --- /dev/null +++ b/src/MSBuildTaskHost/Framework/IExtendedBuildEventArgs.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Build.Framework; + +/// +/// Interface for Extended EventArgs to allow enriching particular events with extended data. +/// Deriving from EventArgs will be deprecated soon and using Extended EventArgs is recommended for custom Event Args. +/// +public interface IExtendedBuildEventArgs +{ + /// + /// Unique string identifying type of extended data so receiver side knows how to interpret, deserialize and handle . + /// + string ExtendedType { get; set; } + + /// + /// Metadata of . + /// Example usage: + /// - data which needed in custom code to properly routing this message without interpreting/deserializing . + /// - simple extended data can be transferred in form of dictionary key-value per one extended property. + /// + Dictionary? ExtendedMetadata { get; set; } + + /// + /// Transparent data as string. + /// Custom code is responsible to serialize and deserialize this string to structured data - if needed. + /// Custom code can use any serialization they deem safe - e.g. json for textual data, base64 for binary data... + /// + string? ExtendedData { get; set; } +} diff --git a/src/MSBuildTaskHost/Framework/ITaskItem2.cs b/src/MSBuildTaskHost/Framework/ITaskItem2.cs new file mode 100644 index 00000000000..f245353a19b --- /dev/null +++ b/src/MSBuildTaskHost/Framework/ITaskItem2.cs @@ -0,0 +1,58 @@ +// 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; +using System.Runtime.InteropServices; + +#nullable disable + +namespace Microsoft.Build.Framework +{ + /// + /// This interface adds escaping support to the ITaskItem interface. + /// + [ComVisible(true)] + [Guid("ac6d5a59-f877-461b-88e3-b2f06fce0cb9")] + public interface ITaskItem2 : ITaskItem + { + /// + /// Gets or sets the item include value e.g. for disk-based items this would be the file path. + /// + /// + /// Taking the opportunity to fix the property name, although this doesn't + /// make it obvious it's an improvement on ItemSpec. + /// + string EvaluatedIncludeEscaped + { + get; + set; + } + + /// + /// Allows the values of metadata on the item to be queried. + /// + /// + /// Taking the opportunity to fix the property name, although this doesn't + /// make it obvious it's an improvement on GetMetadata. + /// + string GetMetadataValueEscaped(string metadataName); + + /// + /// Allows a piece of custom metadata to be set on the item. Assumes that the value passed + /// in is unescaped, and escapes the value as necessary in order to maintain its value. + /// + /// + /// Taking the opportunity to fix the property name, although this doesn't + /// make it obvious it's an improvement on SetMetadata. + /// + void SetMetadataValueLiteral(string metadataName, string metadataValue); + + /// + /// ITaskItem2 implementation which returns a clone of the metadata on this object. + /// Values returned are in their original escaped form. + /// + /// The cloned metadata, with values' escaping preserved. + IDictionary CloneCustomMetadataEscaped(); + } +} diff --git a/src/MSBuildTaskHost/Framework/ITranslatable.cs b/src/MSBuildTaskHost/Framework/ITranslatable.cs new file mode 100644 index 00000000000..fdddd566135 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/ITranslatable.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + /// + /// An interface representing an object which may be serialized by the node packet serializer. + /// + internal interface ITranslatable + { + /// + /// Reads or writes the packet to the serializer. + /// + void Translate(ITranslator translator); + } +} diff --git a/src/MSBuildTaskHost/Framework/ITranslator.cs b/src/MSBuildTaskHost/Framework/ITranslator.cs new file mode 100644 index 00000000000..71059a49272 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/ITranslator.cs @@ -0,0 +1,477 @@ +// 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; +using System.Globalization; +using System.IO; +using Microsoft.Build.Framework; + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + /// + /// This delegate is used for objects which do not have public parameterless constructors and must be constructed using + /// another method. When invoked, this delegate should return a new object which has been translated appropriately. + /// + /// The type to be translated. + internal delegate T NodePacketValueFactory(ITranslator translator); + + /// + /// Delegate for users that want to translate an arbitrary structure that doesn't implement (e.g. translating a complex collection) + /// + /// the translator + /// the object to translate + internal delegate void ObjectTranslator(ITranslator translator, ref T objectToTranslate); + + /// + /// Delegate for users that want to translate an arbitrary structure that doesn't implement (e.g. translating a complex collection) + /// + /// the translator + /// The factory to use to create the value. + /// the object to translate + internal delegate void ObjectTranslatorWithValueFactory(ITranslator translator, NodePacketValueFactory valueFactory, ref T objectToTranslate); + + /// + /// This delegate is used to create arbitrary collection types for serialization. + /// + /// The type of dictionary to be created. + internal delegate T NodePacketCollectionCreator(int capacity); + + /// + /// The serialization mode. + /// + internal enum TranslationDirection + { + /// + /// Indicates the serializer is operating in write mode. + /// + WriteToStream, + + /// + /// Indicates the serializer is operating in read mode. + /// + ReadFromStream + } + + /// + /// This interface represents an object which aids objects in serializing and + /// deserializing INodePackets. + /// + /// + /// The reason we bother with a custom serialization mechanism at all is two fold: + /// 1. The .Net serialization mechanism is inefficient, even if you implement ISerializable + /// with your own custom mechanism. This is because the serializer uses a bag called + /// SerializationInfo into which you are expected to drop all your data. This adds + /// an unnecessary level of indirection to the serialization routines and prevents direct, + /// efficient access to the byte-stream. + /// 2. You have to implement both a reader and writer part, which introduces the potential for + /// error should the classes be later modified. If the reader and writer methods are not + /// kept in perfect sync, serialization errors will occur. Our custom serializer eliminates + /// that by ensuring a single Translate method on a given object can handle both reads and + /// writes without referencing any field more than once. + /// + internal interface ITranslator : IDisposable + { + /// + /// Gets or sets the negotiated packet version between the communicating nodes. + /// This represents the minimum packet version supported by both the sender and receiver, + /// ensuring backward compatibility during cross-version communication. + /// + /// + /// This version is determined during the initial handshake between nodes and may differ + /// from NodePacketTypeExtensions.PacketVersion when nodes are running different MSBuild versions. + /// The negotiated version is used to conditionally serialize/deserialize fields that may + /// not be supported by older packet versions. + /// + byte NegotiatedPacketVersion { get; set; } + + /// + /// Returns the current serialization mode. + /// + TranslationDirection Mode + { + get; + } + + /// + /// Returns the binary reader. + /// + /// + /// This should ONLY be used when absolutely necessary for translation. It is generally unnecessary for the + /// translating object to know the direction of translation. Use one of the Translate methods instead. + /// + BinaryReader Reader + { + get; + } + + /// + /// Returns the binary writer. + /// + /// + /// This should ONLY be used when absolutely necessary for translation. It is generally unnecessary for the + /// translating object to know the direction of translation. Use one of the Translate methods instead. + /// + BinaryWriter Writer + { + get; + } + + /// + /// Translates a boolean. + /// + /// The value to be translated. + void Translate(ref bool value); + + /// + /// Translates an array. + /// + /// The array to be translated. + void Translate(ref bool[] array); + + /// + /// Translates a byte. + /// + /// The value to be translated. + void Translate(ref byte value); + + /// + /// Translates a short. + /// + /// The value to be translated. + void Translate(ref short value); + + /// + /// Translates a unsigned short. + /// + /// The value to be translated. + void Translate(ref ushort value); + + /// + /// Translates an integer. + /// + /// The value to be translated. + void Translate(ref int value); + + /// + /// Translates an unsigned integer. + /// + /// The unsigned integer to translate. + void Translate(ref uint unsignedInteger); + + /// + /// Translates an array. + /// + /// The array to be translated. + void Translate(ref int[] array); + + /// + /// Translates a long. + /// + /// The value to be translated. + void Translate(ref long value); + + /// + /// Translates a string. + /// + /// The value to be translated. + void Translate(ref string value); + + /// + /// Translates a double. + /// + /// The value to be translated. + void Translate(ref double value); + + /// + /// Translates a string array. + /// + /// The array to be translated. + void Translate(ref string[] array); + + /// + /// Translates a list of strings + /// + /// The list to be translated. + void Translate(ref List list); + + /// + /// Translates a set of strings + /// + /// The set to be translated. + void Translate(ref HashSet set); + + /// + /// Translates a list of T using an + /// + /// The list to be translated. + /// The translator to use for the items in the list + /// A TaskItemType + void Translate(ref List list, ObjectTranslator objectTranslator); + + /// + /// Translates a list of T using an + /// + /// The list to be translated. + /// The translator to use for the items in the list + /// The factory to use to create the value. + /// A TaskItemType + void Translate(ref List list, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory); + + /// + /// Translates a list of T using an anda collection factory + /// + /// The list to be translated. + /// The translator to use for the items in the list + /// An ITranslatable subtype + /// An IList subtype + /// factory to create a collection + void Translate(ref IList list, ObjectTranslator objectTranslator, NodePacketCollectionCreator collectionFactory) where L : IList; + + /// + /// Translates a list of T using an and a collection factory + /// + /// The list to be translated. + /// The translator to use for the items in the list + /// The factory to use to create the value. + /// An ITranslatable subtype + /// An IList subtype + /// factory to create a collection + void Translate(ref IList list, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory, NodePacketCollectionCreator collectionFactory) where L : IList; + + /// + /// Translates a collection of T into the specified type using an and + /// + /// The collection to be translated. + /// The translator to use for the values in the collection. + /// The factory to create the ICollection. + /// The type contained in the collection. + /// The type of collection to be created. + void Translate(ref ICollection collection, ObjectTranslator objectTranslator, NodePacketCollectionCreator collectionFactory) where L : ICollection; + + /// + /// Translates a DateTime. + /// + /// The value to be translated. + void Translate(ref DateTime value); + + /// + /// Translates a TimeSpan. + /// + /// The value to be translated. + void Translate(ref TimeSpan value); + + // MSBuildTaskHost is based on CLR 3.5, which does not have the 6-parameter constructor for BuildEventContext, + // which is what current implementations of this method use. However, it also does not ever need to translate + // BuildEventContexts, so it should be perfectly safe to compile this method out of that assembly. I am compiling + // the method out of the interface as well, instead of just making the method empty, so that if we ever do need + // to translate BuildEventContexts from the CLR 3.5 task host, it will become immediately obvious, rather than + // failing or misbehaving silently. +#if !CLR2COMPATIBILITY + + /// + /// Translates a BuildEventContext + /// + /// + /// This method exists only because there is no serialization method built into the BuildEventContext + /// class, and it lives in Framework and we don't want to add a public method to it. + /// + /// The context to be translated. + void Translate(ref BuildEventContext value); +#endif + + /// + /// Translates an enumeration. + /// + /// The enumeration type. + /// The enumeration instance to be translated. + /// The enumeration value as an integer. + /// This is a bit ugly, but it doesn't seem like a nice method signature is possible because + /// you can't pass the enum type as a reference and constrain the generic parameter to Enum. Nor + /// can you simply pass as ref Enum, because an enum instance doesn't match that function signature. + /// Finally, converting the enum to an int assumes that we always want to transport enums as ints. This + /// works in all of our current cases, but certainly isn't perfectly generic. + void TranslateEnum(ref T value, int numericValue) + where T : struct, Enum; + + void TranslateException(ref Exception value); + + /// + /// Translates an object implementing INodePacketTranslatable. + /// + /// The reference type. + /// The value to be translated. + void Translate(ref T value) + where T : ITranslatable, new(); + + /// + /// Translates a culture + /// + /// The culture + void TranslateCulture(ref CultureInfo culture); + + /// + /// Translates a byte array + /// + /// The array to be translated. + void Translate(ref byte[] byteArray); + + /// + /// Translates a byte array + /// + /// The array to be translated. + /// The length of array which will be used in translation + void Translate(ref byte[] byteArray, ref int length); + + /// + /// Translates an array of objects implementing INodePacketTranslatable. + /// + /// The reference type. + /// The array to be translated. + void TranslateArray(ref T[] array) + where T : ITranslatable, new(); + + /// + /// Translates an array of objects using an . + /// + /// The reference type. + /// The array to be translated. + /// The translator to use for the elements in the array. + /// The factory to use to create the value. + void TranslateArray(ref T[] array, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory); + + /// + /// Translates a dictionary of { string, string }. + /// + /// The dictionary to be translated. + /// The comparer used to instantiate the dictionary. + void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer); + + /// + /// Translates a TaskHostParameters. + /// + /// The TaskHostParameters to translate. + void Translate(ref TaskHostParameters value); + + /// + /// Translates a dictionary of { string, string } adding additional entries. + /// + /// The dictionary to be translated. + /// The comparer used to instantiate the dictionary. + /// Additional entries to be translated + /// Additional entries keys + /// + /// This overload is needed for a workaround concerning serializing BuildResult with a version. + /// It serializes/deserializes additional entries together with the main dictionary. + /// + void TranslateDictionary(ref IDictionary dictionary, IEqualityComparer comparer, ref Dictionary additionalEntries, HashSet additionalEntriesKeys); + + void TranslateDictionary(ref IDictionary dictionary, NodePacketCollectionCreator> collectionCreator); + + void TranslateDictionary(ref Dictionary dictionary, StringComparer comparer); + + void TranslateDictionary(ref IDictionary dictionary, ObjectTranslator keyTranslator, ObjectTranslator valueTranslator, NodePacketCollectionCreator> dictionaryCreator); + + void TranslateDictionary(ref IDictionary dictionary, ObjectTranslator keyTranslator, ObjectTranslatorWithValueFactory valueTranslator, NodePacketValueFactory valueFactory, NodePacketCollectionCreator> dictionaryCreator); + + /// + /// Translates a dictionary of { string, T }. + /// + /// The reference type for the values, which implements INodePacketTranslatable. + /// The dictionary to be translated. + /// The comparer used to instantiate the dictionary. + /// The translator to use for the values in the dictionary + /// /// The factory to use to create the value. + void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) + where T : class; + + /// + /// Translates a dictionary of { string, T } for dictionaries with public parameterless constructors. + /// + /// The reference type for the dictionary. + /// The reference type for values in the dictionary. + /// The dictionary to be translated. + /// The translator to use for the values in the dictionary. + /// The factory to use to create the value. + void TranslateDictionary(ref D dictionary, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) + where D : IDictionary, new() + where T : class; + + /// + /// Translates a dictionary of { string, T } for dictionaries with public parameterless constructors. + /// + /// The reference type for the dictionary. + /// The reference type for values in the dictionary. + /// The dictionary to be translated. + /// The translator to use for the values in the dictionary + /// /// The factory to use to create the value. + /// A factory used to create the dictionary. + void TranslateDictionary(ref D dictionary, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory, NodePacketCollectionCreator collectionCreator) + where D : IDictionary + where T : class; + + /// + /// Translates the boolean that says whether this value is null or not + /// + /// The object to test. + /// The type of object to test. + /// True if the object should be written, false otherwise. + bool TranslateNullable(T value); + + /// + /// Creates a scope which activates string interning / deduplication for any Intern_xx method. + /// This should generally be called from the root level packet. + /// + /// The string comparer to use when populating the intern cache. + /// The initial capacity of the intern cache. + /// A delegate providing a translator, in which all Intern_xx calls will go through the intern cache. + /// + /// Packet interning is implemented via a header with an array of all interned strings, followed by the body in + /// which any interned / duplicated strings are replaced by their ID. + /// modes have different ordering requirements, so it would not be + /// possible to implement direction-agnostic serialization via the Intern_xx methods alone: + /// - Write: Because we don't know the full list of strings ahead of time, we need to create a temporary buffer + /// for the packet body, which we can later offset when flushing to the real stream. + /// - Read: The intern header needs to be deserialized before the packet body, otherwise we won't know what + /// string each ID maps to. + /// This method abstracts these requirements to the caller, such that the underlying translator will + /// automatically handle the appropriate IO ordering when entering / exiting the delegate scope. + /// + void WithInterning(IEqualityComparer comparer, int initialCapacity, Action internBlock); + + /// + /// Interns the string if the translator is currently within an intern block. + /// Otherwise, this forwards to the regular Translate method. + /// + /// The value to be translated. + /// + /// Whether to null check and translate the nullable marker. + /// Setting this to false can reduce packet sizes when interning large numbers of strings + /// which are validated to always be non-null, such as dictionary keys. + /// + void Intern(ref string str, bool nullable = true); + + /// + /// Interns each string in the array if the translator is currently within an intern block. + /// Otherwise, this forwards to the regular Translate method. To match behavior, all strings + /// assumed to be non-null. + /// + /// The array to be translated. + void Intern(ref string[] array); + + /// + /// Interns the string if the translator is currently within an intern block. + /// Otherwise, this forwards to the regular Translate method. + /// If the string is determined to be path-like, the path components will be interned separately. + /// + /// The value to be translated. + /// + /// Whether to null check and translate the nullable marker. + /// Setting this to false can reduce packet sizes when interning large numbers of strings + /// which are validated to always be non-null, such as dictionary keys. + /// + void InternPath(ref string str, bool nullable = true); + } +} diff --git a/src/MSBuildTaskHost/Framework/ImmutableDictionaryExtensions.cs b/src/MSBuildTaskHost/Framework/ImmutableDictionaryExtensions.cs new file mode 100644 index 00000000000..ba7b04d91ae --- /dev/null +++ b/src/MSBuildTaskHost/Framework/ImmutableDictionaryExtensions.cs @@ -0,0 +1,47 @@ +// 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; +using System.Collections.Immutable; +using Microsoft.Build.Collections; + +namespace Microsoft.Build.Framework +{ + internal static class ImmutableDictionaryExtensions + { + /// + /// An empty dictionary pre-configured with a comparer for metadata dictionaries. + /// + public static readonly ImmutableDictionary EmptyMetadata = + ImmutableDictionary.Empty.WithComparers(MSBuildNameIgnoreCaseComparer.Default); + +#if !TASKHOST + /// + /// Sets the given items while running a validation function on each key. + /// + /// + /// ProjectItemInstance.TaskItem exposes dictionary values as ProjectMetadataInstance. For perf reasons, + /// we don't want to internally store ProjectMetadataInstance since it prevents us from sharing immutable + /// dictionaries with Utilities.TaskItem, and it results in more than 2x memory allocated per-entry. + /// + public static ImmutableDictionary SetItems( + this ImmutableDictionary dictionary, + IEnumerable> items, + Action verifyThrowKey) + { + ImmutableDictionary.Builder builder = dictionary.ToBuilder(); + + foreach (KeyValuePair item in items) + { + verifyThrowKey(item.Key); + + // Set null as empty string to match behavior with ProjectMetadataInstance. + builder[item.Key] = item.Value ?? string.Empty; + } + + return builder.ToImmutable(); + } +#endif + } +} diff --git a/src/MSBuildTaskHost/Framework/InternPathIds.cs b/src/MSBuildTaskHost/Framework/InternPathIds.cs new file mode 100644 index 00000000000..029f31f95ed --- /dev/null +++ b/src/MSBuildTaskHost/Framework/InternPathIds.cs @@ -0,0 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Build.BackEnd +{ + internal readonly record struct InternPathIds(int DirectoryId, int FileNameId); +} diff --git a/src/MSBuildTaskHost/Framework/InternalErrorException.cs b/src/MSBuildTaskHost/Framework/InternalErrorException.cs new file mode 100644 index 00000000000..8b1096c5eef --- /dev/null +++ b/src/MSBuildTaskHost/Framework/InternalErrorException.cs @@ -0,0 +1,155 @@ +// 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.Diagnostics; +using System.Runtime.Serialization; +using Microsoft.Build.Framework.BuildException; + +#nullable disable + +namespace Microsoft.Build.Framework +{ + /// + /// This exception is to be thrown whenever an assumption we have made in the code turns out to be false. Thus, if this + /// exception ever gets thrown, it is because of a bug in our own code, not because of something the user or project author + /// did wrong. + /// + [Serializable] + internal sealed class InternalErrorException : BuildExceptionBase + { + /// + /// Default constructor. + /// SHOULD ONLY BE CALLED BY DESERIALIZER. + /// SUPPLY A MESSAGE INSTEAD. + /// + internal InternalErrorException() : base() + { + // do nothing + } + + /// + /// Creates an instance of this exception using the given message. + /// + internal InternalErrorException( + String message) : + base("MSB0001: Internal MSBuild Error: " + message) + { + ConsiderDebuggerLaunch(message, null); + } + + /// + /// Creates an instance of this exception using the given message and inner exception. + /// Adds the inner exception's details to the exception message because most bug reporters don't bother + /// to provide the inner exception details which is typically what we care about. + /// + internal InternalErrorException( + String message, + Exception innerException) : + this(message, innerException, false) + { } + + internal static InternalErrorException CreateFromRemote(string message, Exception innerException) + { + return new InternalErrorException(message, innerException, true /* calledFromDeserialization */); + } + + private InternalErrorException(string message, Exception innerException, bool calledFromDeserialization) + : base( + calledFromDeserialization + ? message + : "MSB0001: Internal MSBuild Error: " + message + (innerException == null + ? String.Empty + : $"\n=============\n{innerException}\n\n"), + innerException) + { + if (!calledFromDeserialization) + { + ConsiderDebuggerLaunch(message, innerException); + } + } + + #region Serialization (update when adding new class members) + + /// + /// Private constructor used for (de)serialization. The constructor is private as this class is sealed + /// If we ever add new members to this class, we'll need to update this. + /// +#if NET8_0_OR_GREATER + [Obsolete(DiagnosticId = "SYSLIB0051")] +#endif + private InternalErrorException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + // Do nothing: no fields + } + + // Base implementation of GetObjectData() is sufficient; we have no fields + #endregion + + #region ConsiderDebuggerLaunch + /// + /// A fatal internal error due to a bug has occurred. Give the dev a chance to debug it, if possible. + /// + /// Will in all cases launch the debugger, if the environment variable "MSBUILDLAUNCHDEBUGGER" is set. + /// + /// In DEBUG build, will always launch the debugger, unless we are in razzle (_NTROOT is set) or in NUnit, + /// or MSBUILDDONOTLAUNCHDEBUGGER is set (that could be useful in suite runs). + /// We don't launch in retail or LKG so builds don't jam; they get a callstack, and continue or send a mail, etc. + /// We don't launch in NUnit as tests often intentionally cause InternalErrorExceptions. + /// + /// Because we only call this method from this class, just before throwing an InternalErrorException, there is + /// no danger that this suppression will cause a bug to only manifest itself outside NUnit + /// (which would be most unfortunate!). Do not make this non-private. + /// + /// Unfortunately NUnit can't handle unhandled exceptions like InternalErrorException on anything other than + /// the main test thread. However, there's still a callstack displayed before it quits. + /// + /// If it is going to launch the debugger, it first does a Debug.Fail to give information about what needs to + /// be debugged -- the exception hasn't been thrown yet. This automatically displays the current callstack. + /// + private static void ConsiderDebuggerLaunch(string message, Exception innerException) + { + string innerMessage = (innerException == null) ? String.Empty : innerException.ToString(); + + if (Environment.GetEnvironmentVariable("MSBUILDLAUNCHDEBUGGER") != null) + { + LaunchDebugger(message, innerMessage); + return; + } + +#if DEBUG + if (!RunningTests() && Environment.GetEnvironmentVariable("MSBUILDDONOTLAUNCHDEBUGGER") == null + && Environment.GetEnvironmentVariable("_NTROOT") == null) + { + LaunchDebugger(message, innerMessage); + return; + } +#endif + } + + private static void LaunchDebugger(string message, string innerMessage) + { +#if FEATURE_DEBUG_LAUNCH + Debug.Fail(message, innerMessage); + Debugger.Launch(); +#else + Console.WriteLine("MSBuild Failure: " + message); + if (!string.IsNullOrEmpty(innerMessage)) + { + Console.WriteLine(innerMessage); + } + Console.WriteLine("Waiting for debugger to attach to process: " + Process.GetCurrentProcess().Id); + while (!Debugger.IsAttached) + { + System.Threading.Thread.Sleep(100); + } +#endif + } + #endregion + +#if DEBUG + private static bool RunningTests() => BuildEnvironmentState.s_runningTests; +#endif + } +} diff --git a/src/MSBuildTaskHost/Framework/InterningReadTranslator.cs b/src/MSBuildTaskHost/Framework/InterningReadTranslator.cs new file mode 100644 index 00000000000..76a3d35b8f0 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/InterningReadTranslator.cs @@ -0,0 +1,84 @@ +// 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; + +namespace Microsoft.Build.BackEnd +{ + /// + /// Reads strings form a translator which contains interned packets. + /// + /// + /// This maintains a reusable lookup table to deserialize packets interned by . + /// On Translate, the intern header (aka the array of strings indexed by ID) is deserialized. + /// The caller can then forward reads to deserialize any interned values in the packet body. + /// + internal sealed class InterningReadTranslator : ITranslatable + { + private readonly ITranslator _translator; + + private List _strings = []; + + private Dictionary _pathIdsToString = []; + + internal InterningReadTranslator(ITranslator translator) + { + if (translator.Mode != TranslationDirection.ReadFromStream) + { + throw new InvalidOperationException( + $"{nameof(InterningReadTranslator)} can only be used with {nameof(TranslationDirection.ReadFromStream)}."); + } + + _translator = translator; + } + + internal string? Read() + { + int key = -1; + _translator.Translate(ref key); + return _strings[key]; + } + + internal string? ReadPath() + { + // If the writer set a null marker, read this as a single string. + if (!_translator.TranslateNullable(string.Empty)) + { + return Read(); + } + + int directoryKey = -1; + int fileNameKey = -1; + _translator.Translate(ref directoryKey); + _translator.Translate(ref fileNameKey); + + InternPathIds pathIds = new(directoryKey, fileNameKey); + + // Only concatenate paths the first time we encounter a pair. + if (_pathIdsToString.TryGetValue(pathIds, out string? path)) + { + return path; + } + + string directory = _strings[pathIds.DirectoryId]; + string fileName = _strings[pathIds.FileNameId]; + string str = string.Concat(directory, fileName); + _pathIdsToString.Add(pathIds, str); + + return str; + } + + public void Translate(ITranslator translator) + { + // Only deserialize the intern header since the caller will be reading directly from the stream. + _translator.Translate(ref _strings); +#if NET + _pathIdsToString.Clear(); + _pathIdsToString.EnsureCapacity(_strings.Count); +#else + _pathIdsToString = new(_strings.Count); +#endif + } + } +} diff --git a/src/MSBuildTaskHost/Framework/InterningWriteTranslator.cs b/src/MSBuildTaskHost/Framework/InterningWriteTranslator.cs new file mode 100644 index 00000000000..ec31eb92eb4 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/InterningWriteTranslator.cs @@ -0,0 +1,173 @@ +// 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; +using System.IO; + +namespace Microsoft.Build.BackEnd +{ + /// + /// Writes strings into a translator with interning / deduplication. + /// + /// + /// This maintains a reusable temporary buffer and lookup table for deduplicating strings within a translatable packet. + /// All unique strings (as determined by the comparer) will be assigned an incrementing ID and stored into a dictionary. + /// This ID will be written to a private buffer in place of the string and any repeat occurrences. + /// When serialized into another translator, the interner will: + /// 1. Serialize the list of unique strings to an array, where the ID is the index. + /// 2. Serialize the temporary buffer (aka the packet body) with all interned strings replaced by their ID. + /// This ordering is important since the reader will need the string lookup table before parsing the body. + /// As such, two rules need to be followed when using this class: + /// 1. Any interleaved non-interned writes should be written using the exposed BinaryWriter to keep the overall + /// packet in sync. + /// 2. Translate should *only* be called after all internable writes have been processed. + /// + internal sealed class InterningWriteTranslator : ITranslatable + { + private static readonly char[] DirectorySeparatorChars = [Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar]; + + private static readonly string IsPathMarker = string.Empty; + + private static readonly string? NotPathMarker = null; + + private readonly ITranslator _translator; + + private readonly MemoryStream _packetStream; + + private List _strings = []; + + private Dictionary _stringToIds = []; + + private Dictionary _stringToPathIds = []; + + internal InterningWriteTranslator() + { + _packetStream = new MemoryStream(); + _translator = BinaryTranslator.GetWriteTranslator(_packetStream); + + // Avoid directly exposing the buffered translator - any accidental Intern_xx method calls could go into a + // recursive loop. + Writer = _translator.Writer; + } + + /// + /// The writer for the underlying buffer. + /// Use to forward any non-interning writes into this translator. + /// + internal BinaryWriter Writer { get; } + + /// + /// Setup the intern cache and underlying buffer. This allows the interner to be reused. + /// + /// The string comparer to use for string deduplication. + /// An estimate of the number of unique strings to be interned. + internal void Setup(IEqualityComparer comparer, int initialCapacity) + { +#if NET + if (_stringToIds.Comparer == comparer) + { + // Clear before setting capacity, since dictionaries will rehash every entry. + _strings.Clear(); + _stringToIds.Clear(); + _stringToPathIds.Clear(); + _strings.EnsureCapacity(initialCapacity); + _stringToIds.EnsureCapacity(initialCapacity); + _stringToPathIds.EnsureCapacity(initialCapacity); + } + else + { +#endif + // If the interner is in a reused translator, the comparer might not match between packets. + // Just throw away the old collections in this case. + _strings.Clear(); + _strings.Capacity = initialCapacity; + _stringToIds = new Dictionary(initialCapacity, comparer); + _stringToPathIds = new Dictionary(initialCapacity, comparer); +#if NET + } +#endif + _packetStream.Position = 0; + _packetStream.SetLength(0); + + // This is a rough estimate since the final size will depend on the length of each string and the total number + // of intern cache hits. Assume a mixture of short strings (e.g. item metadata pairs, RAR assembly metadata) + // and file paths (e.g. item include paths, RAR statefile entries). + const int CharactersPerString = 32; + const int BytesPerCharacter = 2; + const int BytesPerInternedString = 5; + int internHeaderSize = initialCapacity * CharactersPerString * BytesPerCharacter; + int packetPayloadSize = initialCapacity * BytesPerInternedString; + _packetStream.Capacity = internHeaderSize + packetPayloadSize; + } + + internal void Intern(string str) => _ = InternString(str); + + private int InternString(string str) + { + if (!_stringToIds.TryGetValue(str, out int index)) + { + index = _strings.Count; + _stringToIds.Add(str, index); + _strings.Add(str); + } + + _translator.Translate(ref index); + return index; + } + + internal void InternPath(string str) + { + // If we've seen a string already and know it's path-like, we just need the index pair. + if (_stringToPathIds.TryGetValue(str, out InternPathIds pathIds)) + { + _ = _translator.TranslateNullable(IsPathMarker); + int directoryId = pathIds.DirectoryId; + int fileNameId = pathIds.FileNameId; + _translator.Translate(ref directoryId); + _translator.Translate(ref fileNameId); + return; + } + + // Quick and basic heuristic to check if we have a path-like string. + int splitId = str.LastIndexOfAny(DirectorySeparatorChars); + bool hasDirectorySeparator = splitId > -1 + && splitId < str.Length - 1 + && str.IndexOf('%') == -1; + + if (!hasDirectorySeparator) + { + // Set a marker to signal the reader to parse this as a single string. + _ = _translator.TranslateNullable(NotPathMarker); + _ = InternString(str); + return; + } + + string directory = str.Substring(0, splitId + 1); + string fileName = str.Substring(splitId + 1); + + _ = _translator.TranslateNullable(IsPathMarker); + int directoryIndex = InternString(directory); + int fileNameIndex = InternString(fileName); + + _stringToPathIds.Add(str, new InternPathIds(directoryIndex, fileNameIndex)); + } + + public void Translate(ITranslator translator) + { + if (translator.Mode != TranslationDirection.WriteToStream) + { + throw new InvalidOperationException( + $"{nameof(InterningWriteTranslator)} can only be used with {nameof(TranslationDirection.WriteToStream)}."); + } + + // Write the set of unique strings as the packet header. + translator.Translate(ref _strings); + + // Write the temporary buffer as the packet body. + byte[] buffer = _packetStream.GetBuffer(); + int bufferSize = (int)_packetStream.Length; + translator.Writer.Write(buffer, 0, bufferSize); + } + } +} diff --git a/src/MSBuildTaskHost/Framework/Logging/AnsiDetector.cs b/src/MSBuildTaskHost/Framework/Logging/AnsiDetector.cs new file mode 100644 index 00000000000..6c2b09bd7a0 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/Logging/AnsiDetector.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Portions of the code in this file were ported from the spectre.console by Patrik Svensson, Phil Scott, Nils Andresen +// https://github.com/spectreconsole/spectre.console/blob/main/src/Spectre.Console/Internal/Backends/Ansi/AnsiDetector.cs +// and from the supports-ansi project by Qingrong Ke +// https://github.com/keqingrong/supports-ansi/blob/master/index.js + +using System.Linq; +using System.Text.RegularExpressions; + +namespace Microsoft.Build.Framework.Logging +{ + internal class AnsiDetector + { + private static readonly Regex[] terminalsRegexes = + { + new("^xterm"), // xterm, PuTTY, Mintty + new("^rxvt"), // RXVT + new("^(?!eterm-color).*eterm.*"), // Accepts eterm, but not eterm-color, which does not support moving the cursor, see #9950. + new("^screen"), // GNU screen, tmux + new("tmux"), // tmux + new("^vt100"), // DEC VT series + new("^vt102"), // DEC VT series + new("^vt220"), // DEC VT series + new("^vt320"), // DEC VT series + new("ansi"), // ANSI + new("scoansi"), // SCO ANSI + new("cygwin"), // Cygwin, MinGW + new("linux"), // Linux console + new("konsole"), // Konsole + new("bvterm"), // Bitvise SSH Client + new("^st-256color"), // Suckless Simple Terminal, st + new("alacritty"), // Alacritty + }; + + internal static bool IsAnsiSupported(string termType) + { + if (string.IsNullOrEmpty(termType)) + { + return false; + } + + if (terminalsRegexes.Any(regex => regex.IsMatch(termType))) + { + return true; + } + + return false; + } + } +} diff --git a/src/MSBuildTaskHost/Framework/MSBuildNameIgnoreCaseComparer.cs b/src/MSBuildTaskHost/Framework/MSBuildNameIgnoreCaseComparer.cs new file mode 100644 index 00000000000..ef837fcced6 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/MSBuildNameIgnoreCaseComparer.cs @@ -0,0 +1,194 @@ +// 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; +using Microsoft.Build.Framework; + +#nullable disable + +namespace Microsoft.Build.Collections +{ + /// + /// This is a custom string comparer that has three advantages over the regular + /// string comparer: + /// 1) It can generate hash codes and perform equivalence operations on parts of a string rather than a whole + /// 2) It uses "unsafe" pointers to maximize performance of those operations + /// 3) It takes advantage of limitations on MSBuild Property/Item names to cheaply do case insensitive comparison. + /// + [Serializable] + internal class MSBuildNameIgnoreCaseComparer : IConstrainedEqualityComparer, IEqualityComparer + { + /// + /// The processor architecture on which we are running, but default it will be x86 + /// + private static readonly NativeMethods.ProcessorArchitectures s_runningProcessorArchitecture = NativeMethods.ProcessorArchitecture; + + /// + /// The default immutable comparer instance. + /// + internal static MSBuildNameIgnoreCaseComparer Default { get; } = new MSBuildNameIgnoreCaseComparer(); + + public bool Equals(string x, string y) + { + return Equals(x, y, 0, y?.Length ?? 0); + } + + public int GetHashCode(string obj) + { + return GetHashCode(obj, 0, obj?.Length ?? 0); + } + + /// + /// Performs the "Equals" operation on two MSBuild property, item or metadata names + /// + public bool Equals(string compareToString, string constrainedString, int start, int lengthToCompare) + { + if (lengthToCompare < 0) + { + EscapeHatches.ThrowInternalError("Invalid lengthToCompare '{0}' {1} {2}", constrainedString, start, lengthToCompare); + } + + if (start < 0 || start > (constrainedString?.Length ?? 0) - lengthToCompare) + { + EscapeHatches.ThrowInternalError("Invalid start '{0}' {1} {2}", constrainedString, start, lengthToCompare); + } + + if (ReferenceEquals(compareToString, constrainedString)) + { + return true; + } + + if (compareToString == null || constrainedString == null) + { + return false; + } + +#if NET + return compareToString.AsSpan().Equals(constrainedString.AsSpan(start, lengthToCompare), StringComparison.OrdinalIgnoreCase); +#else + if (lengthToCompare != compareToString.Length) + { + return false; + } + + if ((s_runningProcessorArchitecture != NativeMethods.ProcessorArchitectures.IA64) + && (s_runningProcessorArchitecture != NativeMethods.ProcessorArchitectures.ARM)) + { + // The use of unsafe here is quite a bit faster than the regular + // mechanism in the BCL. This is because we can make assumptions + // about the characters that are within the strings being compared + // i.e. they are valid MSBuild property, item and metadata names + unsafe + { + fixed (char* px = compareToString) + { + fixed (char* py = constrainedString) + { + for (int i = 0; i < compareToString.Length; i++) + { + int chx = px[i]; + int chy = py[i + start]; + chx &= 0x00DF; // Extract the uppercase character + chy &= 0x00DF; // Extract the uppercase character + + if (chx != chy) + { + return false; + } + } + } + } + } + } + else + { + return String.Compare(compareToString, 0, constrainedString, start, lengthToCompare, StringComparison.OrdinalIgnoreCase) == 0; + } + + return true; +#endif + } + + /// + /// Getting a case insensitive hash code for the msbuild property, item or metadata name + /// + public int GetHashCode(string obj, int start, int length) + { + if (obj == null) + { + return 0; // per BCL convention + } + + if ((s_runningProcessorArchitecture != NativeMethods.ProcessorArchitectures.IA64) + && (s_runningProcessorArchitecture != NativeMethods.ProcessorArchitectures.ARM)) + { + unsafe + { + // This algorithm is based on the 32bit version from the CLR's string::GetHashCode + fixed (char* src = obj) + { + int hash1 = (5381 << 16) + 5381; + + int hash2 = hash1; + + char* src2 = src + start; + var pint = (int*)src2; + + while (length > 0) + { + // We're only interested in uppercase ASCII characters + int val = pint[0] & 0x00DF00DF; + + // When we reach the end of the string, we need to + // stop short when gathering our data to compute the + // hash code - we are only interested in the data within + // the string, and not the null terminator etc. + if (length == 1) + { + if (BitConverter.IsLittleEndian) + { + val &= 0xFFFF; + } + else + { + val &= unchecked((int)0xFFFF0000); + } + } + + hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ val; + if (length <= 2) + { + break; + } + + // Once again we're only interested in the uppercase ASCII characters + val = pint[1] & 0x00DF00DF; + if (length == 3) + { + if (BitConverter.IsLittleEndian) + { + val &= 0xFFFF; + } + else + { + val &= unchecked((int)0xFFFF0000); + } + } + + hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ val; + pint += 2; + length -= 4; + } + + return hash1 + (hash2 * 1566083941); + } + } + } + else + { + return StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Substring(start, length)); + } + } + } +} diff --git a/src/MSBuildTaskHost/Framework/NativeMethods.cs b/src/MSBuildTaskHost/Framework/NativeMethods.cs new file mode 100644 index 00000000000..e5858135cf0 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/NativeMethods.cs @@ -0,0 +1,1913 @@ +// 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; +using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using Microsoft.Build.Shared; +using Microsoft.Win32; +using Microsoft.Win32.SafeHandles; + +#if !CLR2COMPATIBILITY +using Microsoft.Build.Framework.Logging; +#endif + +using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; + +#nullable disable + +namespace Microsoft.Build.Framework; + +internal static class NativeMethods +{ + #region Constants + + internal const uint ERROR_INSUFFICIENT_BUFFER = 0x8007007A; + internal const uint STARTUP_LOADER_SAFEMODE = 0x10; + internal const uint S_OK = 0x0; + internal const uint S_FALSE = 0x1; + internal const uint ERROR_ACCESS_DENIED = 0x5; + internal const uint ERROR_FILE_NOT_FOUND = 0x80070002; + internal const uint FUSION_E_PRIVATE_ASM_DISALLOWED = 0x80131044; // Tried to find unsigned assembly in GAC + internal const uint RUNTIME_INFO_DONT_SHOW_ERROR_DIALOG = 0x40; + internal const uint FILE_TYPE_CHAR = 0x0002; + internal const Int32 STD_OUTPUT_HANDLE = -11; + internal const Int32 STD_ERROR_HANDLE = -12; + internal const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004; + internal const uint RPC_S_CALLPENDING = 0x80010115; + internal const uint E_ABORT = (uint)0x80004004; + + internal const int FILE_ATTRIBUTE_READONLY = 0x00000001; + internal const int FILE_ATTRIBUTE_DIRECTORY = 0x00000010; + internal const int FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400; + + /// + /// Default buffer size to use when dealing with the Windows API. + /// + internal const int MAX_PATH = 260; + + private const string kernel32Dll = "kernel32.dll"; + + private const string WINDOWS_FILE_SYSTEM_REGISTRY_KEY = @"SYSTEM\CurrentControlSet\Control\FileSystem"; + private const string WINDOWS_LONG_PATHS_ENABLED_VALUE_NAME = "LongPathsEnabled"; + + private const string WINDOWS_SAC_REGISTRY_KEY = @"SYSTEM\CurrentControlSet\Control\CI\Policy"; + private const string WINDOWS_SAC_VALUE_NAME = "VerifiedAndReputablePolicyState"; + + internal static DateTime MinFileDate { get; } = DateTime.FromFileTimeUtc(0); + + internal static HandleRef NullHandleRef = new HandleRef(null, IntPtr.Zero); + + internal static IntPtr NullIntPtr = new IntPtr(0); + internal static IntPtr InvalidHandle = new IntPtr(-1); + + // As defined in winnt.h: + internal const ushort PROCESSOR_ARCHITECTURE_INTEL = 0; + internal const ushort PROCESSOR_ARCHITECTURE_ARM = 5; + internal const ushort PROCESSOR_ARCHITECTURE_IA64 = 6; + internal const ushort PROCESSOR_ARCHITECTURE_AMD64 = 9; + internal const ushort PROCESSOR_ARCHITECTURE_ARM64 = 12; + + internal const uint INFINITE = 0xFFFFFFFF; + internal const uint WAIT_ABANDONED_0 = 0x00000080; + internal const uint WAIT_OBJECT_0 = 0x00000000; + internal const uint WAIT_TIMEOUT = 0x00000102; + + #endregion + + #region Enums + + internal enum StreamHandleType + { + StdOut = STD_OUTPUT_HANDLE, + StdErr = STD_ERROR_HANDLE, + }; + + private enum PROCESSINFOCLASS : int + { + ProcessBasicInformation = 0, + ProcessQuotaLimits, + ProcessIoCounters, + ProcessVmCounters, + ProcessTimes, + ProcessBasePriority, + ProcessRaisePriority, + ProcessDebugPort, + ProcessExceptionPort, + ProcessAccessToken, + ProcessLdtInformation, + ProcessLdtSize, + ProcessDefaultHardErrorMode, + ProcessIoPortHandlers, // Note: this is kernel mode only + ProcessPooledUsageAndLimits, + ProcessWorkingSetWatch, + ProcessUserModeIOPL, + ProcessEnableAlignmentFaultFixup, + ProcessPriorityClass, + ProcessWx86Information, + ProcessHandleCount, + ProcessAffinityMask, + ProcessPriorityBoost, + MaxProcessInfoClass + }; + + private enum eDesiredAccess : int + { + DELETE = 0x00010000, + READ_CONTROL = 0x00020000, + WRITE_DAC = 0x00040000, + WRITE_OWNER = 0x00080000, + SYNCHRONIZE = 0x00100000, + STANDARD_RIGHTS_ALL = 0x001F0000, + + PROCESS_TERMINATE = 0x0001, + PROCESS_CREATE_THREAD = 0x0002, + PROCESS_SET_SESSIONID = 0x0004, + PROCESS_VM_OPERATION = 0x0008, + PROCESS_VM_READ = 0x0010, + PROCESS_VM_WRITE = 0x0020, + PROCESS_DUP_HANDLE = 0x0040, + PROCESS_CREATE_PROCESS = 0x0080, + PROCESS_SET_QUOTA = 0x0100, + PROCESS_SET_INFORMATION = 0x0200, + PROCESS_QUERY_INFORMATION = 0x0400, + PROCESS_ALL_ACCESS = SYNCHRONIZE | 0xFFF + } +#pragma warning disable 0649, 0169 + internal enum LOGICAL_PROCESSOR_RELATIONSHIP + { + RelationProcessorCore, + RelationNumaNode, + RelationCache, + RelationProcessorPackage, + RelationGroup, + RelationAll = 0xffff + } + internal struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX + { + public LOGICAL_PROCESSOR_RELATIONSHIP Relationship; + public uint Size; + public PROCESSOR_RELATIONSHIP Processor; + } + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct PROCESSOR_RELATIONSHIP + { + public byte Flags; + private byte EfficiencyClass; + private fixed byte Reserved[20]; + public ushort GroupCount; + public IntPtr GroupInfo; + } +#pragma warning restore 0169, 0149 + + /// + /// Flags for CoWaitForMultipleHandles + /// + [Flags] + public enum COWAIT_FLAGS : int + { + /// + /// Exit when a handle is signaled. + /// + COWAIT_NONE = 0, + + /// + /// Exit when all handles are signaled AND a message is received. + /// + COWAIT_WAITALL = 0x00000001, + + /// + /// Exit when an RPC call is serviced. + /// + COWAIT_ALERTABLE = 0x00000002 + } + + /// + /// Processor architecture values + /// + internal enum ProcessorArchitectures + { + // Intel 32 bit + X86, + + // AMD64 64 bit + X64, + + // Itanium 64 + IA64, + + // ARM + ARM, + + // ARM64 + ARM64, + + // WebAssembly + WASM, + + // S390x + S390X, + + // LongAarch64 + LOONGARCH64, + + // 32-bit ARMv6 + ARMV6, + + // PowerPC 64-bit (little-endian) + PPC64LE, + + // Who knows + Unknown + } + + internal enum SymbolicLink + { + File = 0, + Directory = 1, + AllowUnprivilegedCreate = 2, + } + + #endregion + + #region Structs + + /// + /// Structure that contain information about the system on which we are running + /// + [StructLayout(LayoutKind.Sequential)] + internal struct SYSTEM_INFO + { + // This is a union of a DWORD and a struct containing 2 WORDs. + internal ushort wProcessorArchitecture; + internal ushort wReserved; + + internal uint dwPageSize; + internal IntPtr lpMinimumApplicationAddress; + internal IntPtr lpMaximumApplicationAddress; + internal IntPtr dwActiveProcessorMask; + internal uint dwNumberOfProcessors; + internal uint dwProcessorType; + internal uint dwAllocationGranularity; + internal ushort wProcessorLevel; + internal ushort wProcessorRevision; + } + + /// + /// Wrap the intptr returned by OpenProcess in a safe handle. + /// + internal class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid + { + // Create a SafeHandle, informing the base class + // that this SafeHandle instance "owns" the handle, + // and therefore SafeHandle should call + // our ReleaseHandle method when the SafeHandle + // is no longer in use + private SafeProcessHandle() : base(true) + { + } + + [SupportedOSPlatform("windows")] + protected override bool ReleaseHandle() + { + return CloseHandle(handle); + } + } + + /// + /// Contains information about the current state of both physical and virtual memory, including extended memory + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal class MemoryStatus + { + /// + /// Initializes a new instance of the class. + /// + public MemoryStatus() + { +#if CLR2COMPATIBILITY + _length = (uint)Marshal.SizeOf(typeof(MemoryStatus)); +#else + _length = (uint)Marshal.SizeOf(); +#endif + } + + /// + /// Size of the structure, in bytes. You must set this member before calling GlobalMemoryStatusEx. + /// + private uint _length; + + /// + /// Number between 0 and 100 that specifies the approximate percentage of physical + /// memory that is in use (0 indicates no memory use and 100 indicates full memory use). + /// + public uint MemoryLoad; + + /// + /// Total size of physical memory, in bytes. + /// + public ulong TotalPhysical; + + /// + /// Size of physical memory available, in bytes. + /// + public ulong AvailablePhysical; + + /// + /// Size of the committed memory limit, in bytes. This is physical memory plus the + /// size of the page file, minus a small overhead. + /// + public ulong TotalPageFile; + + /// + /// Size of available memory to commit, in bytes. The limit is ullTotalPageFile. + /// + public ulong AvailablePageFile; + + /// + /// Total size of the user mode portion of the virtual address space of the calling process, in bytes. + /// + public ulong TotalVirtual; + + /// + /// Size of unreserved and uncommitted memory in the user mode portion of the virtual + /// address space of the calling process, in bytes. + /// + public ulong AvailableVirtual; + + /// + /// Size of unreserved and uncommitted memory in the extended portion of the virtual + /// address space of the calling process, in bytes. + /// + public ulong AvailableExtendedVirtual; + } + + [StructLayout(LayoutKind.Sequential)] + private struct PROCESS_BASIC_INFORMATION + { + public uint ExitStatus; + public IntPtr PebBaseAddress; + public UIntPtr AffinityMask; + public int BasePriority; + public UIntPtr UniqueProcessId; + public UIntPtr InheritedFromUniqueProcessId; + + public readonly uint Size + { + get + { + unsafe + { + return (uint)sizeof(PROCESS_BASIC_INFORMATION); + } + } + } + }; + + /// + /// Contains information about a file or directory; used by GetFileAttributesEx. + /// + [StructLayout(LayoutKind.Sequential)] + public struct WIN32_FILE_ATTRIBUTE_DATA + { + internal int fileAttributes; + internal uint ftCreationTimeLow; + internal uint ftCreationTimeHigh; + internal uint ftLastAccessTimeLow; + internal uint ftLastAccessTimeHigh; + internal uint ftLastWriteTimeLow; + internal uint ftLastWriteTimeHigh; + internal uint fileSizeHigh; + internal uint fileSizeLow; + } + + /// + /// Contains the security descriptor for an object and specifies whether + /// the handle retrieved by specifying this structure is inheritable. + /// + [StructLayout(LayoutKind.Sequential)] + internal class SecurityAttributes + { + public SecurityAttributes() + { +#if (CLR2COMPATIBILITY) + _nLength = (uint)Marshal.SizeOf(typeof(SecurityAttributes)); +#else + _nLength = (uint)Marshal.SizeOf(); +#endif + } + + private uint _nLength; + + public IntPtr lpSecurityDescriptor; + + public bool bInheritHandle; + } + + private class SystemInformationData + { + /// + /// Architecture as far as the current process is concerned. + /// It's x86 in wow64 (native architecture is x64 in that case). + /// Otherwise it's the same as the native architecture. + /// + public readonly ProcessorArchitectures ProcessorArchitectureType; + + /// + /// Actual architecture of the system. + /// + public readonly ProcessorArchitectures ProcessorArchitectureTypeNative; + + /// + /// Convert SYSTEM_INFO architecture values to the internal enum + /// + /// + /// + private static ProcessorArchitectures ConvertSystemArchitecture(ushort arch) + { + return arch switch + { + PROCESSOR_ARCHITECTURE_INTEL => ProcessorArchitectures.X86, + PROCESSOR_ARCHITECTURE_AMD64 => ProcessorArchitectures.X64, + PROCESSOR_ARCHITECTURE_ARM => ProcessorArchitectures.ARM, + PROCESSOR_ARCHITECTURE_IA64 => ProcessorArchitectures.IA64, + PROCESSOR_ARCHITECTURE_ARM64 => ProcessorArchitectures.ARM64, + _ => ProcessorArchitectures.Unknown, + }; + } + + /// + /// Read system info values + /// + public SystemInformationData() + { + ProcessorArchitectureType = ProcessorArchitectures.Unknown; + ProcessorArchitectureTypeNative = ProcessorArchitectures.Unknown; + + if (IsWindows) + { + var systemInfo = new SYSTEM_INFO(); + + GetSystemInfo(ref systemInfo); + ProcessorArchitectureType = ConvertSystemArchitecture(systemInfo.wProcessorArchitecture); + + GetNativeSystemInfo(ref systemInfo); + ProcessorArchitectureTypeNative = ConvertSystemArchitecture(systemInfo.wProcessorArchitecture); + } + else + { + ProcessorArchitectures processorArchitecture = ProcessorArchitectures.Unknown; + +#if NET || NETSTANDARD1_1_OR_GREATER + // Get the architecture from the runtime. + processorArchitecture = RuntimeInformation.OSArchitecture switch + { + Architecture.Arm => ProcessorArchitectures.ARM, + Architecture.Arm64 => ProcessorArchitectures.ARM64, + Architecture.X64 => ProcessorArchitectures.X64, + Architecture.X86 => ProcessorArchitectures.X86, +#if NET + Architecture.Wasm => ProcessorArchitectures.WASM, + Architecture.S390x => ProcessorArchitectures.S390X, + Architecture.LoongArch64 => ProcessorArchitectures.LOONGARCH64, + Architecture.Armv6 => ProcessorArchitectures.ARMV6, + Architecture.Ppc64le => ProcessorArchitectures.PPC64LE, +#endif + _ => ProcessorArchitectures.Unknown, + }; + +#endif + + ProcessorArchitectureTypeNative = ProcessorArchitectureType = processorArchitecture; + } + } + } + + public static int GetLogicalCoreCount() + { + int numberOfCpus = Environment.ProcessorCount; + // .NET on Windows returns a core count limited to the current NUMA node + // https://github.com/dotnet/runtime/issues/29686 + // so always double-check it. + if (IsWindows) + { + var result = GetLogicalCoreCountOnWindows(); + if (result != -1) + { + numberOfCpus = result; + } + } + + return numberOfCpus; + } + + /// + /// Get the exact physical core count on Windows + /// Useful for getting the exact core count in 32 bits processes, + /// as Environment.ProcessorCount has a 32-core limit in that case. + /// https://github.com/dotnet/runtime/blob/221ad5b728f93489655df290c1ea52956ad8f51c/src/libraries/System.Runtime.Extensions/src/System/Environment.Windows.cs#L171-L210 + /// + [SupportedOSPlatform("windows")] + private static unsafe int GetLogicalCoreCountOnWindows() + { + uint len = 0; + const int ERROR_INSUFFICIENT_BUFFER = 122; + + if (!GetLogicalProcessorInformationEx(LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorCore, IntPtr.Zero, ref len) && + Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER) + { + // Allocate that much space + var buffer = new byte[len]; + fixed (byte* bufferPtr = buffer) + { + // Call GetLogicalProcessorInformationEx with the allocated buffer + if (GetLogicalProcessorInformationEx(LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorCore, (IntPtr)bufferPtr, ref len)) + { + // Walk each SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX in the buffer, where the Size of each dictates how + // much space it's consuming. For each group relation, count the number of active processors in each of its group infos. + int processorCount = 0; + byte* ptr = bufferPtr; + byte* endPtr = bufferPtr + len; + while (ptr < endPtr) + { + var current = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)ptr; + if (current->Relationship == LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorCore) + { + // Flags is 0 if the core has a single logical proc, LTP_PC_SMT if more than one + // for now, assume "more than 1" == 2, as it has historically been for hyperthreading + processorCount += (current->Processor.Flags == 0) ? 1 : 2; + } + ptr += current->Size; + } + return processorCount; + } + } + } + + return -1; + } + + #endregion + + #region Member data + + internal static bool HasMaxPath => MaxPath == MAX_PATH; + + /// + /// Gets the max path limit of the current OS. + /// + internal static int MaxPath + { + get + { + if (!IsMaxPathSet) + { + SetMaxPath(); + } + return _maxPath; + } + } + + /// + /// Cached value for MaxPath. + /// + private static int _maxPath; + + private static bool IsMaxPathSet { get; set; } + + private static readonly LockType MaxPathLock = new LockType(); + + private static void SetMaxPath() + { + lock (MaxPathLock) + { + if (!IsMaxPathSet) + { + bool isMaxPathRestricted = Traits.Instance.EscapeHatches.DisableLongPaths || IsMaxPathLegacyWindows(); + _maxPath = isMaxPathRestricted ? MAX_PATH : int.MaxValue; + IsMaxPathSet = true; + } + } + } + + internal enum LongPathsStatus + { + /// + /// The registry key is set to 0 or does not exist. + /// + Disabled, + + /// + /// The registry key does not exist. + /// + Missing, + + /// + /// The registry key is set to 1. + /// + Enabled, + + /// + /// Not on Windows. + /// + NotApplicable, + } + + internal static LongPathsStatus IsLongPathsEnabled() + { + if (!IsWindows) + { + return LongPathsStatus.NotApplicable; + } + + try + { + return IsLongPathsEnabledRegistry(); + } + catch + { + return LongPathsStatus.Disabled; + } + } + + internal static bool IsMaxPathLegacyWindows() + { + var longPathsStatus = IsLongPathsEnabled(); + return longPathsStatus == LongPathsStatus.Disabled || longPathsStatus == LongPathsStatus.Missing; + } + + [SupportedOSPlatform("windows")] + private static LongPathsStatus IsLongPathsEnabledRegistry() + { + using (RegistryKey fileSystemKey = Registry.LocalMachine.OpenSubKey(WINDOWS_FILE_SYSTEM_REGISTRY_KEY)) + { + object longPathsEnabledValue = fileSystemKey?.GetValue(WINDOWS_LONG_PATHS_ENABLED_VALUE_NAME, -1); + if (fileSystemKey != null && Convert.ToInt32(longPathsEnabledValue) == -1) + { + return LongPathsStatus.Missing; + } + else if (fileSystemKey != null && Convert.ToInt32(longPathsEnabledValue) == 1) + { + return LongPathsStatus.Enabled; + } + else + { + return LongPathsStatus.Disabled; + } + } + } + + private static SAC_State? s_sacState; + + /// + /// Get from registry state of the Smart App Control (SAC) on the system. + /// + /// State of SAC + internal static SAC_State GetSACState() + { + s_sacState ??= GetSACStateInternal(); + + return s_sacState.Value; + } + + internal static SAC_State GetSACStateInternal() + { + if (IsWindows) + { + try + { + return GetSACStateRegistry(); + } + catch + { + return SAC_State.Missing; + } + } + + return SAC_State.NotApplicable; + } + + [SupportedOSPlatform("windows")] + private static SAC_State GetSACStateRegistry() + { + SAC_State SACState = SAC_State.Missing; + + using (RegistryKey policyKey = Registry.LocalMachine.OpenSubKey(WINDOWS_SAC_REGISTRY_KEY)) + { + if (policyKey != null) + { + object sacValue = policyKey.GetValue(WINDOWS_SAC_VALUE_NAME, -1); + SACState = Convert.ToInt32(sacValue) switch + { + 0 => SAC_State.Off, + 1 => SAC_State.Enforcement, + 2 => SAC_State.Evaluation, + _ => SAC_State.Missing, + }; + } + } + + return SACState; + } + + /// + /// State of Smart App Control (SAC) on the system. + /// + internal enum SAC_State + { + /// + /// 1: SAC is on and enforcing. + /// + Enforcement, + /// + /// 2: SAC is on and in evaluation mode. + /// + Evaluation, + /// + /// 0: SAC is off. + /// + Off, + /// + /// The registry key is missing. + /// + Missing, + /// + /// Not on Windows. + /// + NotApplicable + } + + /// + /// Cached value for IsUnixLike (this method is called frequently during evaluation). + /// + private static readonly bool s_isUnixLike = IsLinux || IsOSX || IsBSD; + + /// + /// Gets a flag indicating if we are running under a Unix-like system (Mac, Linux, etc.) + /// + internal static bool IsUnixLike + { + get { return s_isUnixLike; } + } + + /// + /// Gets a flag indicating if we are running under Linux + /// + [SupportedOSPlatformGuard("linux")] + internal static bool IsLinux + { +#if CLR2COMPATIBILITY + get { return false; } +#else + get { return RuntimeInformation.IsOSPlatform(OSPlatform.Linux); } +#endif + } + + /// + /// Gets a flag indicating if we are running under flavor of BSD (NetBSD, OpenBSD, FreeBSD) + /// + internal static bool IsBSD + { +#if CLR2COMPATIBILITY + get { return false; } +#else + get + { + return RuntimeInformation.IsOSPlatform(OSPlatform.Create("FREEBSD")) || + RuntimeInformation.IsOSPlatform(OSPlatform.Create("NETBSD")) || + RuntimeInformation.IsOSPlatform(OSPlatform.Create("OPENBSD")); + } +#endif + } + +#if !CLR2COMPATIBILITY + private static bool? _isWindows; +#endif + /// + /// Gets a flag indicating if we are running under some version of Windows + /// + [SupportedOSPlatformGuard("windows")] + internal static bool IsWindows + { +#if CLR2COMPATIBILITY + get { return true; } +#else + get + { + _isWindows ??= RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + return _isWindows.Value; + } +#endif + } + +#if !CLR2COMPATIBILITY + private static bool? _isOSX; +#endif + + /// + /// Gets a flag indicating if we are running under Mac OSX + /// + internal static bool IsOSX + { +#if CLR2COMPATIBILITY + get { return false; } +#else + get + { + _isOSX ??= RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + return _isOSX.Value; + } +#endif + } + + /// + /// Gets a string for the current OS. This matches the OS env variable + /// for Windows (Windows_NT). + /// + internal static string OSName + { + get { return IsWindows ? "Windows_NT" : "Unix"; } + } + + /// + /// Framework named as presented to users (for example in version info). + /// + internal static string FrameworkName + { + get + { +#if RUNTIME_TYPE_NETCORE + const string frameworkName = ".NET"; +#else + const string frameworkName = ".NET Framework"; +#endif + return frameworkName; + } + } + + /// + /// OS name that can be used for the msbuildExtensionsPathSearchPaths element + /// for a toolset + /// + internal static string GetOSNameForExtensionsPath() + { + return IsOSX ? "osx" : IsUnixLike ? "unix" : "windows"; + } + + internal static bool OSUsesCaseSensitivePaths + { + get { return IsLinux; } + } + +#if !CLR2COMPATIBILITY + /// + /// Determines whether the file system is case sensitive by creating a test file. + /// Copied from FileUtilities.GetIsFileSystemCaseSensitive() in Shared. + /// FIXME: shared code should be consolidated to Framework https://github.com/dotnet/msbuild/issues/6984 + /// + private static readonly Lazy s_isFileSystemCaseSensitive = new(() => + { + try + { + string pathWithUpperCase = Path.Combine(Path.GetTempPath(), $"INTCASESENSITIVETEST{Guid.NewGuid():N}"); + using (new FileStream(pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose)) + { + return !File.Exists(pathWithUpperCase.ToLowerInvariant()); + } + } + catch + { + return OSUsesCaseSensitivePaths; + } + }); + + internal static bool IsFileSystemCaseSensitive => s_isFileSystemCaseSensitive.Value; +#endif + + /// + /// The base directory for all framework paths in Mono + /// + private static string s_frameworkBasePath; + + /// + /// The directory of the current framework + /// + private static string s_frameworkCurrentPath; + + /// + /// Gets the currently running framework path + /// + internal static string FrameworkCurrentPath + { + get + { + if (s_frameworkCurrentPath == null) + { + var baseTypeLocation = AssemblyUtilities.GetAssemblyLocation(typeof(string).GetTypeInfo().Assembly); + + s_frameworkCurrentPath = + Path.GetDirectoryName(baseTypeLocation) + ?? string.Empty; + } + + return s_frameworkCurrentPath; + } + } + + /// + /// Gets the base directory of all Mono frameworks + /// + internal static string FrameworkBasePath + { + get + { + if (s_frameworkBasePath == null) + { + var dir = FrameworkCurrentPath; + if (dir != string.Empty) + { + dir = Path.GetDirectoryName(dir); + } + + s_frameworkBasePath = dir ?? string.Empty; + } + + return s_frameworkBasePath; + } + } + + /// + /// System information, initialized when required. + /// + /// + /// Initially implemented as , but + /// that's .NET 4+, and this is used in MSBuildTaskHost. + /// + private static SystemInformationData SystemInformation + { + get + { + if (!_systemInformationInitialized) + { + lock (SystemInformationLock) + { + if (!_systemInformationInitialized) + { + _systemInformation = new SystemInformationData(); + _systemInformationInitialized = true; + } + } + } + return _systemInformation; + } + } + + private static SystemInformationData _systemInformation; + private static bool _systemInformationInitialized; + private static readonly LockType SystemInformationLock = new LockType(); + + /// + /// Architecture getter + /// + internal static ProcessorArchitectures ProcessorArchitecture => SystemInformation.ProcessorArchitectureType; + + /// + /// Native architecture getter + /// + internal static ProcessorArchitectures ProcessorArchitectureNative => SystemInformation.ProcessorArchitectureTypeNative; + + #endregion + + #region Wrapper methods + + + [DllImport("kernel32.dll", SetLastError = true)] + [SupportedOSPlatform("windows")] + internal static extern void GetSystemInfo(ref SYSTEM_INFO lpSystemInfo); + + [DllImport("kernel32.dll", SetLastError = true)] + [SupportedOSPlatform("windows")] + internal static extern void GetNativeSystemInfo(ref SYSTEM_INFO lpSystemInfo); + + [DllImport("kernel32.dll", SetLastError = true)] + [SupportedOSPlatform("windows")] + internal static extern bool GetLogicalProcessorInformationEx(LOGICAL_PROCESSOR_RELATIONSHIP RelationshipType, IntPtr Buffer, ref uint ReturnedLength); + + /// + /// Get the last write time of the fullpath to a directory. If the pointed path is not a directory, or + /// if the directory does not exist, then false is returned and fileModifiedTimeUtc is set DateTime.MinValue. + /// + /// Full path to the file in the filesystem + /// The UTC last write time for the directory + internal static bool GetLastWriteDirectoryUtcTime(string fullPath, out DateTime fileModifiedTimeUtc) + { + // This code was copied from the reference manager, if there is a bug fix in that code, see if the same fix should also be made + // there + if (IsWindows) + { + fileModifiedTimeUtc = DateTime.MinValue; + + WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); + bool success = GetFileAttributesEx(fullPath, 0, ref data); + if (success) + { + if ((data.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + { + long dt = ((long)(data.ftLastWriteTimeHigh) << 32) | ((long)data.ftLastWriteTimeLow); + fileModifiedTimeUtc = DateTime.FromFileTimeUtc(dt); + } + else + { + // Path does not point to a directory + success = false; + } + } + + return success; + } + + if (Directory.Exists(fullPath)) + { + fileModifiedTimeUtc = Directory.GetLastWriteTimeUtc(fullPath); + return true; + } + else + { + fileModifiedTimeUtc = DateTime.MinValue; + return false; + } + } + + /// + /// Takes the path and returns the short path + /// + internal static string GetShortFilePath(string path) + { + if (!IsWindows) + { + return path; + } + + if (path != null) + { + int length = GetShortPathName(path, null, 0); + int errorCode = Marshal.GetLastWin32Error(); + + if (length > 0) + { + char[] fullPathBuffer = new char[length]; + length = GetShortPathName(path, fullPathBuffer, length); + errorCode = Marshal.GetLastWin32Error(); + + if (length > 0) + { + string fullPath = new(fullPathBuffer, 0, length); + path = fullPath; + } + } + + if (length == 0 && errorCode != 0) + { + ThrowExceptionForErrorCode(errorCode); + } + } + + return path; + } + + /// + /// Takes the path and returns a full path + /// + /// + /// + [SupportedOSPlatform("windows")] + internal static string GetLongFilePath(string path) + { + if (IsUnixLike) + { + return path; + } + + if (path != null) + { + int length = GetLongPathName(path, null, 0); + int errorCode = Marshal.GetLastWin32Error(); + + if (length > 0) + { + char[] fullPathBuffer = new char[length]; + length = GetLongPathName(path, fullPathBuffer, length); + errorCode = Marshal.GetLastWin32Error(); + + if (length > 0) + { + string fullPath = new(fullPathBuffer, 0, length); + path = fullPath; + } + } + + if (length == 0 && errorCode != 0) + { + ThrowExceptionForErrorCode(errorCode); + } + } + + return path; + } + + /// + /// Retrieves the current global memory status. + /// + internal static MemoryStatus GetMemoryStatus() + { + if (IsWindows) + { + MemoryStatus status = new MemoryStatus(); + bool returnValue = GlobalMemoryStatusEx(status); + if (!returnValue) + { + return null; + } + + return status; + } + + return null; + } + + internal static bool MakeSymbolicLink(string newFileName, string existingFileName, ref string errorMessage) + { + bool symbolicLinkCreated; + if (IsWindows) + { + Version osVersion = Environment.OSVersion.Version; + SymbolicLink flags = SymbolicLink.File; + if (osVersion.Major >= 11 || (osVersion.Major == 10 && osVersion.Build >= 14972)) + { + flags |= SymbolicLink.AllowUnprivilegedCreate; + } + + symbolicLinkCreated = CreateSymbolicLink(newFileName, existingFileName, flags); + errorMessage = symbolicLinkCreated ? null : Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()).Message; + } + else + { + symbolicLinkCreated = symlink(existingFileName, newFileName) == 0; + errorMessage = symbolicLinkCreated ? null : Marshal.GetLastWin32Error().ToString(); + } + + return symbolicLinkCreated; + } + + /// + /// Get the last write time of the fullpath to the file. + /// + /// Full path to the file in the filesystem + /// The last write time of the file, or DateTime.MinValue if the file does not exist. + /// + /// This method should be accurate for regular files and symlinks, but can report incorrect data + /// if the file's content was modified by writing to it through a different link, unless + /// MSBUILDALWAYSCHECKCONTENTTIMESTAMP=1. + /// + internal static DateTime GetLastWriteFileUtcTime(string fullPath) + { +#if !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS + if (Traits.Instance.EscapeHatches.AlwaysDoImmutableFilesUpToDateCheck) + { + return LastWriteFileUtcTime(fullPath); + } + + bool isNonModifiable = FileClassifier.Shared.IsNonModifiable(fullPath); + if (isNonModifiable) + { + if (ImmutableFilesTimestampCache.Shared.TryGetValue(fullPath, out DateTime modifiedAt)) + { + return modifiedAt; + } + } + + DateTime modifiedTime = LastWriteFileUtcTime(fullPath); + + if (isNonModifiable && modifiedTime != DateTime.MinValue) + { + ImmutableFilesTimestampCache.Shared.TryAdd(fullPath, modifiedTime); + } + + return modifiedTime; +#else + return LastWriteFileUtcTime(fullPath); +#endif + + DateTime LastWriteFileUtcTime(string path) + { + DateTime fileModifiedTime = DateTime.MinValue; + + if (IsWindows) + { + if (Traits.Instance.EscapeHatches.AlwaysUseContentTimestamp) + { + return GetContentLastWriteFileUtcTime(path); + } + + WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); + bool success = NativeMethods.GetFileAttributesEx(path, 0, ref data); + + if (success && (data.fileAttributes & NativeMethods.FILE_ATTRIBUTE_DIRECTORY) == 0) + { + long dt = ((long)(data.ftLastWriteTimeHigh) << 32) | ((long)data.ftLastWriteTimeLow); + fileModifiedTime = DateTime.FromFileTimeUtc(dt); + + // If file is a symlink _and_ we're not instructed to do the wrong thing, get a more accurate timestamp. + if ((data.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT && !Traits.Instance.EscapeHatches.UseSymlinkTimeInsteadOfTargetTime) + { + fileModifiedTime = GetContentLastWriteFileUtcTime(path); + } + } + + return fileModifiedTime; + } + else + { + return File.Exists(path) + ? File.GetLastWriteTimeUtc(path) + : DateTime.MinValue; + } + } + } + + /// + /// Get the SafeFileHandle for a file, while skipping reparse points (going directly to target file). + /// + /// Full path to the file in the filesystem + /// the SafeFileHandle for a file (target file in case of symlinks) + [SupportedOSPlatform("windows")] + private static SafeFileHandle OpenFileThroughSymlinks(string fullPath) + { + return CreateFile(fullPath, + GENERIC_READ, + FILE_SHARE_READ, + IntPtr.Zero, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, /* No FILE_FLAG_OPEN_REPARSE_POINT; read through to content */ + IntPtr.Zero); + } + + /// + /// Get the last write time of the content pointed to by a file path. + /// + /// Full path to the file in the filesystem + /// The last write time of the file, or DateTime.MinValue if the file does not exist. + /// + /// This is the most accurate timestamp-extraction mechanism, but it is too slow to use all the time. + /// See https://github.com/dotnet/msbuild/issues/2052. + /// + [SupportedOSPlatform("windows")] + private static DateTime GetContentLastWriteFileUtcTime(string fullPath) + { + DateTime fileModifiedTime = DateTime.MinValue; + + using (SafeFileHandle handle = OpenFileThroughSymlinks(fullPath)) + { + if (!handle.IsInvalid) + { + FILETIME ftCreationTime, ftLastAccessTime, ftLastWriteTime; + if (GetFileTime(handle, out ftCreationTime, out ftLastAccessTime, out ftLastWriteTime)) + { + long fileTime = ((long)(uint)ftLastWriteTime.dwHighDateTime) << 32 | + (long)(uint)ftLastWriteTime.dwLowDateTime; + fileModifiedTime = + DateTime.FromFileTimeUtc(fileTime); + } + } + } + + return fileModifiedTime; + } + + /// + /// Did the HRESULT succeed + /// + public static bool HResultSucceeded(int hr) + { + return hr >= 0; + } + + /// + /// Did the HRESULT Fail + /// + public static bool HResultFailed(int hr) + { + return hr < 0; + } + + /// + /// Given an error code, converts it to an HRESULT and throws the appropriate exception. + /// + /// + public static void ThrowExceptionForErrorCode(int errorCode) + { + // See ndp\clr\src\bcl\system\io\__error.cs for this code as it appears in the CLR. + + // Something really bad went wrong with the call + // translate the error into an exception + + // Convert the errorcode into an HRESULT (See MakeHRFromErrorCode in Win32Native.cs in + // ndp\clr\src\bcl\microsoft\win32) + errorCode = unchecked(((int)0x80070000) | errorCode); + + // Throw an exception as best we can + Marshal.ThrowExceptionForHR(errorCode); + } + + /// + /// Kills the specified process by id and all of its children recursively. + /// + [SupportedOSPlatform("windows")] + internal static void KillTree(int processIdToKill) + { + // Note that GetProcessById does *NOT* internally hold on to the process handle. + // Only when you create the process using the Process object + // does the Process object retain the original handle. + + Process thisProcess; + try + { + thisProcess = Process.GetProcessById(processIdToKill); + } + catch (ArgumentException) + { + // The process has already died for some reason. So shrug and assume that any child processes + // have all also either died or are in the process of doing so. + return; + } + + try + { + DateTime myStartTime = thisProcess.StartTime; + + // Grab the process handle. We want to keep this open for the duration of the function so that + // it cannot be reused while we are running. + using (SafeProcessHandle hProcess = OpenProcess(eDesiredAccess.PROCESS_QUERY_INFORMATION, false, processIdToKill)) + { + if (hProcess.IsInvalid) + { + return; + } + + try + { + // Kill this process, so that no further children can be created. + thisProcess.Kill(); + } + catch (Win32Exception e) when (e.NativeErrorCode == ERROR_ACCESS_DENIED) + { + // Access denied is potentially expected -- it happens when the process that + // we're attempting to kill is already dead. So just ignore in that case. + } + + // Now enumerate our children. Children of this process are any process which has this process id as its parent + // and which also started after this process did. + List> children = GetChildProcessIds(processIdToKill, myStartTime); + + try + { + foreach (KeyValuePair childProcessInfo in children) + { + KillTree(childProcessInfo.Key); + } + } + finally + { + foreach (KeyValuePair childProcessInfo in children) + { + childProcessInfo.Value.Dispose(); + } + } + } + } + finally + { + thisProcess.Dispose(); + } + } + + /// + /// Returns the parent process id for the specified process. + /// Returns zero if it cannot be gotten for some reason. + /// + [SupportedOSPlatform("windows")] + internal static int GetParentProcessId(int processId) + { + int ParentID = 0; +#if !CLR2COMPATIBILITY + if (IsUnixLike) + { + string line = null; + + try + { + // /proc//stat returns a bunch of space separated fields. Get that string + + // TODO: this was + // using (var r = FileUtilities.OpenRead("/proc/" + processId + "/stat")) + // and could be again when FileUtilities moves to Framework + + using var fileStream = new FileStream($"/proc/{processId}/stat", FileMode.Open, System.IO.FileAccess.Read); + using StreamReader r = new(fileStream); + + line = r.ReadLine(); + } + catch // Ignore errors since the process may have terminated + { + } + + if (!string.IsNullOrWhiteSpace(line)) + { + // One of the fields is the process name. It may contain any characters, but since it's + // in parenthesis, we can finds its end by looking for the last parenthesis. After that, + // there comes a space, then the second fields separated by a space is the parent id. + string[] statFields = line.Substring(line.LastIndexOf(')')).Split(MSBuildConstants.SpaceChar, 4); + if (statFields.Length >= 3) + { + ParentID = Int32.Parse(statFields[2]); + } + } + } + else +#endif + { + using SafeProcessHandle hProcess = OpenProcess(eDesiredAccess.PROCESS_QUERY_INFORMATION, false, processId); + { + if (!hProcess.IsInvalid) + { + // UNDONE: NtQueryInformationProcess will fail if we are not elevated and other process is. Advice is to change to use ToolHelp32 API's + // For now just return zero and worst case we will not kill some children. + PROCESS_BASIC_INFORMATION pbi = new PROCESS_BASIC_INFORMATION(); + int pSize = 0; + + if (0 == NtQueryInformationProcess(hProcess, PROCESSINFOCLASS.ProcessBasicInformation, ref pbi, pbi.Size, ref pSize)) + { + ParentID = (int)pbi.InheritedFromUniqueProcessId; + } + } + } + } + + return ParentID; + } + + /// + /// Returns an array of all the immediate child processes by id. + /// NOTE: The IntPtr in the tuple is the handle of the child process. CloseHandle MUST be called on this. + /// + [SupportedOSPlatform("windows")] + internal static List> GetChildProcessIds(int parentProcessId, DateTime parentStartTime) + { + List> myChildren = new List>(); + + foreach (Process possibleChildProcess in Process.GetProcesses()) + { + using (possibleChildProcess) + { + // Hold the child process handle open so that children cannot die and restart with a different parent after we've started looking at it. + // This way, any handle we pass back is guaranteed to be one of our actual children. +#pragma warning disable CA2000 // Dispose objects before losing scope - caller must dispose returned handles + SafeProcessHandle childHandle = OpenProcess(eDesiredAccess.PROCESS_QUERY_INFORMATION, false, possibleChildProcess.Id); +#pragma warning restore CA2000 // Dispose objects before losing scope + { + if (childHandle.IsInvalid) + { + continue; + } + + bool keepHandle = false; + try + { + if (possibleChildProcess.StartTime > parentStartTime) + { + int childParentProcessId = GetParentProcessId(possibleChildProcess.Id); + if (childParentProcessId != 0) + { + if (parentProcessId == childParentProcessId) + { + // Add this one + myChildren.Add(new KeyValuePair(possibleChildProcess.Id, childHandle)); + keepHandle = true; + } + } + } + } + finally + { + if (!keepHandle) + { + childHandle.Dispose(); + } + } + } + } + } + + return myChildren; + } + + /// + /// Internal, optimized GetCurrentDirectory implementation that simply delegates to the native method + /// + /// + internal static unsafe string GetCurrentDirectory() + { +#if FEATURE_LEGACY_GETCURRENTDIRECTORY + if (IsWindows) + { + int bufferSize = GetCurrentDirectoryWin32(0, null); + char* buffer = stackalloc char[bufferSize]; + int pathLength = GetCurrentDirectoryWin32(bufferSize, buffer); + return new string(buffer, startIndex: 0, length: pathLength); + } +#endif + return Directory.GetCurrentDirectory(); + } + + [SupportedOSPlatform("windows")] + private static unsafe int GetCurrentDirectoryWin32(int nBufferLength, char* lpBuffer) + { + int pathLength = GetCurrentDirectory(nBufferLength, lpBuffer); + VerifyThrowWin32Result(pathLength); + return pathLength; + } + + [SupportedOSPlatform("windows")] + internal static unsafe string GetFullPath(string path) + { + char* buffer = stackalloc char[MAX_PATH]; + int fullPathLength = GetFullPathWin32(path, MAX_PATH, buffer, IntPtr.Zero); + + // if user is using long paths we could need to allocate a larger buffer + if (fullPathLength > MAX_PATH) + { + char* newBuffer = stackalloc char[fullPathLength]; + fullPathLength = GetFullPathWin32(path, fullPathLength, newBuffer, IntPtr.Zero); + + buffer = newBuffer; + } + + // Avoid creating new strings unnecessarily + return AreStringsEqual(buffer, fullPathLength, path) ? path : new string(buffer, startIndex: 0, length: fullPathLength); + } + + [SupportedOSPlatform("windows")] + private static unsafe int GetFullPathWin32(string target, int bufferLength, char* buffer, IntPtr mustBeZero) + { + int pathLength = GetFullPathName(target, bufferLength, buffer, mustBeZero); + VerifyThrowWin32Result(pathLength); + return pathLength; + } + + /// + /// Compare an unsafe char buffer with a to see if their contents are identical. + /// + /// The beginning of the char buffer. + /// The length of the buffer. + /// The string. + /// True only if the contents of and the first characters in are identical. + private static unsafe bool AreStringsEqual(char* buffer, int len, string s) + { +#if CLR2COMPATIBILITY + if (len != s.Length) + { + return false; + } + + foreach (char ch in s) + { + if (ch != *buffer++) + { + return false; + } + } + + return true; +#else + return s.AsSpan().SequenceEqual(new ReadOnlySpan(buffer, len)); +#endif + } + + internal static void VerifyThrowWin32Result(int result) + { + bool isError = result == 0; + if (isError) + { + int code = Marshal.GetLastWin32Error(); + ThrowExceptionForErrorCode(code); + } + } + +#if !CLR2COMPATIBILITY + internal static (bool acceptAnsiColorCodes, bool outputIsScreen, uint? originalConsoleMode) QueryIsScreenAndTryEnableAnsiColorCodes(StreamHandleType handleType = StreamHandleType.StdOut) + { + if (Console.IsOutputRedirected) + { + // There's no ANSI terminal support if console output is redirected. + return (acceptAnsiColorCodes: false, outputIsScreen: false, originalConsoleMode: null); + } + + if (Console.BufferHeight == 0 || Console.BufferWidth == 0) + { + // The current console doesn't have a valid buffer size, which means it is not a real console. let's default to not using TL + // in those scenarios. + return (acceptAnsiColorCodes: false, outputIsScreen: false, originalConsoleMode: null); + } + + bool acceptAnsiColorCodes = false; + bool outputIsScreen = false; + uint? originalConsoleMode = null; + if (IsWindows) + { + try + { + IntPtr outputStream = GetStdHandle((int)handleType); + if (GetConsoleMode(outputStream, out uint consoleMode)) + { + if ((consoleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == ENABLE_VIRTUAL_TERMINAL_PROCESSING) + { + // Console is already in required state. + acceptAnsiColorCodes = true; + } + else + { + originalConsoleMode = consoleMode; + consoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + if (SetConsoleMode(outputStream, consoleMode) && GetConsoleMode(outputStream, out consoleMode)) + { + // We only know if vt100 is supported if the previous call actually set the new flag, older + // systems ignore the setting. + acceptAnsiColorCodes = (consoleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == ENABLE_VIRTUAL_TERMINAL_PROCESSING; + } + } + + uint fileType = GetFileType(outputStream); + // The std out is a char type (LPT or Console). + outputIsScreen = fileType == FILE_TYPE_CHAR; + acceptAnsiColorCodes &= outputIsScreen; + } + } + catch + { + // In the unlikely case that the above fails we just ignore and continue. + } + } + else + { + // On posix OSes detect whether the terminal supports VT100 from the value of the TERM environment variable. + acceptAnsiColorCodes = AnsiDetector.IsAnsiSupported(Environment.GetEnvironmentVariable("TERM")); + // It wasn't redirected as tested above so we assume output is screen/console + outputIsScreen = true; + } + return (acceptAnsiColorCodes, outputIsScreen, originalConsoleMode); + } + + internal static void RestoreConsoleMode(uint? originalConsoleMode, StreamHandleType handleType = StreamHandleType.StdOut) + { + if (IsWindows && originalConsoleMode is not null) + { + IntPtr stdOut = GetStdHandle((int)handleType); + _ = SetConsoleMode(stdOut, originalConsoleMode.Value); + } + } +#endif // !CLR2COMPATIBILITY + + #endregion + + #region PInvoke + [SupportedOSPlatform("linux")] + [DllImport("libc", SetLastError = true)] + internal static extern int chmod(string pathname, int mode); + + [SupportedOSPlatform("linux")] + [DllImport("libc", SetLastError = true)] + internal static extern int mkdir(string path, int mode); + + /// + /// Gets the current OEM code page which is used by console apps + /// (as opposed to the Windows/ANSI code page) + /// Basically for each ANSI code page (set in Regional settings) there's a corresponding OEM code page + /// that needs to be used for instance when writing to batch files + /// + [DllImport(kernel32Dll)] + [SupportedOSPlatform("windows")] + internal static extern int GetOEMCP(); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + [SupportedOSPlatform("windows")] + internal static extern bool GetFileAttributesEx(String name, int fileInfoLevel, ref WIN32_FILE_ATTRIBUTE_DATA lpFileInformation); + + [DllImport("kernel32.dll", PreserveSig = true, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + [SupportedOSPlatform("windows")] + internal static extern bool FreeLibrary([In] IntPtr module); + + [DllImport("kernel32.dll", PreserveSig = true, BestFitMapping = false, ThrowOnUnmappableChar = true, CharSet = CharSet.Ansi, SetLastError = true)] + [SupportedOSPlatform("windows")] + internal static extern IntPtr GetProcAddress(IntPtr module, string procName); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, PreserveSig = true, SetLastError = true)] + [SupportedOSPlatform("windows")] + internal static extern IntPtr LoadLibrary(string fileName); + + /// + /// Gets the fully qualified filename of the currently executing .exe. + /// + /// of the module for which we are finding the file name. + /// The character buffer used to return the file name. + /// The length of the buffer. + [DllImport(kernel32Dll, SetLastError = true, CharSet = CharSet.Unicode)] + [SupportedOSPlatform("windows")] + internal static extern int GetModuleFileName(HandleRef hModule, [Out] char[] buffer, int length); + + [DllImport("kernel32.dll")] + [SupportedOSPlatform("windows")] + internal static extern IntPtr GetStdHandle(int nStdHandle); + + [DllImport("kernel32.dll")] + [SupportedOSPlatform("windows")] + internal static extern uint GetFileType(IntPtr hFile); + + [DllImport("kernel32.dll")] + internal static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); + + [DllImport("kernel32.dll")] + internal static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); + + [SuppressMessage("Microsoft.Usage", "CA2205:UseManagedEquivalentsOfWin32Api", Justification = "Using unmanaged equivalent for performance reasons")] + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [SupportedOSPlatform("windows")] + internal static extern unsafe int GetCurrentDirectory(int nBufferLength, char* lpBuffer); + + [SuppressMessage("Microsoft.Usage", "CA2205:UseManagedEquivalentsOfWin32Api", Justification = "Using unmanaged equivalent for performance reasons")] + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetCurrentDirectory")] + [return: MarshalAs(UnmanagedType.Bool)] + [SupportedOSPlatform("windows")] + internal static extern bool SetCurrentDirectoryWindows(string path); + + internal static bool SetCurrentDirectory(string path) + { + if (IsWindows) + { + return SetCurrentDirectoryWindows(path); + } + + // Make sure this does not throw + try + { + Directory.SetCurrentDirectory(path); + } + catch + { + } + return true; + } + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [SupportedOSPlatform("windows")] + internal static extern unsafe int GetFullPathName(string target, int bufferLength, char* buffer, IntPtr mustBeZero); + + [DllImport("KERNEL32.DLL")] + [SupportedOSPlatform("windows")] + private static extern SafeProcessHandle OpenProcess(eDesiredAccess dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId); + + [DllImport("NTDLL.DLL")] + [SupportedOSPlatform("windows")] + private static extern int NtQueryInformationProcess(SafeProcessHandle hProcess, PROCESSINFOCLASS pic, ref PROCESS_BASIC_INFORMATION pbi, uint cb, ref int pSize); + + [return: MarshalAs(UnmanagedType.Bool)] + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [SupportedOSPlatform("windows")] + private static extern bool GlobalMemoryStatusEx([In, Out] MemoryStatus lpBuffer); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, BestFitMapping = false)] + [SupportedOSPlatform("windows")] + internal static extern int GetShortPathName(string path, [Out] char[] fullpath, [In] int length); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, BestFitMapping = false)] + [SupportedOSPlatform("windows")] + internal static extern int GetLongPathName([In] string path, [Out] char[] fullpath, [In] int length); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [SupportedOSPlatform("windows")] + internal static extern bool CreatePipe(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, SecurityAttributes lpPipeAttributes, int nSize); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [SupportedOSPlatform("windows")] + internal static extern bool ReadFile(SafeFileHandle hFile, byte[] lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped); + + /// + /// CoWaitForMultipleHandles allows us to wait in an STA apartment and still service RPC requests from other threads. + /// VS needs this in order to allow the in-proc compilers to properly initialize, since they will make calls from the + /// build thread which the main thread (blocked on BuildSubmission.Execute) must service. + /// + [DllImport("ole32.dll")] + [SupportedOSPlatform("windows")] + public static extern int CoWaitForMultipleHandles(COWAIT_FLAGS dwFlags, int dwTimeout, int cHandles, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] pHandles, out int pdwIndex); + + internal const uint GENERIC_READ = 0x80000000; + internal const uint FILE_SHARE_READ = 0x1; + internal const uint FILE_ATTRIBUTE_NORMAL = 0x80; + internal const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000; + internal const uint OPEN_EXISTING = 3; + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, + SetLastError = true)] + [SupportedOSPlatform("windows")] + internal static extern SafeFileHandle CreateFile( + string lpFileName, + uint dwDesiredAccess, + uint dwShareMode, + IntPtr lpSecurityAttributes, + uint dwCreationDisposition, + uint dwFlagsAndAttributes, + IntPtr hTemplateFile); + + [DllImport("kernel32.dll", SetLastError = true)] + [SupportedOSPlatform("windows")] + internal static extern bool GetFileTime( + SafeFileHandle hFile, + out FILETIME lpCreationTime, + out FILETIME lpLastAccessTime, + out FILETIME lpLastWriteTime); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + [SupportedOSPlatform("windows")] + internal static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError = true)] + [SupportedOSPlatform("windows")] + internal static extern bool SetThreadErrorMode(int newMode, out int oldMode); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.I1)] + [SupportedOSPlatform("windows")] + internal static extern bool CreateSymbolicLink(string symLinkFileName, string targetFileName, SymbolicLink dwFlags); + + [DllImport("libc", SetLastError = true)] + internal static extern int symlink(string oldpath, string newpath); + + #endregion + + #region helper methods + + internal static bool DirectoryExists(string fullPath) + { + return IsWindows + ? DirectoryExistsWindows(fullPath) + : Directory.Exists(fullPath); + } + + [SupportedOSPlatform("windows")] + internal static bool DirectoryExistsWindows(string fullPath) + { + WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); + bool success = GetFileAttributesEx(fullPath, 0, ref data); + return success && (data.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + } + + internal static bool FileExists(string fullPath) + { + return IsWindows + ? FileExistsWindows(fullPath) + : File.Exists(fullPath); + } + + [SupportedOSPlatform("windows")] + internal static bool FileExistsWindows(string fullPath) + { + WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); + bool success = GetFileAttributesEx(fullPath, 0, ref data); + return success && (data.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0; + } + + internal static bool FileOrDirectoryExists(string path) + { + return IsWindows + ? FileOrDirectoryExistsWindows(path) + : File.Exists(path) || Directory.Exists(path); + } + + [SupportedOSPlatform("windows")] + internal static bool FileOrDirectoryExistsWindows(string path) + { + WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); + return GetFileAttributesEx(path, 0, ref data); + } + + #endregion + +} diff --git a/src/MSBuildTaskHost/Framework/Polyfills/CallerArgumentExpressionAttribute.cs b/src/MSBuildTaskHost/Framework/Polyfills/CallerArgumentExpressionAttribute.cs new file mode 100644 index 00000000000..91623fbd9f9 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/Polyfills/CallerArgumentExpressionAttribute.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if NETCOREAPP3_0_OR_GREATER + +using System.Runtime.CompilerServices; + +// This is a supporting forwarder for an internal polyfill API +[assembly: TypeForwardedTo(typeof(CallerArgumentExpressionAttribute))] + +#else + +namespace System.Runtime.CompilerServices; + +[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] +internal sealed class CallerArgumentExpressionAttribute : Attribute +{ + public CallerArgumentExpressionAttribute(string parameterName) + { + ParameterName = parameterName; + } + + public string ParameterName { get; } +} + +#endif diff --git a/src/MSBuildTaskHost/Framework/Polyfills/StringSyntaxAttribute.cs b/src/MSBuildTaskHost/Framework/Polyfills/StringSyntaxAttribute.cs new file mode 100644 index 00000000000..2be24ae6499 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/Polyfills/StringSyntaxAttribute.cs @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if NET7_0_OR_GREATER + +using System.Runtime.CompilerServices; + +// This is a supporting forwarder for an internal polyfill API +[assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.StringSyntaxAttribute))] + +#else + +namespace System.Diagnostics.CodeAnalysis; + +[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] +internal sealed class StringSyntaxAttribute : Attribute +{ + /// Initializes the with the identifier of the syntax used. + /// The syntax identifier. + public StringSyntaxAttribute(string syntax) + { + Syntax = syntax; + Arguments = []; + } + + /// Initializes the with the identifier of the syntax used. + /// The syntax identifier. + /// Optional arguments associated with the specific syntax employed. + public StringSyntaxAttribute(string syntax, params object?[] arguments) + { + Syntax = syntax; + Arguments = arguments; + } + + /// Gets the identifier of the syntax used. + public string Syntax { get; } + + /// Optional arguments associated with the specific syntax employed. + public object?[] Arguments { get; } + + /// The syntax identifier for strings containing composite formats for string formatting. + public const string CompositeFormat = nameof(CompositeFormat); + + /// The syntax identifier for strings containing date format specifiers. + public const string DateOnlyFormat = nameof(DateOnlyFormat); + + /// The syntax identifier for strings containing date and time format specifiers. + public const string DateTimeFormat = nameof(DateTimeFormat); + + /// The syntax identifier for strings containing format specifiers. + public const string EnumFormat = nameof(EnumFormat); + + /// The syntax identifier for strings containing format specifiers. + public const string GuidFormat = nameof(GuidFormat); + + /// The syntax identifier for strings containing JavaScript Object Notation (JSON). + public const string Json = nameof(Json); + + /// The syntax identifier for strings containing numeric format specifiers. + public const string NumericFormat = nameof(NumericFormat); + + /// The syntax identifier for strings containing regular expressions. + public const string Regex = nameof(Regex); + + /// The syntax identifier for strings containing time format specifiers. + public const string TimeOnlyFormat = nameof(TimeOnlyFormat); + + /// The syntax identifier for strings containing format specifiers. + public const string TimeSpanFormat = nameof(TimeSpanFormat); + + /// The syntax identifier for strings containing URIs. + public const string Uri = nameof(Uri); + + /// The syntax identifier for strings containing XML. + public const string Xml = nameof(Xml); +} + +#endif diff --git a/src/MSBuildTaskHost/Framework/ResponseFileUsedEventArgs.cs b/src/MSBuildTaskHost/Framework/ResponseFileUsedEventArgs.cs new file mode 100644 index 00000000000..7e9f132a262 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/ResponseFileUsedEventArgs.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Build.Framework +{ + /// + /// Arguments for the response file used event + /// + [Serializable] + public class ResponseFileUsedEventArgs : BuildMessageEventArgs + { + public ResponseFileUsedEventArgs() + { + } + /// + /// Initialize a new instance of the ResponseFileUsedEventArgs class. + /// + public ResponseFileUsedEventArgs(string? responseFilePath) : base() + { + ResponseFilePath = responseFilePath; + } + public string? ResponseFilePath { set; get; } + } +} diff --git a/src/MSBuildTaskHost/Framework/RunInSTAAttribute.cs b/src/MSBuildTaskHost/Framework/RunInSTAAttribute.cs new file mode 100644 index 00000000000..259bc5f968c --- /dev/null +++ b/src/MSBuildTaskHost/Framework/RunInSTAAttribute.cs @@ -0,0 +1,26 @@ +// 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.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Microsoft.Build.Framework +{ + /// + /// This attribute is used to mark a task class as being required to run in a Single Threaded Apartment for COM. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "STA", Justification = "It is cased correctly.")] + public sealed class RunInSTAAttribute : Attribute + { + /// + /// Default constructor. + /// + public RunInSTAAttribute() + { + // do nothing + } + } +} diff --git a/src/MSBuildTaskHost/Framework/SerializableMetadata.cs b/src/MSBuildTaskHost/Framework/SerializableMetadata.cs new file mode 100644 index 00000000000..857a4c15fb6 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/SerializableMetadata.cs @@ -0,0 +1,62 @@ +// 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; +using System.Collections.Immutable; +using System.Linq; +using System.Runtime.Serialization; + +namespace Microsoft.Build.Framework +{ + /// + /// Serializable wrapper for immutable metadata dictionaries. + /// Safe to use across AppDomains. + /// + [Serializable] + internal readonly struct SerializableMetadata : ISerializable + { + /// + /// Initializes a new instance of the struct. + /// + /// The metadata dictionary to set. + /// + /// Calling this constructor implies that the instance value is usable. As such, a null input will be converted + /// to an empty instance. + /// + public SerializableMetadata(ImmutableDictionary dictionary) => + Dictionary = dictionary ?? ImmutableDictionaryExtensions.EmptyMetadata; + + public SerializableMetadata(SerializationInfo info, StreamingContext context) + { + bool hasValue = info.GetBoolean("hasValue"); + if (hasValue) + { + object entries = info.GetValue("value", typeof(KeyValuePair[]))!; + Dictionary = ImmutableDictionaryExtensions.EmptyMetadata.AddRange((KeyValuePair[])entries); + } + } + + /// + /// Gets the backing metadata dictionary. + /// + internal ImmutableDictionary? Dictionary { get; } + + /// + /// Gets a value indicating whether the wrapped dictionary represents a usable instance. + /// + /// + /// Since SerializableMetadata is a struct, this allows the default value to represent an unusable instance. + /// + internal bool HasValue => Dictionary != null; + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("hasValue", HasValue); + if (HasValue) + { + info.AddValue("value", Dictionary!.ToArray()); + } + } + } +} diff --git a/src/MSBuildTaskHost/Framework/StringBuilderCache.cs b/src/MSBuildTaskHost/Framework/StringBuilderCache.cs new file mode 100644 index 00000000000..5fd67790e9d --- /dev/null +++ b/src/MSBuildTaskHost/Framework/StringBuilderCache.cs @@ -0,0 +1,115 @@ +// 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.Diagnostics; +using System.Text; +#if DEBUG && !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS +using Microsoft.Build.Eventing; +#endif + +#nullable disable + +namespace Microsoft.Build.Framework +{ + /// + /// A cached reusable instance of StringBuilder. + /// + /// + /// An optimization that reduces the number of instances of constructed and collected. + /// + internal static class StringBuilderCache + { + // The value 512 was chosen empirically as 95% percentile of returning string length. + private const int MAX_BUILDER_SIZE = 512; + + [ThreadStatic] + private static StringBuilder t_cachedInstance; + + /// + /// Get a of at least the specified capacity. + /// + /// The suggested starting size of this instance. + /// A that may or may not be reused. + /// + /// It can be called any number of times; if a is in the cache then + /// it will be returned and the cache emptied. Subsequent calls will return a new . + /// + /// The instance is cached in Thread Local Storage and so there is one per thread. + /// + public static StringBuilder Acquire(int capacity = 16 /*StringBuilder.DefaultCapacity*/) + { + if (capacity <= MAX_BUILDER_SIZE) + { + StringBuilder sb = StringBuilderCache.t_cachedInstance; + StringBuilderCache.t_cachedInstance = null; + if (sb != null) + { + // Avoid StringBuilder block fragmentation by getting a new StringBuilder + // when the requested size is larger than the current capacity + if (capacity <= sb.Capacity) + { + sb.Length = 0; // Equivalent of sb.Clear() that works on .Net 3.5 +#if DEBUG && !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS + MSBuildEventSource.Log.ReusableStringBuilderFactoryStart(hash: sb.GetHashCode(), newCapacity: capacity, oldCapacity: sb.Capacity, type: "sbc-hit"); +#endif + return sb; + } + } + } + + StringBuilder stringBuilder = new StringBuilder(capacity); +#if DEBUG && !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS + MSBuildEventSource.Log.ReusableStringBuilderFactoryStart(hash: stringBuilder.GetHashCode(), newCapacity: capacity, oldCapacity: stringBuilder.Capacity, type: "sbc-miss"); +#endif + return stringBuilder; + } + + /// + /// Place the specified builder in the cache if it is not too big. Unbalanced Releases are acceptable. + /// The StringBuilder should not be used after it has + /// been released. + /// Unbalanced Releases are perfectly acceptable.It + /// will merely cause the runtime to create a new + /// StringBuilder next time Acquire is called. + /// + /// The to cache. Likely returned from . + /// + /// The StringBuilder should not be used after it has been released. + /// + /// + /// Unbalanced Releases are perfectly acceptable.It + /// will merely cause the runtime to create a new + /// StringBuilder next time Acquire is called. + /// + /// + public static void Release(StringBuilder sb) + { + if (sb.Capacity <= MAX_BUILDER_SIZE) + { + // Assert we are not replacing another string builder. That could happen when Acquire is reentered. + // User of StringBuilderCache has to make sure that calling method call stacks do not also use StringBuilderCache. + Debug.Assert(StringBuilderCache.t_cachedInstance == null, "Unexpected replacing of other StringBuilder."); + StringBuilderCache.t_cachedInstance = sb; + } +#if DEBUG && !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS + MSBuildEventSource.Log.ReusableStringBuilderFactoryStop(hash: sb.GetHashCode(), returningCapacity: sb.Capacity, returningLength: sb.Length, type: sb.Capacity <= MAX_BUILDER_SIZE ? "sbc-return" : "sbc-discard"); +#endif + } + + /// + /// Get a string and return its builder to the cache. + /// + /// Builder to cache (if it's not too big). + /// The equivalent to 's contents. + /// + /// Convenience method equivalent to calling followed by . + /// + public static string GetStringAndRelease(StringBuilder sb) + { + string result = sb.ToString(); + Release(sb); + return result; + } + } +} diff --git a/src/MSBuildTaskHost/Framework/SupportedOSPlatform.cs b/src/MSBuildTaskHost/Framework/SupportedOSPlatform.cs new file mode 100644 index 00000000000..f532d4569e6 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/SupportedOSPlatform.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if !NET6_0_OR_GREATER +namespace System.Runtime.Versioning +{ + /// + /// SupportedOSPlatform is a net5.0+ Attribute. + /// Create the same type only in full-framework and netstandard2.0 builds + /// to prevent many #if RUNTIME_TYPE_NETCORE checks. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] + internal class SupportedOSPlatformGuard : Attribute + { + internal SupportedOSPlatformGuard(string platformName) + { + } + } + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Class)] + internal class SupportedOSPlatform : Attribute + { + internal SupportedOSPlatform(string platformName) + { + } + } +} +#endif diff --git a/src/MSBuildTaskHost/Framework/TaskHostParameters.cs b/src/MSBuildTaskHost/Framework/TaskHostParameters.cs new file mode 100644 index 00000000000..97ee9223a96 --- /dev/null +++ b/src/MSBuildTaskHost/Framework/TaskHostParameters.cs @@ -0,0 +1,168 @@ +// 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; + +namespace Microsoft.Build.Framework +{ + /// + /// A readonly struct that represents task host parameters used to determine which host process to launch. + /// + public readonly struct TaskHostParameters : IEquatable + { + /// + /// A static empty instance to avoid allocations when default parameters are needed. + /// + public static readonly TaskHostParameters Empty = new(); + + private readonly string? _runtime; + private readonly string? _architecture; + private readonly string? _dotnetHostPath; + private readonly string? _msBuildAssemblyPath; + private readonly bool? _taskHostFactoryExplicitlyRequested; + + /// + /// Initializes a new instance of the TaskHostParameters struct with the specified parameters. + /// + /// The target runtime identifier (e.g., "net8.0", "net472"). + /// The target architecture (e.g., "x64", "x86", "arm64"). + /// The path to the dotnet host executable. + /// The path to the MSBuild assembly. + /// Defines if Task Host Factory was explicitly requested. + internal TaskHostParameters( + string? runtime = null, + string? architecture = null, + string? dotnetHostPath = null, + string? msBuildAssemblyPath = null, + bool? taskHostFactoryExplicitlyRequested = null) + { + _runtime = runtime; + _architecture = architecture; + _dotnetHostPath = dotnetHostPath; + _msBuildAssemblyPath = msBuildAssemblyPath; + _taskHostFactoryExplicitlyRequested = taskHostFactoryExplicitlyRequested; + } + + /// + /// Gets the target runtime identifier (e.g., "net8.0", "net472"). + /// + /// The runtime identifier, or null if not specified. + public string? Runtime => _runtime; + + /// + /// Gets the target architecture (e.g., "x64", "x86", "arm64"). + /// + /// The architecture identifier, or an empty string if not specified. + public string? Architecture => _architecture; + + /// + /// Gets the path to the dotnet host executable. + /// + /// The dotnet host path, or null if not specified. + public string? DotnetHostPath => _dotnetHostPath; + + /// + /// Gets the path to the MSBuild assembly. + /// + /// The MSBuild assembly path, or null if not specified. + public string? MSBuildAssemblyPath => _msBuildAssemblyPath; + + /// + /// Gets if Task Host Factory was requested explicitly (by specifying TaskHost="TaskHostFactory" in UsingTask element). + /// + public bool? TaskHostFactoryExplicitlyRequested => _taskHostFactoryExplicitlyRequested; + + public override bool Equals(object? obj) => obj is TaskHostParameters other && Equals(other); + + public bool Equals(TaskHostParameters other) => + StringComparer.OrdinalIgnoreCase.Equals(Runtime ?? string.Empty, other.Runtime ?? string.Empty) + && StringComparer.OrdinalIgnoreCase.Equals(Architecture ?? string.Empty, other.Architecture ?? string.Empty) + && TaskHostFactoryExplicitlyRequested == other.TaskHostFactoryExplicitlyRequested; + + public override int GetHashCode() + { + // Manual hash code implementation for compatibility with .NET Framework 4.7.2 + var comparer = StringComparer.OrdinalIgnoreCase; + + unchecked + { + int hash = 17; + hash = hash * 31 + comparer.GetHashCode(Runtime ?? string.Empty); + hash = hash * 31 + comparer.GetHashCode(Architecture ?? string.Empty); + hash = hash * 31 + (TaskHostFactoryExplicitlyRequested?.GetHashCode() ?? 0); + + return hash; + } + } + + /// + /// Gets a value indicating whether returns true if parameters were unset. + /// + internal bool IsEmpty => Equals(Empty); + + /// + /// Merges two TaskHostParameters instances, with the second parameter values taking precedence when both are specified. + /// + /// The base parameters. + /// The override parameters that take precedence. + /// A new TaskHostParameters with merged values. + internal static TaskHostParameters MergeTaskHostParameters(TaskHostParameters baseParameters, TaskHostParameters overrideParameters) + { + // If both are empty, return empty + if (baseParameters.IsEmpty && overrideParameters.IsEmpty) + { + return Empty; + } + + // If override is empty, return base + if (overrideParameters.IsEmpty) + { + return baseParameters; + } + + // If base is empty, return override + if (baseParameters.IsEmpty) + { + return overrideParameters; + } + + // Merge: override values take precedence, fall back to base values + return new TaskHostParameters( + runtime: overrideParameters.Runtime ?? baseParameters.Runtime, + architecture: overrideParameters.Architecture ?? baseParameters.Architecture, + dotnetHostPath: overrideParameters.DotnetHostPath ?? baseParameters.DotnetHostPath, + msBuildAssemblyPath: overrideParameters.MSBuildAssemblyPath ?? baseParameters.MSBuildAssemblyPath, + taskHostFactoryExplicitlyRequested: overrideParameters.TaskHostFactoryExplicitlyRequested ?? baseParameters.TaskHostFactoryExplicitlyRequested); + } + + /// + /// Creates a new instance of with the specified value for the + /// property. + /// + internal TaskHostParameters WithTaskHostFactoryExplicitlyRequested(bool taskHostFactoryExplicitlyRequested) + { + if (_taskHostFactoryExplicitlyRequested == taskHostFactoryExplicitlyRequested) + { + return this; + } + + return new TaskHostParameters( + runtime: _runtime, + architecture: _architecture, + dotnetHostPath: _dotnetHostPath, + msBuildAssemblyPath: _msBuildAssemblyPath, + taskHostFactoryExplicitlyRequested: taskHostFactoryExplicitlyRequested); + } + + /// + /// The method was added to sustain compatibility with ITaskFactory2 factoryIdentityParameters parameters dictionary. + /// + internal Dictionary ToDictionary() => new(3, StringComparer.OrdinalIgnoreCase) + { + { nameof(Runtime), Runtime ?? string.Empty }, + { nameof(Architecture), Architecture ?? string.Empty }, + { nameof(TaskHostFactoryExplicitlyRequested), TaskHostFactoryExplicitlyRequested?.ToString() ?? string.Empty }, + }; + } +} diff --git a/src/MSBuildTaskHost/Framework/Traits.cs b/src/MSBuildTaskHost/Framework/Traits.cs new file mode 100644 index 00000000000..f14208e145a --- /dev/null +++ b/src/MSBuildTaskHost/Framework/Traits.cs @@ -0,0 +1,632 @@ +// 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.Globalization; + +namespace Microsoft.Build.Framework +{ + /// + /// Represents toggleable features of the MSBuild engine. + /// + internal class Traits + { + private static Traits _instance = new Traits(); + + public static Traits Instance + { + get + { + if (BuildEnvironmentState.s_runningTests) + { + return new Traits(); + } + return _instance; + } + } + + public Traits() + { + EscapeHatches = new EscapeHatches(); + + DebugScheduler = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDDEBUGSCHEDULER")); + DebugNodeCommunication = DebugEngine || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDDEBUGCOMM")); + } + + public EscapeHatches EscapeHatches { get; } + + internal readonly string? MSBuildDisableFeaturesFromVersion = Environment.GetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION"); + + // This will affect all tasks except for MSBuild and CallTarget. Those two have to run in-proc, as they depend on IBuildEngine callbacks. + public readonly bool ForceAllTasksOutOfProcToTaskHost = Environment.GetEnvironmentVariable("MSBUILDFORCEALLTASKSOUTOFPROC") == "1"; + + /// + /// Do not expand wildcards that match a certain pattern + /// + public readonly bool UseLazyWildCardEvaluation = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildSkipEagerWildCardEvaluationRegexes")); + public readonly bool LogExpandedWildcards = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDLOGEXPANDEDWILDCARDS")); + public readonly bool ThrowOnDriveEnumeratingWildcard = Environment.GetEnvironmentVariable("MSBUILDFAILONDRIVEENUMERATINGWILDCARD") == "1"; + + /// + /// Cache file existence for the entire process + /// + public readonly bool CacheFileExistence = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildCacheFileExistence")); + + public readonly bool UseSimpleProjectRootElementCacheConcurrency = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildUseSimpleProjectRootElementCacheConcurrency")); + + /// + /// Cache wildcard expansions for the entire process + /// + public readonly bool MSBuildCacheFileEnumerations = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildCacheFileEnumerations")); + + public readonly bool EnableAllPropertyFunctions = Environment.GetEnvironmentVariable("MSBUILDENABLEALLPROPERTYFUNCTIONS") == "1"; + + /// + /// Enable restore first functionality in MSBuild.exe + /// + public readonly bool EnableRestoreFirst = Environment.GetEnvironmentVariable("MSBUILDENABLERESTOREFIRST") == "1"; + + /// + /// Allow the user to specify that two processes should not be communicating via an environment variable. + /// + public static readonly string? MSBuildNodeHandshakeSalt = Environment.GetEnvironmentVariable("MSBUILDNODEHANDSHAKESALT"); + + /// + /// Override property "MSBuildRuntimeType" to "Full", ignoring the actual runtime type of MSBuild. + /// + public readonly bool ForceEvaluateAsFullFramework = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildForceEvaluateAsFullFramework")); + + /// + /// Setting the associated environment variable to 1 restores the pre-15.8 single + /// threaded (slower) copy behavior. Zero implies Int32.MaxValue, less than zero + /// (default) uses the empirical default in Copy.cs, greater than zero can allow + /// perf tuning beyond the defaults chosen. + /// + public readonly int CopyTaskParallelism = ParseIntFromEnvironmentVariableOrDefault("MSBUILDCOPYTASKPARALLELISM", -1); + + /// + /// Instruct MSBuild to write out the generated "metaproj" file to disk when building a solution file. + /// + public readonly bool EmitSolutionMetaproj = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildEmitSolution")); + + /// + /// Modifies Solution Generator to generate a metaproj that batches multiple Targets into one MSBuild task invoke. + /// + /// + /// For example, a run of Clean;Build target will first run Clean on all projects, + /// then run Build on all projects. When enabled, it will run Clean;Build on all + /// Projects at the back to back. Allowing the second target to start sooner than before. + /// + public readonly bool SolutionBatchTargets = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildSolutionBatchTargets")); + + /// + /// Log statistics about property functions which require reflection + /// + public readonly bool LogPropertyFunctionsRequiringReflection = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildLogPropertyFunctionsRequiringReflection")); + + /// + /// Log all assembly loads including those that come from known MSBuild and .NET SDK sources in the binary log. + /// + public readonly bool LogAllAssemblyLoads = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDLOGALLASSEMBLYLOADS")); + + /// + /// Log all environment variables whether or not they are used in a build in the binary log. + /// + public static bool LogAllEnvironmentVariables = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDLOGALLENVIRONMENTVARIABLES")); + + /// + /// Log property tracking information. + /// + public readonly int LogPropertyTracking = ParseIntFromEnvironmentVariableOrDefault("MsBuildLogPropertyTracking", 0); // Default to logging nothing via the property tracker. + + /// + /// When evaluating items, this is the minimum number of items on the running list to use a dictionary-based remove optimization. + /// + public readonly int DictionaryBasedItemRemoveThreshold = ParseIntFromEnvironmentVariableOrDefault("MSBUILDDICTIONARYBASEDITEMREMOVETHRESHOLD", 100); + + /// + /// Launches a persistent RAR process. + /// + /// TODO: Replace with command line flag when feature is completed. The environment variable is intented to avoid exposing the flag early. + public readonly bool EnableRarNode = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildRarNode")); + + /// + /// Name of environment variables used to enable MSBuild server. + /// + public const string UseMSBuildServerEnvVarName = "MSBUILDUSESERVER"; + + /// + /// Name of environment variable for logging arguments (e.g., -bl, -check). + /// + public const string MSBuildLoggingArgsEnvVarName = "MSBUILD_LOGGING_ARGS"; + + /// + /// Name of environment variable that controls the logging level for diagnostic messages + /// emitted when processing the MSBUILD_LOGGING_ARGS environment variable. + /// Set to "message" to emit as low-importance build messages instead of console warnings. + /// + public const string MSBuildLoggingArgsLevelEnvVarName = "MSBUILD_LOGGING_ARGS_LEVEL"; + + /// + /// Value of the MSBUILD_LOGGING_ARGS environment variable. + /// + public static string? MSBuildLoggingArgs => Environment.GetEnvironmentVariable(MSBuildLoggingArgsEnvVarName); + + /// + /// Gets if the logging level for MSBUILD_LOGGING_ARGS diagnostic is message. + /// + public readonly bool EmitLogsAsMessage = string.Equals(Environment.GetEnvironmentVariable(MSBuildLoggingArgsLevelEnvVarName), "message", StringComparison.OrdinalIgnoreCase); + + public readonly bool DebugEngine = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildDebugEngine")); + public readonly bool DebugScheduler; + public readonly bool DebugNodeCommunication; + public readonly bool DebugUnitTests = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildDebugUnitTests")); + + public readonly bool InProcNodeDisabled = Environment.GetEnvironmentVariable("MSBUILDNOINPROCNODE") == "1"; + + /// + /// Forces execution of tasks coming from a different TaskFactory than AssemblyTaskFactory out of proc. + /// + public readonly bool ForceTaskFactoryOutOfProc = Environment.GetEnvironmentVariable("MSBUILDFORCEINLINETASKFACTORIESOUTOFPROC") == "1"; + + /// + /// Make Console use default encoding in the system. It opts out automatic console encoding UTF-8. + /// + public readonly bool ConsoleUseDefaultEncoding = Environment.GetEnvironmentVariable("MSBUILD_CONSOLE_USE_DEFAULT_ENCODING") == "1" || Environment.GetEnvironmentVariable("DOTNET_CLI_CONSOLE_USE_DEFAULT_ENCODING") == "1"; + + /// + /// Variables controlling opt out at the level of not initializing telemetry infrastructure. Set to "1" or "true" to opt out. + /// mirroring + /// https://learn.microsoft.com/en-us/dotnet/core/tools/telemetry + /// + public bool SdkTelemetryOptOut = IsEnvVarOneOrTrue("DOTNET_CLI_TELEMETRY_OPTOUT"); + public bool FrameworkTelemetryOptOut = IsEnvVarOneOrTrue("MSBUILD_TELEMETRY_OPTOUT"); + public bool ExcludeTasksDetailsFromTelemetry = IsEnvVarOneOrTrue("MSBUILDTELEMETRYEXCLUDETASKSDETAILS"); + public bool FlushNodesTelemetryIntoConsole = IsEnvVarOneOrTrue("MSBUILDFLUSHNODESTELEMETRYINTOCONSOLE"); + + public bool EnableTargetOutputLogging = IsEnvVarOneOrTrue("MSBUILDTARGETOUTPUTLOGGING"); + + // for VS17.14 + public readonly bool SlnParsingWithSolutionPersistenceOptIn = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILD_PARSE_SLN_WITH_SOLUTIONPERSISTENCE")); + + public static void UpdateFromEnvironment() + { + // Re-create Traits instance to update values in Traits according to current environment. + if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_10)) + { + _instance = new Traits(); + } + } + + private static int ParseIntFromEnvironmentVariableOrDefault(string environmentVariable, int defaultValue) + { + return int.TryParse(Environment.GetEnvironmentVariable(environmentVariable), out int result) + ? result + : defaultValue; + } + + internal static bool IsEnvVarOneOrTrue(string name) + { + string? value = Environment.GetEnvironmentVariable(name); + return value != null && + (value.Equals("1", StringComparison.OrdinalIgnoreCase) || + value.Equals("true", StringComparison.OrdinalIgnoreCase)); + } + } + + internal class EscapeHatches + { + /// + /// Do not log command line information to build loggers. Useful to unbreak people who parse the msbuild log and who are unwilling to change their code. + /// + public readonly bool DoNotSendDeferredMessagesToBuildManager = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildDoNotSendDeferredMessagesToBuildManager")); + + /// + /// https://github.com/dotnet/msbuild/pull/4975 started expanding qualified metadata in Update operations. Before they'd expand to empty strings. + /// This escape hatch turns back the old empty string behavior. + /// + public readonly bool DoNotExpandQualifiedMetadataInUpdateOperation = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildDoNotExpandQualifiedMetadataInUpdateOperation")); + + /// + /// Force whether Project based evaluations should evaluate elements with false conditions. + /// + public readonly bool? EvaluateElementsWithFalseConditionInProjectEvaluation = ParseNullableBoolFromEnvironmentVariable("MSBUILDEVALUATEELEMENTSWITHFALSECONDITIONINPROJECTEVALUATION"); + + /// + /// Always use the accurate-but-slow CreateFile approach to timestamp extraction. + /// + public readonly bool AlwaysUseContentTimestamp = Environment.GetEnvironmentVariable("MSBUILDALWAYSCHECKCONTENTTIMESTAMP") == "1"; + + /// + /// Truncate task inputs when logging them. This can reduce memory pressure + /// at the expense of log usefulness. + /// + public readonly bool TruncateTaskInputs = Environment.GetEnvironmentVariable("MSBUILDTRUNCATETASKINPUTS") == "1"; + + /// + /// Disables truncation of Condition messages in Tasks/Targets via ExpanderOptions.Truncate. + /// + public readonly bool DoNotTruncateConditions = Environment.GetEnvironmentVariable("MSBuildDoNotTruncateConditions") == "1"; + + /// + /// Disables skipping full drive/filesystem globs that are behind a false condition. + /// + public readonly bool AlwaysEvaluateDangerousGlobs = Environment.GetEnvironmentVariable("MSBuildAlwaysEvaluateDangerousGlobs") == "1"; + + /// + /// Disables skipping full up to date check for immutable files. See FileClassifier class. + /// + public readonly bool AlwaysDoImmutableFilesUpToDateCheck = Environment.GetEnvironmentVariable("MSBUILDDONOTCACHEMODIFICATIONTIME") == "1"; + + /// + /// When copying over an existing file, copy directly into the existing file rather than deleting and recreating. + /// + public readonly bool CopyWithoutDelete = Environment.GetEnvironmentVariable("MSBUILDCOPYWITHOUTDELETE") == "1"; + + /// + /// Emit events for project imports. + /// + private bool? _logProjectImports; + + /// + /// Emit events for project imports. + /// + public bool LogProjectImports + { + get + { + // Cache the first time + if (_logProjectImports == null) + { + _logProjectImports = !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDLOGIMPORTS")); + } + return _logProjectImports.Value; + } + set + { + _logProjectImports = value; + } + } + + private bool? _logTaskInputs; + public bool LogTaskInputs + { + get + { + if (_logTaskInputs == null) + { + _logTaskInputs = Environment.GetEnvironmentVariable("MSBUILDLOGTASKINPUTS") == "1"; + } + return _logTaskInputs.Value; + } + set + { + _logTaskInputs = value; + } + } + + private bool? _logPropertiesAndItemsAfterEvaluation; + private bool _logPropertiesAndItemsAfterEvaluationInitialized = false; + public bool? LogPropertiesAndItemsAfterEvaluation + { + get + { + if (!_logPropertiesAndItemsAfterEvaluationInitialized) + { + _logPropertiesAndItemsAfterEvaluationInitialized = true; + var variable = Environment.GetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION"); + if (!string.IsNullOrEmpty(variable)) + { + _logPropertiesAndItemsAfterEvaluation = variable == "1" || string.Equals(variable, "true", StringComparison.OrdinalIgnoreCase); + } + } + + return _logPropertiesAndItemsAfterEvaluation; + } + + set + { + _logPropertiesAndItemsAfterEvaluationInitialized = true; + _logPropertiesAndItemsAfterEvaluation = value; + } + } + + /// + /// Read information only once per file per ResolveAssemblyReference invocation. + /// + public readonly bool CacheAssemblyInformation = Environment.GetEnvironmentVariable("MSBUILDDONOTCACHERARASSEMBLYINFORMATION") != "1"; + + public readonly ProjectInstanceTranslationMode? ProjectInstanceTranslation = ComputeProjectInstanceTranslation(); + + /// + /// Never use the slow (but more accurate) CreateFile approach to timestamp extraction. + /// + public readonly bool UseSymlinkTimeInsteadOfTargetTime = Environment.GetEnvironmentVariable("MSBUILDUSESYMLINKTIMESTAMP") == "1"; + + /// + /// Allow node reuse of TaskHost nodes. This results in task assemblies locked past the build lifetime, preventing them from being rebuilt if custom tasks change, but may improve performance. + /// + public readonly bool ReuseTaskHostNodes = Environment.GetEnvironmentVariable("MSBUILDREUSETASKHOSTNODES") == "1"; + + /// + /// Whether or not to ignore imports that are considered empty. See ProjectRootElement.IsEmptyXmlFile() for more info. + /// + public readonly bool IgnoreEmptyImports = Environment.GetEnvironmentVariable("MSBUILDIGNOREEMPTYIMPORTS") == "1"; + + /// + /// Whether to respect the TreatAsLocalProperty parameter on the Project tag. + /// + public readonly bool IgnoreTreatAsLocalProperty = Environment.GetEnvironmentVariable("MSBUILDIGNORETREATASLOCALPROPERTY") != null; + + /// + /// Whether to write information about why we evaluate to debug output. + /// + public readonly bool DebugEvaluation = Environment.GetEnvironmentVariable("MSBUILDDEBUGEVALUATION") != null; + + /// + /// Whether to warn when we set a property for the first time, after it was previously used. + /// + public readonly bool WarnOnUninitializedProperty = !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDWARNONUNINITIALIZEDPROPERTY")); + + /// + /// MSBUILDUSECASESENSITIVEITEMNAMES is an escape hatch for the fix + /// for https://github.com/dotnet/msbuild/issues/1751. It should + /// be removed (permanently set to false) after establishing that + /// it's unneeded (at least by the 16.0 timeframe). + /// + public readonly bool UseCaseSensitiveItemNames = Environment.GetEnvironmentVariable("MSBUILDUSECASESENSITIVEITEMNAMES") == "1"; + + /// + /// Disable the use of paths longer than Windows MAX_PATH limits (260 characters) when running on a long path enabled OS. + /// + public readonly bool DisableLongPaths = Environment.GetEnvironmentVariable("MSBUILDDISABLELONGPATHS") == "1"; + + /// + /// Disable the use of any caching when resolving SDKs. + /// + public readonly bool DisableSdkResolutionCache = Environment.GetEnvironmentVariable("MSBUILDDISABLESDKCACHE") == "1"; + + /// + /// Don't delete TargetPath metadata from associated files found by RAR. + /// + public readonly bool TargetPathForRelatedFiles = Environment.GetEnvironmentVariable("MSBUILDTARGETPATHFORRELATEDFILES") == "1"; + + /// + /// Disable AssemblyLoadContext isolation for plugins. + /// + public readonly bool UseSingleLoadContext = Environment.GetEnvironmentVariable("MSBUILDSINGLELOADCONTEXT") == "1"; + + /// + /// Enables the user of autorun functionality in CMD.exe on Windows which is disabled by default in MSBuild. + /// + public readonly bool UseAutoRunWhenLaunchingProcessUnderCmd = Environment.GetEnvironmentVariable("MSBUILDUSERAUTORUNINCMD") == "1"; + + /// + /// Disables switching codepage to UTF-8 after detection of characters that can't be represented in the current codepage. + /// + public readonly bool AvoidUnicodeWhenWritingToolTaskBatch = Environment.GetEnvironmentVariable("MSBUILDAVOIDUNICODE") == "1"; + + /// + /// Workaround for https://github.com/Microsoft/vstest/issues/1503. + /// + public readonly bool EnsureStdOutForChildNodesIsPrimaryStdout = Environment.GetEnvironmentVariable("MSBUILDENSURESTDOUTFORTASKPROCESSES") == "1"; + + /// + /// Use the original, string-only resx parsing in .NET Core scenarios. + /// + /// + /// Escape hatch for problems arising from https://github.com/dotnet/msbuild/pull/4420. + /// + public readonly bool UseMinimalResxParsingInCoreScenarios = Environment.GetEnvironmentVariable("MSBUILDUSEMINIMALRESX") == "1"; + + /// + /// Escape hatch to ensure msbuild produces the compatible build results cache without versioning. + /// + /// + /// Escape hatch for problems arising from https://github.com/dotnet/msbuild/issues/10208. + /// + public readonly bool DoNotVersionBuildResult = Environment.GetEnvironmentVariable("MSBUILDDONOTVERSIONBUILDRESULT") == "1"; + + /// + /// Escape hatch to ensure build check does not limit amount of results. + /// + public readonly bool DoNotLimitBuildCheckResultsNumber = Environment.GetEnvironmentVariable("MSBUILDDONOTLIMITBUILDCHECKRESULTSNUMBER") == "1"; + + private bool _sdkReferencePropertyExpansionInitialized; + private SdkReferencePropertyExpansionMode? _sdkReferencePropertyExpansionValue; + + /// + /// Overrides the default behavior of property expansion on evaluation of a . + /// + /// + /// Escape hatch for problems arising from https://github.com/dotnet/msbuild/pull/5552. + /// + public SdkReferencePropertyExpansionMode? SdkReferencePropertyExpansion + { + get + { + if (!_sdkReferencePropertyExpansionInitialized) + { + _sdkReferencePropertyExpansionValue = ComputeSdkReferencePropertyExpansion(); + _sdkReferencePropertyExpansionInitialized = true; + } + + return _sdkReferencePropertyExpansionValue; + } + } + + public bool UnquoteTargetSwitchParameters + { + get + { + return ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_10); + } + } + + private static bool? ParseNullableBoolFromEnvironmentVariable(string environmentVariable) + { + var value = Environment.GetEnvironmentVariable(environmentVariable); + + if (string.IsNullOrEmpty(value)) + { + return null; + } + + if (bool.TryParse(value, out bool result)) + { + return result; + } + + ThrowInternalError($"Environment variable \"{environmentVariable}\" should have values \"true\", \"false\" or undefined"); + + return null; + } + + private static ProjectInstanceTranslationMode? ComputeProjectInstanceTranslation() + { + var mode = Environment.GetEnvironmentVariable("MSBUILD_PROJECTINSTANCE_TRANSLATION_MODE"); + + if (mode == null) + { + return null; + } + + if (mode.Equals("full", StringComparison.OrdinalIgnoreCase)) + { + return ProjectInstanceTranslationMode.Full; + } + + if (mode.Equals("partial", StringComparison.OrdinalIgnoreCase)) + { + return ProjectInstanceTranslationMode.Partial; + } + + ThrowInternalError($"Invalid escape hatch for project instance translation: {mode}"); + + return null; + } + + private static SdkReferencePropertyExpansionMode? ComputeSdkReferencePropertyExpansion() + { + var mode = Environment.GetEnvironmentVariable("MSBUILD_SDKREFERENCE_PROPERTY_EXPANSION_MODE"); + + if (mode == null) + { + return null; + } + + // The following uses StartsWith instead of Equals to enable possible tricks like + // the dpiAware "True/PM" trick (see https://devblogs.microsoft.com/oldnewthing/20160617-00/?p=93695) + // in the future. + + const StringComparison comparison = StringComparison.OrdinalIgnoreCase; + + if (mode.StartsWith("no", comparison)) + { + return SdkReferencePropertyExpansionMode.NoExpansion; + } + + if (mode.StartsWith("default", comparison)) + { + return SdkReferencePropertyExpansionMode.DefaultExpand; + } + + if (mode.StartsWith(nameof(SdkReferencePropertyExpansionMode.ExpandUnescape), comparison)) + { + return SdkReferencePropertyExpansionMode.ExpandUnescape; + } + + if (mode.StartsWith(nameof(SdkReferencePropertyExpansionMode.ExpandLeaveEscaped), comparison)) + { + return SdkReferencePropertyExpansionMode.ExpandLeaveEscaped; + } + + ThrowInternalError($"Invalid escape hatch for SdkReference property expansion: {mode}"); + + return null; + } + + public enum ProjectInstanceTranslationMode + { + Full, + Partial + } + + public enum SdkReferencePropertyExpansionMode + { + NoExpansion, + DefaultExpand, + ExpandUnescape, + ExpandLeaveEscaped + } + + /// + /// Throws InternalErrorException. + /// + /// + /// Clone of ErrorUtilities.ThrowInternalError which isn't available in Framework. + /// + internal static void ThrowInternalError(string message) + { + throw new InternalErrorException(message); + } + + /// + /// Throws InternalErrorException. + /// This is only for situations that would mean that there is a bug in MSBuild itself. + /// + /// + /// Clone from ErrorUtilities which isn't available in Framework. + /// + internal static void ThrowInternalError(string message, params object?[] args) + { + throw new InternalErrorException(FormatString(message, args)); + } + + /// + /// Formats the given string using the variable arguments passed in. + /// + /// PERF WARNING: calling a method that takes a variable number of arguments is expensive, because memory is allocated for + /// the array of arguments -- do not call this method repeatedly in performance-critical scenarios + /// + /// Thread safe. + /// + /// The string to format. + /// Optional arguments for formatting the given string. + /// The formatted string. + /// + /// Clone from ResourceUtilities which isn't available in Framework. + /// + internal static string FormatString(string unformatted, params object?[] args) + { + string formatted = unformatted; + + // NOTE: String.Format() does not allow a null arguments array + if ((args?.Length > 0)) + { +#if DEBUG + // If you accidentally pass some random type in that can't be converted to a string, + // FormatResourceString calls ToString() which returns the full name of the type! + foreach (object? param in args) + { + // Check it has a real implementation of ToString() and the type is not actually System.String + if (param != null) + { + if (string.Equals(param.GetType().ToString(), param.ToString(), StringComparison.Ordinal) && + param.GetType() != typeof(string)) + { + ThrowInternalError("Invalid resource parameter type, was {0}", + param.GetType().FullName); + } + } + } +#endif + // Format the string, using the variable arguments passed in. + // NOTE: all String methods are thread-safe + formatted = String.Format(CultureInfo.CurrentCulture, unformatted, args); + } + + return formatted; + } + } +} diff --git a/src/MSBuildTaskHost/Framework/VisualStudioLocationHelper.cs b/src/MSBuildTaskHost/Framework/VisualStudioLocationHelper.cs new file mode 100644 index 00000000000..a747ea8bd1a --- /dev/null +++ b/src/MSBuildTaskHost/Framework/VisualStudioLocationHelper.cs @@ -0,0 +1,143 @@ +// 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 FEATURE_VISUALSTUDIOSETUP +using System.Runtime.InteropServices; +using Microsoft.VisualStudio.Setup.Configuration; +#endif + +#nullable disable + +namespace Microsoft.Build.Shared +{ + /// + /// Helper class to wrap the Microsoft.VisualStudio.Setup.Configuration.Interop API to query + /// Visual Studio setup for instances installed on the machine. + /// Code derived from sample: https://code.msdn.microsoft.com/Visual-Studio-Setup-0cedd331 + /// + internal class VisualStudioLocationHelper + { +#if FEATURE_VISUALSTUDIOSETUP + private const int REGDB_E_CLASSNOTREG = unchecked((int)0x80040154); +#endif // FEATURE_VISUALSTUDIOSETUP + + /// + /// Query the Visual Studio setup API to get instances of Visual Studio installed + /// on the machine. Will not include anything before Visual Studio "15". + /// + /// Enumerable list of Visual Studio instances + internal static IList GetInstances() + { + var validInstances = new List(); + +#if FEATURE_VISUALSTUDIOSETUP + try + { + // This code is not obvious. See the sample (link above) for reference. + var query = (ISetupConfiguration2)GetQuery(); + var e = query.EnumAllInstances(); + + int fetched; + var instances = new ISetupInstance[1]; + do + { + // Call e.Next to query for the next instance (single item or nothing returned). + e.Next(1, instances, out fetched); + if (fetched <= 0) + { + continue; + } + + var instance = instances[0]; + var state = ((ISetupInstance2)instance).GetState(); + Version version; + + try + { + version = new Version(instance.GetInstallationVersion()); + } + catch (FormatException) + { + continue; + } + + // If the install was complete and a valid version, consider it. + if (state == InstanceState.Complete) + { + validInstances.Add(new VisualStudioInstance( + instance.GetDisplayName(), + instance.GetInstallationPath(), + version)); + } + } while (fetched > 0); + } + catch (COMException) + { } + catch (DllNotFoundException) + { + // This is OK, VS "15" or greater likely not installed. + } +#endif + return validInstances; + } + +#if FEATURE_VISUALSTUDIOSETUP + private static ISetupConfiguration GetQuery() + { + try + { + // Try to CoCreate the class object. + return new SetupConfiguration(); + } + catch (COMException ex) when (ex.ErrorCode == REGDB_E_CLASSNOTREG) + { + // Try to get the class object using app-local call. + ISetupConfiguration query; + var result = GetSetupConfiguration(out query, IntPtr.Zero); + + if (result < 0) + { + throw new COMException("Failed to get query", result); + } + + return query; + } + } + + [DllImport("Microsoft.VisualStudio.Setup.Configuration.Native.dll", ExactSpelling = true, PreserveSig = true)] + private static extern int GetSetupConfiguration( + [MarshalAs(UnmanagedType.Interface), Out] out ISetupConfiguration configuration, + IntPtr reserved); +#endif + } + + /// + /// Wrapper class to represent an installed instance of Visual Studio. + /// + internal class VisualStudioInstance + { + /// + /// Version of the Visual Studio Instance + /// + internal Version Version { get; } + + /// + /// Path to the Visual Studio installation + /// + internal string Path { get; } + + /// + /// Full name of the Visual Studio instance with SKU name + /// + internal string Name { get; } + + internal VisualStudioInstance(string name, string path, Version version) + { + Name = name; + Path = path; + Version = version; + } + } +} diff --git a/src/MSBuildTaskHost/GlobalUsings.cs b/src/MSBuildTaskHost/GlobalUsings.cs new file mode 100644 index 00000000000..a252c3a096b --- /dev/null +++ b/src/MSBuildTaskHost/GlobalUsings.cs @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if NET +global using LockType = System.Threading.Lock; +#else +global using LockType = System.Object; +#endif \ No newline at end of file diff --git a/src/MSBuildTaskHost/INodeEndpoint.cs b/src/MSBuildTaskHost/INodeEndpoint.cs new file mode 100644 index 00000000000..373250acce2 --- /dev/null +++ b/src/MSBuildTaskHost/INodeEndpoint.cs @@ -0,0 +1,113 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + #region Delegates + /// + /// Used to receive link status updates from an endpoint. + /// + /// The endpoint invoking the delegate. + /// The current status of the link. + internal delegate void LinkStatusChangedDelegate(INodeEndpoint endpoint, LinkStatus status); + + /// + /// Used to receive data from a node + /// + /// The endpoint invoking the delegate. + /// The packet received. + internal delegate void DataReceivedDelegate(INodeEndpoint endpoint, INodePacket packet); + #endregion + + #region Enums + /// + /// The connection status of a link between the NodeEndpoint on the host and the NodeEndpoint + /// on the peer. + /// + internal enum LinkStatus + { + /// + /// The connection has never been started. + /// + Inactive, + + /// + /// The connection is active, the most recent data has been successfully sent, and the + /// node is responding to pings. + /// + Active, + + /// + /// The connection has failed and been terminated. + /// + Failed, + + /// + /// The connection could not be made/timed out. + /// + ConnectionFailed, + } + + #endregion + + /// + /// This interface represents one end of a connection between the INodeProvider and a Node. + /// Implementations of this interface define the actual mechanism by which data is communicated. + /// + internal interface INodeEndpoint + { + #region Events + + /// + /// Raised when the status of the node's link has changed. + /// + event LinkStatusChangedDelegate OnLinkStatusChanged; + + #endregion + + #region Properties + + /// + /// The current link status for this endpoint. + /// + LinkStatus LinkStatus + { + get; + } + #endregion + + #region Methods + /// + /// Waits for the remote node to establish a connection. + /// + /// The factory used to deserialize packets. + /// Only one of Listen() or Connect() may be called on an endpoint. + void Listen(INodePacketFactory factory); + + /// + /// Instructs the node to connect to its peer endpoint. + /// + /// The factory used to deserialize packets. + void Connect(INodePacketFactory factory); + + /// + /// Instructs the node to disconnect from its peer endpoint. + /// + void Disconnect(); + + /// + /// Sends a data packet to the node. + /// + /// The packet to be sent. + void SendData(INodePacket packet); + #endregion + + /// + /// Called when we are about to send last packet to finalize graceful disconnection with client. + /// This is needed to handle race condition when both client and server is gracefully about to close connection. + /// + void ClientWillDisconnect(); + } +} diff --git a/src/MSBuildTaskHost/INodePacket.cs b/src/MSBuildTaskHost/INodePacket.cs new file mode 100644 index 00000000000..af4502e57f0 --- /dev/null +++ b/src/MSBuildTaskHost/INodePacket.cs @@ -0,0 +1,381 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +using System; +using System.IO; +using Microsoft.Build.Internal; + +namespace Microsoft.Build.BackEnd +{ + #region Enums + + /// + /// Enumeration of all of the packet types used for communication. + /// Uses lower 6 bits for packet type (0-63), upper 2 bits reserved for flags. + /// + internal enum NodePacketType : byte + { + // Mask for extracting packet type (lower 6 bits) + TypeMask = 0x3F, // 00111111 + + /// + /// Notifies the Node to set a configuration for a particular build. This is sent before + /// any BuildRequests are made and will not be sent again for a particular build. This instructs + /// the node to prepare to receive build requests. + /// + /// Contains: + /// Build ID + /// Environment variables + /// Logging Services Configuration + /// Node ID + /// Default Global Properties + /// Toolset Definition Locations + /// Startup Directory + /// UI Culture Information + /// App Domain Configuration XML + /// + NodeConfiguration = 0x00, + + /// + /// A BuildRequestConfiguration object. + /// When sent TO a node, this informs the node of a build configuration. + /// When sent FROM a node, this requests a BuildRequestConfigurationResponse to map the configuration to the + /// appropriate global configuration ID. + /// + /// Contents: + /// Configuration ID + /// Project Filename + /// Project Properties + /// Project Tools Version + /// + BuildRequestConfiguration, // 0x01 + + /// + /// A response to a request to map a build configuration + /// + /// Contents: + /// Node Configuration ID + /// Global Configuration ID + /// + BuildRequestConfigurationResponse, // 0x02 + + /// + /// Information about a project that has been loaded by a node. + /// + /// Contents: + /// Global Configuration ID + /// Initial Targets + /// Default Targets + /// + ProjectLoadInfo, // 0x03 + + /// + /// Packet used to inform the scheduler that a node's active build request is blocked. + /// + /// Contents: + /// Build Request ID + /// Active Targets + /// Blocked Target, if any + /// Child Requests, if any + /// + BuildRequestBlocker, // 0x04 + + /// + /// Packet used to unblocked a blocked request on a node. + /// + /// Contents: + /// Build Request ID + /// Build Results for child requests, if any. + /// + BuildRequestUnblocker, // 0x05 + + /// + /// A BuildRequest object + /// + /// Contents: + /// Build Request ID + /// Configuration ID + /// Project Instance ID + /// Targets + /// + BuildRequest, // 0x06 + + /// + /// A BuildResult object + /// + /// Contents: + /// Build ID + /// Project Instance ID + /// Targets + /// Outputs (per Target) + /// Results (per Target) + /// + BuildResult, // 0x07 + + /// + /// A logging message. + /// + /// Contents: + /// Build Event Type + /// Build Event Args + /// + LogMessage, // 0x08 + + /// + /// Informs the node that the build is complete. + /// + /// Contents: + /// Prepare For Reuse + /// + NodeBuildComplete, // 0x09 + + /// + /// Reported by the node (or node provider) when a node has terminated. This is the final packet that will be received + /// from a node. + /// + /// Contents: + /// Reason + /// + NodeShutdown, // 0x0A + + /// + /// Notifies the task host to set the task-specific configuration for a particular task execution. + /// This is sent in place of NodeConfiguration and gives the task host all the information it needs + /// to set itself up and execute the task that matches this particular configuration. + /// + /// Contains: + /// Node ID (of parent MSBuild node, to make the logging work out) + /// Startup directory + /// Environment variables + /// UI Culture information + /// App Domain Configuration XML + /// Task name + /// Task assembly location + /// Parameter names and values to set to the task prior to execution + /// + TaskHostConfiguration, // 0x0B + + /// + /// Informs the parent node that the task host has finished executing a + /// particular task. Does not need to contain identifying information + /// about the task, because the task host will only ever be connected to + /// one parent node at a a time, and will only ever be executing one task + /// for that node at any one time. + /// + /// Contents: + /// Task result (success / failure) + /// Resultant parameter values (for output gathering) + /// + TaskHostTaskComplete, // 0x0C + + /// + /// Message sent from the node to its paired task host when a task that + /// supports ICancellableTask is cancelled. + /// + /// Contents: + /// (nothing) + /// + TaskHostTaskCancelled, // 0x0D + + /// + /// Message sent from a node when it needs to have an SDK resolved. + /// + ResolveSdkRequest, // 0x0E + + /// + /// Message sent back to a node when an SDK has been resolved. + /// + ResolveSdkResponse, // 0x0F + + /// + /// Message sent from a node when a task is requesting or returning resources from the scheduler. + /// + ResourceRequest, // 0x10 + + /// + /// Message sent back to a node informing it about the resource that were granted by the scheduler. + /// + ResourceResponse, // 0x11 + + /// + /// Message sent from a node reporting a file access. + /// + FileAccessReport, // 0x12 + + /// + /// Message sent from a node reporting process data. + /// + ProcessReport, // 0x13 + + + /// Notifies the RAR node to set a configuration for a particular build. + RarNodeEndpointConfiguration, + + /// + /// A request contains the inputs to the RAR task. + /// + RarNodeExecuteRequest, // 0x14 + + /// + /// A request contains the outputs and log events of a completed RAR task. + /// + RarNodeExecuteResponse, // 0x15 + + // Reserve space for future core packet types (0x16-0x3B available for expansion) + + // Server command packets placed at end of safe range to maintain separation from core packets + #region ServerNode enums + + /// + /// A batch of log events emitted while the RAR task is executing. + /// + RarNodeBufferedLogEvents, + + /// + /// Command in form of MSBuild command line for server node - MSBuild Server. + /// + ServerNodeBuildCommand = 0x3C, // End of safe range + + /// + /// Response from server node command. + /// + ServerNodeBuildResult = 0x3D, + + /// + /// Info about server console activity. + /// + ServerNodeConsoleWrite = 0x3E, + + /// + /// Command to cancel ongoing build. + /// + ServerNodeBuildCancel = 0x3F, // Last value in safe range (0x3F = 00111111) + + #endregion + } + #endregion + + /// + /// This interface represents a packet which may be transmitted using an INodeEndpoint. + /// Implementations define the serialized form of the data. + /// + internal interface INodePacket : ITranslatable + { + #region Properties + /// + /// The type of the packet. Used to reconstitute the packet using the correct factory. + /// + NodePacketType Type + { + get; + } + + #endregion + } + + /// + /// Provides utilities for handling node packet types and extended headers in MSBuild's distributed build system. + /// + /// This class manages the communication protocol between build nodes, including: + /// - Packet versioning for protocol compatibility + /// - Extended header flags for enhanced packet metadata + /// - Type extraction and manipulation for network communication + /// + /// The packet format uses the upper 2 bits (6-7) for flags while preserving + /// the lower 6 bits for the actual packet type enumeration. + /// + internal static class NodePacketTypeExtensions + { + /// + /// Defines the communication protocol version for node communication. + /// + /// Version 1: Introduced for the .NET Task Host protocol. This version + /// excludes the translation of appDomainConfig within TaskHostConfiguration + /// to maintain backward compatibility and reduce serialization overhead. + /// + /// Version 2: Adds support of HostServices and target name translation in TaskHostConfiguration. + /// + /// When incrementing this version, ensure compatibility with existing + /// task hosts and update the corresponding deserialization logic. + /// + public const byte PacketVersion = 2; + + // Flag bits in upper 2 bits + private const byte ExtendedHeaderFlag = 0x40; // Bit 6: 01000000 + + /// + /// Determines if a packet has an extended header by checking if the extended header flag is set. + /// Uses bit 6 which is now safely separated from packet type values. + /// + /// The raw packet type byte. + /// True if the packet has an extended header, false otherwise + public static bool HasExtendedHeader(byte rawType) => (rawType & ExtendedHeaderFlag) != 0; + + /// + /// Get base packet type, stripping all flag bits (bits 6 and 7). + /// + /// The raw packet type byte with potential flags. + /// The clean packet type without flag bits. + public static NodePacketType GetNodePacketType(byte rawType) => (NodePacketType)(rawType & (byte)NodePacketType.TypeMask); + + /// + /// Create a packet type byte with extended header flag for net task host packets. + /// + /// Handshake options to check. + /// Base packet type. + /// Output byte with flag set if applicable. + /// True if extended header flag was set, false otherwise. + public static bool TryCreateExtendedHeaderType(HandshakeOptions handshakeOptions, NodePacketType type, out byte extendedheader) + { + if (Handshake.IsHandshakeOptionEnabled(handshakeOptions, HandshakeOptions.TaskHost) && Handshake.IsHandshakeOptionEnabled(handshakeOptions, HandshakeOptions.NET)) + { + extendedheader = (byte)((byte)type | ExtendedHeaderFlag); + return true; + } + + extendedheader = (byte)type; + return false; + } + + /// + /// Reads the protocol version from an extended header in the stream. + /// This method expects the stream to be positioned at the version byte. + /// + /// The stream to read the version byte from. + /// The protocol version byte read from the stream. + /// Thrown when the stream ends unexpectedly while reading the version. + public static byte ReadVersion(Stream stream) + { + int value = stream.ReadByte(); + if (value == -1) + { + throw new EndOfStreamException("Unexpected end of stream while reading version"); + } + + return (byte)value; + } + + /// + /// Writes the protocol version byte to the extended header in the stream. + /// This is typically called after writing a packet type with the extended header flag. + /// + /// The stream to write the version byte to. + /// The protocol version to write to the stream. + public static void WriteVersion(Stream stream, byte version) => stream.WriteByte(version); + + /// + /// Negotiates the packet version to use for communication between nodes. + /// Returns the lower of the two versions to ensure compatibility between + /// nodes that may be running different versions of MSBuild. + /// + /// This allows forward and backward compatibility when nodes with different + /// packet versions communicate - they will use the lowest common version + /// that both understand. + /// + /// The packet version supported by the other node. + /// The negotiated protocol version that both nodes can use (the minimum of the two versions). + public static byte GetNegotiatedPacketVersion(byte otherPacketVersion) => Math.Min(PacketVersion, otherPacketVersion); + } +} diff --git a/src/MSBuildTaskHost/INodePacketFactory.cs b/src/MSBuildTaskHost/INodePacketFactory.cs new file mode 100644 index 00000000000..63d469eb021 --- /dev/null +++ b/src/MSBuildTaskHost/INodePacketFactory.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + /// + /// A delegate representing factory methods used to re-create packets deserialized from a stream. + /// + /// The translator containing the packet data. + /// The packet reconstructed from the stream. + internal delegate INodePacket NodePacketFactoryMethod(ITranslator translator); + + /// + /// This interface represents an object which is used to reconstruct packet objects from + /// binary data. + /// + internal interface INodePacketFactory + { + #region Methods + + /// + /// Registers the specified handler for a particular packet type. + /// + /// The packet type. + /// The factory for packets of the specified type. + /// The handler to be called when packets of the specified type are received. + void RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory, INodePacketHandler handler); + + /// + /// Unregisters a packet handler. + /// + /// The packet type. + void UnregisterPacketHandler(NodePacketType packetType); + + /// + /// Takes a serializer, deserializes the packet and routes it to the appropriate handler. + /// + /// The node from which the packet was received. + /// The packet type. + /// The translator containing the data from which the packet should be reconstructed. + void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITranslator translator); + + /// + /// Takes a serializer and deserializes the packet. + /// + /// The packet type. + /// The translator containing the data from which the packet should be reconstructed. + INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator); + + /// + /// Routes the specified packet. + /// + /// The node from which the packet was received. + /// The packet to route. + void RoutePacket(int nodeId, INodePacket packet); + + #endregion + } +} diff --git a/src/MSBuildTaskHost/INodePacketHandler.cs b/src/MSBuildTaskHost/INodePacketHandler.cs new file mode 100644 index 00000000000..8d72f9dd053 --- /dev/null +++ b/src/MSBuildTaskHost/INodePacketHandler.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + /// + /// Objects which wish to receive packets from the NodePacketRouter must implement this interface. + /// + internal interface INodePacketHandler + { + /// + /// This method is invoked by the NodePacketRouter when a packet is received and is intended for + /// this recipient. + /// + /// The node from which the packet was received. + /// The packet. + void PacketReceived(int node, INodePacket packet); + } +} diff --git a/src/MSBuildTaskHost/InterningBinaryReader.cs b/src/MSBuildTaskHost/InterningBinaryReader.cs new file mode 100644 index 00000000000..307cc68bdc9 --- /dev/null +++ b/src/MSBuildTaskHost/InterningBinaryReader.cs @@ -0,0 +1,311 @@ +// 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.Text; +using System.IO; +using System.Diagnostics; +using System.Threading; + +#if !CLR2COMPATIBILITY +using System.Buffers; +#endif + +using ErrorUtilities = Microsoft.Build.Shared.ErrorUtilities; + +using Microsoft.NET.StringTools; + +#nullable disable + +namespace Microsoft.Build +{ + /// + /// Replacement for BinaryReader which attempts to intern the strings read by ReadString. + /// + internal class InterningBinaryReader : BinaryReader + { + /// + /// The maximum size, in bytes, to read at once. + /// +#if DEBUG + private const int MaxCharsBuffer = 10; +#else + private const int MaxCharsBuffer = 20000; +#endif + + /// + /// A cache of recently used buffers. This is a pool of size 1 to avoid allocating moderately sized + /// objects repeatedly. Used in scenarios that don't have a good context to attach + /// a shared buffer to. + /// + private static Buffer s_bufferPool; + + /// + /// Shared buffer saves allocating these arrays many times. + /// + private Buffer _buffer; + + /// + /// True if is owned by this instance, false if it was passed by the caller. + /// + private bool _isPrivateBuffer; + + /// + /// The decoder used to translate from UTF8 (or whatever). + /// + private Decoder _decoder; + + /// + /// Comment about constructing. + /// + private InterningBinaryReader(Stream input, Buffer buffer, bool isPrivateBuffer) + : base(input, Encoding.UTF8) + { + if (input == null) + { + throw new InvalidOperationException(); + } + + _buffer = buffer; + _isPrivateBuffer = isPrivateBuffer; + _decoder = Encoding.UTF8.GetDecoder(); + } + + /// + /// Read a string while checking the string precursor for intern opportunities. + /// Taken from ndp\clr\src\bcl\system\io\binaryreader.cs-ReadString() + /// + public override String ReadString() + { + char[] resultBuffer = null; + try + { + MemoryStream memoryStream = this.BaseStream as MemoryStream; + + int currPos = 0; + int n = 0; + int stringLength; + int readLength; + int charsRead = 0; + + // Length of the string in bytes, not chars + stringLength = Read7BitEncodedInt(); + if (stringLength < 0) + { + throw new IOException(); + } + + if (stringLength == 0) + { + return String.Empty; + } + + char[] charBuffer = _buffer.CharBuffer; + do + { + readLength = ((stringLength - currPos) > MaxCharsBuffer) ? MaxCharsBuffer : (stringLength - currPos); + + byte[] rawBuffer = null; + int rawPosition = 0; + + if (memoryStream != null) + { + // Optimization: we can avoid reading into a byte buffer + // and instead read directly from the memorystream's backing buffer + rawBuffer = memoryStream.GetBuffer(); + rawPosition = (int)memoryStream.Position; + int length = (int)memoryStream.Length; + n = (rawPosition + readLength) < length ? readLength : length - rawPosition; + + // Attempt to track down an intermittent failure -- n should not ever be negative, but + // we're occasionally seeing it when we do the decoder.GetChars below -- by providing + // a bit more information when we do hit the error, in the place where (by code inspection) + // the actual error seems most likely to be occurring. + if (n < 0) + { + ErrorUtilities.ThrowInternalError("From calculating based on the memorystream, about to read n = {0}. length = {1}, rawPosition = {2}, readLength = {3}, stringLength = {4}, currPos = {5}.", n, length, rawPosition, readLength, stringLength, currPos); + } + + memoryStream.Seek(n, SeekOrigin.Current); + } + + if (rawBuffer == null) + { + rawBuffer = _buffer.ByteBuffer; + rawPosition = 0; + n = BaseStream.Read(rawBuffer, 0, readLength); + + // See above explanation -- the OutOfRange exception may also be coming from our setting of n here ... + if (n < 0) + { + ErrorUtilities.ThrowInternalError("From getting the length out of BaseStream.Read directly, about to read n = {0}. readLength = {1}, stringLength = {2}, currPos = {3}", n, readLength, stringLength, currPos); + } + } + + if (n == 0) + { + throw new EndOfStreamException(); + } + + if (currPos == 0 && n == stringLength) + { + charsRead = _decoder.GetChars(rawBuffer, rawPosition, n, charBuffer, 0); + return Strings.WeakIntern(charBuffer.AsSpan(0, charsRead)); + } +#if !CLR2COMPATIBILITY + resultBuffer ??= ArrayPool.Shared.Rent(stringLength); // Actual string length in chars may be smaller. +#else + // Since NET35 is only used in rare TaskHost processes, we decided to leave it as-is. + resultBuffer ??= new char[stringLength]; // Actual string length in chars may be smaller. +#endif + charsRead += _decoder.GetChars(rawBuffer, rawPosition, n, resultBuffer, charsRead); + + currPos += n; + } + while (currPos < stringLength); + + var retval = Strings.WeakIntern(resultBuffer.AsSpan(0, charsRead)); + + return retval; + } + catch (Exception e) + { + Debug.Assert(false, e.ToString()); + throw; + } +#if !CLR2COMPATIBILITY + finally + { + // resultBuffer shall always be either Rented or null + if (resultBuffer != null) + { + ArrayPool.Shared.Return(resultBuffer); + } + } +#endif + } + + /// + /// A shared buffer to avoid extra allocations in InterningBinaryReader. + /// + /// + /// The caller is responsible for managing the lifetime of the returned buffer and for passing it to . + /// + internal static BinaryReaderFactory CreateSharedBuffer() + { + return new Buffer(); + } + + /// + /// A placeholder instructing InterningBinaryReader to use pooled buffer (to avoid extra allocations). + /// + /// + /// Lifetime of the pooled buffer is managed by InterningBinaryReader (tied to BinaryReader lifetime wrapping the buffer) + /// + internal static BinaryReaderFactory PoolingBuffer => NullBuffer.Instance; + + /// + /// Gets a buffer from the pool or creates a new one. + /// + /// The . Should be returned to the pool after we're done with it. + private static Buffer GetPooledBuffer() + { + Buffer buffer = Interlocked.Exchange(ref s_bufferPool, null); + if (buffer != null) + { + return buffer; + } + return new Buffer(); + } + + #region IDisposable pattern + + /// + /// Returns our buffer to the pool if we were not passed one by the caller. + /// + protected override void Dispose(bool disposing) + { + if (_isPrivateBuffer) + { + // If we created this buffer then try to return it to the pool. If s_bufferPool is non-null we leave it alone, + // the idea being that it's more likely to have lived longer than our buffer. + Interlocked.CompareExchange(ref s_bufferPool, _buffer, null); + } + base.Dispose(disposing); + } + + #endregion + + /// + /// Create a BinaryReader. It will either be an interning reader or standard binary reader + /// depending on whether the interning reader is possible given the buffer and stream. + /// + private static BinaryReader Create(Stream stream, BinaryReaderFactory sharedBuffer) + { + Buffer buffer = (Buffer)sharedBuffer; + if (buffer != null) + { + return new InterningBinaryReader(stream, buffer, false); + } + return new InterningBinaryReader(stream, GetPooledBuffer(), true); + } + + /// + /// Holds thepreallocated buffer. + /// + private class Buffer : BinaryReaderFactory + { + private char[] _charBuffer; + private byte[] _byteBuffer; + + /// + /// Yes, we are constructing. + /// + internal Buffer() + { + } + + /// + /// The char buffer. + /// + internal char[] CharBuffer + { + get + { + _charBuffer ??= new char[MaxCharsBuffer]; + return _charBuffer; + } + } + + /// + /// The byte buffer. + /// + internal byte[] ByteBuffer + { + get + { + _byteBuffer ??= new byte[Encoding.UTF8.GetMaxByteCount(MaxCharsBuffer)]; + return _byteBuffer; + } + } + + public override BinaryReader Create(Stream stream) + { + return InterningBinaryReader.Create(stream, this); + } + } + + private class NullBuffer : BinaryReaderFactory + { + private NullBuffer() + { } + + public static readonly BinaryReaderFactory Instance = new NullBuffer(); + + public override BinaryReader Create(Stream stream) + { + return InterningBinaryReader.Create(stream, null); + } + } + } +} diff --git a/src/MSBuildTaskHost/IsExternalInit.cs b/src/MSBuildTaskHost/IsExternalInit.cs new file mode 100644 index 00000000000..f0a0588d1df --- /dev/null +++ b/src/MSBuildTaskHost/IsExternalInit.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if NET +using System.Runtime.CompilerServices; + +// Type-forward to the inbox class where available, in order to maintain binary compatibility +// between the .NET and .NET Standard 2.0 assemblies. +[assembly: TypeForwardedTo(typeof(IsExternalInit))] + +#else + +using System.ComponentModel; + +namespace System.Runtime.CompilerServices +{ + // Needed so we can use init setters in full fw or netstandard + // (details: https://developercommunity.visualstudio.com/t/error-cs0518-predefined-type-systemruntimecompiler/1244809) + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class IsExternalInit { } +} +#endif diff --git a/src/MSBuildTaskHost/LoadedType.cs b/src/MSBuildTaskHost/LoadedType.cs new file mode 100644 index 00000000000..18f59bd164f --- /dev/null +++ b/src/MSBuildTaskHost/LoadedType.cs @@ -0,0 +1,256 @@ +// 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.Reflection; +using Microsoft.Build.Execution; +using Microsoft.Build.Framework; + + +namespace Microsoft.Build.Shared +{ + /// + /// This class packages information about a type loaded from an assembly: for example, + /// the GenerateResource task class type or the ConsoleLogger logger class type. + /// + internal sealed class LoadedType + { + #region Constructor + + /// + /// Creates an instance of this class for the given type. + /// + /// The Type to be loaded + /// Information used to load the assembly + /// The assembly which has been loaded, if any + /// type of an ITaskItem + /// Assembly runtime based on assembly attributes. + /// Assembly architecture extracted from PE flags + /// Whether this type was loaded via MetadataLoadContext + internal LoadedType( + Type type, + AssemblyLoadInfo assemblyLoadInfo, + Assembly loadedAssembly, + Type iTaskItemType, + string? runtime = null, + string? architecture = null, + bool loadedViaMetadataLoadContext = false) + { + ErrorUtilities.VerifyThrow(type != null, "We must have the type."); + ErrorUtilities.VerifyThrow(assemblyLoadInfo != null, "We must have the assembly the type was loaded from."); + ErrorUtilities.VerifyThrow(loadedAssembly is not null, "The assembly should always be loaded even if only by MetadataLoadContext."); + + Type = type; + Assembly = assemblyLoadInfo; + + HasSTAThreadAttribute = CheckForHardcodedSTARequirement(); + LoadedAssemblyName = loadedAssembly.GetName(); + LoadedViaMetadataLoadContext = loadedViaMetadataLoadContext; + Architecture = architecture; + Runtime = runtime; + + // For inline tasks loaded from bytes, Assembly.Location is empty, so use the original path + Path = string.IsNullOrEmpty(loadedAssembly.Location) + ? assemblyLoadInfo.AssemblyLocation + : loadedAssembly.Location; + + LoadedAssembly = loadedAssembly; + +#if !NET35 + // This block is reflection only loaded type implementation. Net35 does not support it, and fall backs to former implementation in #else + // Property `Properties` set in this block aren't used by TaskHosts. Properties below are only used on the NodeProvider side to get information about the + // properties and reflect over them without needing them to be fully loaded, so it also isn't need for TaskHosts. + + // MetadataLoadContext-loaded Type objects don't support testing for inherited attributes, so we manually walk the BaseType chain. + Type? t = type; + while (t is not null) + { + try + { + if (TypeUtilities.HasAttribute(t)) + { + HasLoadInSeparateAppDomainAttribute = true; + } + + if (TypeUtilities.HasAttribute(t)) + { + HasSTAThreadAttribute = true; + } + + if (t.IsMarshalByRef) + { + IsMarshalByRef = true; + } + } + catch when (loadedViaMetadataLoadContext) + { + // when assembly is loaded via metadata load context we can ignore exception because there is no expectation to have it in proc. + // BUT we should throw for in-proc case and handle it on higher level. + } + + t = t.BaseType; + } + + PropertyInfo[] props = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); + Properties = new ReflectableTaskPropertyInfo[props.Length]; + if (loadedViaMetadataLoadContext) + { + PropertyAssemblyQualifiedNames = new string[props.Length]; + } + + for (int i = 0; i < props.Length; i++) + { + bool outputAttribute = false; + bool requiredAttribute = false; + foreach (CustomAttributeData attr in CustomAttributeData.GetCustomAttributes(props[i])) + { + try + { + if (attr.AttributeType?.Name.Equals(nameof(OutputAttribute)) == true) + { + outputAttribute = true; + } + else if (attr.AttributeType?.Name.Equals(nameof(RequiredAttribute)) == true) + { + requiredAttribute = true; + } + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + // Skip attributes that can't be loaded + continue; + } + } + + // Check whether it's assignable to ITaskItem or ITaskItem[]. Simplify to just checking for ITaskItem. + Type? pt = null; + try + { + pt = props[i].PropertyType; + if (pt.IsArray) + { + pt = pt.GetElementType(); + } + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + // Skip properties that can't be loaded + continue; + } + + bool isAssignableToITask = false; + try + { + isAssignableToITask = pt != null && iTaskItemType.IsAssignableFrom(pt); + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + // Can't determine assignability, default to false + } + + Properties[i] = new ReflectableTaskPropertyInfo(props[i], outputAttribute, requiredAttribute, isAssignableToITask); + if (loadedViaMetadataLoadContext && PropertyAssemblyQualifiedNames != null) + { + try + { + PropertyAssemblyQualifiedNames[i] = Properties[i]?.PropertyType?.AssemblyQualifiedName ?? string.Empty; + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + PropertyAssemblyQualifiedNames[i] = string.Empty; + } + } + } +#else + // For v3.5 fallback to old full type approach, as oppose to reflection only + HasLoadInSeparateAppDomainAttribute = Type.GetTypeInfo().IsDefined(typeof(LoadInSeparateAppDomainAttribute), true /* inherited */); + HasSTAThreadAttribute = Type.GetTypeInfo().IsDefined(typeof(RunInSTAAttribute), true /* inherited */); + IsMarshalByRef = Type.IsMarshalByRef; +#endif + } + + #endregion + + /// + /// Gets whether there's a LoadInSeparateAppDomain attribute on this type. + /// + public bool HasLoadInSeparateAppDomainAttribute { get; } + + /// + /// Gets whether there's a STAThread attribute on the Execute method of this type. + /// + public bool HasSTAThreadAttribute { get; } + + /// + /// Gets whether this type implements MarshalByRefObject. + /// + public bool IsMarshalByRef { get; } + + /// + /// Gets whether this type was loaded by using MetadataLoadContext. + /// + public bool LoadedViaMetadataLoadContext { get; } + + /// + /// Determines if the task has a hardcoded requirement for STA thread usage. + /// + private bool CheckForHardcodedSTARequirement() + { + // Special hard-coded attributes for certain legacy tasks which need to run as STA because they were written before + // we changed to running all tasks in MTA. + if (String.Equals("Microsoft.Build.Tasks.Xaml.PartialClassGenerationTask", Type.FullName, StringComparison.OrdinalIgnoreCase)) + { + AssemblyName assemblyName = Type.GetTypeInfo().Assembly.GetName(); + Version lastVersionToForce = new Version(3, 5); + if (assemblyName.Version?.CompareTo(lastVersionToForce) > 0) + { + if (String.Equals(assemblyName.Name, "PresentationBuildTasks", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + } + + return false; + } + + #region Properties + + /// + /// Gets the type that was loaded from an assembly. + /// + /// The loaded type. + internal Type Type { get; private set; } + + internal AssemblyName LoadedAssemblyName { get; private set; } + + internal string? Architecture { get; private set; } + + internal string? Runtime { get; private set; } + + internal string Path { get; private set; } + + /// + /// If we loaded an assembly for this type. + /// We use this information to help created AppDomains to resolve types that it could not load successfully + /// + internal Assembly LoadedAssembly { get; private set; } + +#if !NET35 + internal ReflectableTaskPropertyInfo[] Properties { get; private set; } +#endif + + /// + /// Assembly-qualified names for properties. Only has a value if this type was loaded using MetadataLoadContext. + /// + internal string[]? PropertyAssemblyQualifiedNames { get; private set; } + + /// + /// Gets the assembly the type was loaded from. + /// + /// The assembly info for the loaded type. + internal AssemblyLoadInfo Assembly { get; private set; } + + #endregion + } +} diff --git a/src/MSBuildTaskHost/LogMessagePacketBase.cs b/src/MSBuildTaskHost/LogMessagePacketBase.cs new file mode 100644 index 00000000000..33e7c619c97 --- /dev/null +++ b/src/MSBuildTaskHost/LogMessagePacketBase.cs @@ -0,0 +1,1075 @@ +// 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; +using System.IO; +using System.Reflection; + +using Microsoft.Build.BackEnd; +using Microsoft.Build.Framework; + +#if !TASKHOST +using Microsoft.Build.Framework.Telemetry; +using Microsoft.Build.Experimental.BuildCheck; +#endif + +#nullable disable + +namespace Microsoft.Build.Shared +{ + #region Enumerations + /// + /// An enumeration of all the types of BuildEventArgs that can be + /// packaged by this logMessagePacket + /// + internal enum LoggingEventType : int + { + /// + /// An invalid eventId, used during initialization of a . + /// + Invalid = -1, + + /// + /// Event is a CustomEventArgs. + /// + CustomEvent = 0, + + /// + /// Event is a . + /// + BuildErrorEvent = 1, + + /// + /// Event is a . + /// + BuildFinishedEvent = 2, + + /// + /// Event is a . + /// + BuildMessageEvent = 3, + + /// + /// Event is a . + /// + BuildStartedEvent = 4, + + /// + /// Event is a . + /// + BuildWarningEvent = 5, + + /// + /// Event is a . + /// + ProjectFinishedEvent = 6, + + /// + /// Event is a . + /// + ProjectStartedEvent = 7, + + /// + /// Event is a . + /// + TargetStartedEvent = 8, + + /// + /// Event is a . + /// + TargetFinishedEvent = 9, + + /// + /// Event is a . + /// + TaskStartedEvent = 10, + + /// + /// Event is a . + /// + TaskFinishedEvent = 11, + + /// + /// Event is a . + /// + TaskCommandLineEvent = 12, + + /// + /// Event is a . + /// + TaskParameterEvent = 13, + + /// + /// Event is a . + /// + ProjectEvaluationStartedEvent = 14, + + /// + /// Event is a . + /// + ProjectEvaluationFinishedEvent = 15, + + /// + /// Event is a . + /// + ProjectImportedEvent = 16, + + /// + /// Event is a . + /// + TargetSkipped = 17, + + /// + /// Event is a . + /// + Telemetry = 18, + + /// + /// Event is an . + /// + EnvironmentVariableReadEvent = 19, + + /// + /// Event is a . + /// + ResponseFileUsedEvent = 20, + + /// + /// Event is an . + /// + AssemblyLoadEvent = 21, + + /// + /// Event is . + /// + ExternalProjectStartedEvent = 22, + + /// + /// Event is . + /// + ExternalProjectFinishedEvent = 23, + + /// + /// Event is . + /// + ExtendedCustomEvent = 24, + + /// + /// Event is . + /// + ExtendedBuildErrorEvent = 25, + + /// + /// Event is . + /// + ExtendedBuildWarningEvent = 26, + + /// + /// Event is . + /// + ExtendedBuildMessageEvent = 27, + + /// + /// Event is . + /// + CriticalBuildMessage = 28, + + /// + /// Event is . + /// + MetaprojectGenerated = 29, + + /// + /// Event is . + /// + PropertyInitialValueSet = 30, + + /// + /// Event is . + /// + PropertyReassignment = 31, + + /// + /// Event is . + /// + UninitializedPropertyRead = 32, + + /// + /// Event is . + /// + ExtendedCriticalBuildMessageEvent = 33, + + /// + /// Event is a . + /// + GeneratedFileUsedEvent = 34, + + /// + /// Event is . + /// + BuildCheckMessageEvent = 35, + + /// + /// Event is . + /// + BuildCheckWarningEvent = 36, + + /// + /// Event is . + /// + BuildCheckErrorEvent = 37, + + /// + /// Event is . + /// + BuildCheckTracingEvent = 38, + + /// + /// Event is . + /// + BuildCheckAcquisitionEvent = 39, + + /// + /// Event is . + /// + BuildSubmissionStartedEvent = 40, + + /// + /// Event is + /// + BuildCanceledEvent = 41, + + /// + /// Event is + /// + WorkerNodeTelemetryEvent = 42, + } + #endregion + + /// + /// A packet to encapsulate a BuildEventArg logging message. + /// Contents: + /// Build Event Type + /// Build Event Args + /// + internal class LogMessagePacketBase : INodePacket + { + /// + /// The packet version, which is based on the CLR version. Cached because querying Environment.Version each time becomes an allocation bottleneck. + /// + private static readonly int s_defaultPacketVersion = (Environment.Version.Major * 10) + Environment.Version.Minor; + +#if TASKHOST + /// + /// Dictionary of methods used to read BuildEventArgs. + /// + private static readonly Dictionary s_readMethodCache = new Dictionary(); + +#endif + /// + /// Dictionary of methods used to write BuildEventArgs. + /// + private static readonly Dictionary s_writeMethodCache = new Dictionary(); + + #region Data + + /// + /// The event type of the buildEventArg based on the + /// LoggingEventType enumeration + /// + private LoggingEventType _eventType = LoggingEventType.Invalid; + + /// + /// The buildEventArg which is encapsulated by the packet + /// + private BuildEventArgs _buildEvent; + + /// + /// The sink id + /// + private int _sinkId; + + #endregion + + #region Constructors + + /// + /// Encapsulates the buildEventArg in this packet. + /// + internal LogMessagePacketBase(KeyValuePair? nodeBuildEvent) + { + ErrorUtilities.VerifyThrow(nodeBuildEvent != null, "nodeBuildEvent was null"); + _buildEvent = nodeBuildEvent.Value.Value; + _sinkId = nodeBuildEvent.Value.Key; + _eventType = GetLoggingEventId(_buildEvent); + } + + /// + /// Constructor for deserialization + /// + internal LogMessagePacketBase(ITranslator translator) => Translate(translator); + + #endregion + + #region Delegates + + /// + /// Delegate representing a method on the BuildEventArgs classes used to write to a stream. + /// + private delegate void ArgsWriterDelegate(BinaryWriter writer); + + /// + /// Delegate representing a method on the BuildEventArgs classes used to read from a stream. + /// + private delegate void ArgsReaderDelegate(BinaryReader reader, int version); + + #endregion + + #region Properties + + /// + /// The nodePacket Type, in this case the packet is a Logging Message + /// + public NodePacketType Type + { + get { return NodePacketType.LogMessage; } + } + + /// + /// The buildEventArg wrapped by this packet + /// + internal KeyValuePair? NodeBuildEvent + { + get + { + return new KeyValuePair(_sinkId, _buildEvent); + } + } + + /// + /// The event type of the wrapped buildEventArg + /// based on the LoggingEventType enumeration + /// + internal LoggingEventType EventType + { + get + { + return _eventType; + } + } + #endregion + + #region INodePacket Methods + + /// + /// Reads/writes this packet + /// + public void Translate(ITranslator translator) + { + translator.TranslateEnum(ref _eventType, (int)_eventType); + translator.Translate(ref _sinkId); + if (translator.Mode == TranslationDirection.ReadFromStream) + { + ReadFromStream(translator); + } + else + { + WriteToStream(translator); + } + } + + #endregion + + /// + /// Writes the logging packet to the translator. + /// + internal void WriteToStream(ITranslator translator) + { + ErrorUtilities.VerifyThrow(_eventType != LoggingEventType.CustomEvent, "_eventType should not be a custom event"); + + MethodInfo methodInfo = null; + lock (s_writeMethodCache) + { + if (!s_writeMethodCache.TryGetValue(_eventType, out methodInfo)) + { + Type eventDerivedType = _buildEvent.GetType(); + methodInfo = eventDerivedType.GetMethod("WriteToStream", BindingFlags.NonPublic | BindingFlags.Instance); + s_writeMethodCache.Add(_eventType, methodInfo); + } + } + + int packetVersion = s_defaultPacketVersion; + + // Make sure the other side knows what sort of serialization is coming + translator.Translate(ref packetVersion); + + bool eventCanSerializeItself = EventCanSerializeItself(_eventType, methodInfo); + + translator.Translate(ref eventCanSerializeItself); + + if (eventCanSerializeItself) + { + // 3.5 or later -- we have custom serialization methods, so let's use them. + ArgsWriterDelegate writerMethod = (ArgsWriterDelegate)CreateDelegateRobust(typeof(ArgsWriterDelegate), _buildEvent, methodInfo); + writerMethod(translator.Writer); + + TranslateAdditionalProperties(translator, _eventType, _buildEvent); + } + else + { + WriteEventToStream(_buildEvent, _eventType, translator); + } + } + + /// + /// Reads the logging packet from the translator. + /// + internal void ReadFromStream(ITranslator translator) + { + ErrorUtilities.VerifyThrow(_eventType != LoggingEventType.CustomEvent, "_eventType should not be a custom event"); + + _buildEvent = GetBuildEventArgFromId(); + + // The other side is telling us whether the event knows how to log itself, or whether we're going to have + // to do it manually + int packetVersion = s_defaultPacketVersion; + translator.Translate(ref packetVersion); + + bool eventCanSerializeItself = true; + translator.Translate(ref eventCanSerializeItself); + + if (eventCanSerializeItself) + { + +#if TASKHOST + MethodInfo methodInfo = null; + lock (s_readMethodCache) + { + if (!s_readMethodCache.TryGetValue(_eventType, out methodInfo)) + { + Type eventDerivedType = _buildEvent.GetType(); + methodInfo = eventDerivedType.GetMethod("CreateFromStream", BindingFlags.NonPublic | BindingFlags.Instance); + s_readMethodCache.Add(_eventType, methodInfo); + } + } + + ArgsReaderDelegate readerMethod = (ArgsReaderDelegate)CreateDelegateRobust(typeof(ArgsReaderDelegate), _buildEvent, methodInfo); + + readerMethod(translator.Reader, packetVersion); + +#else + _buildEvent.CreateFromStream(translator.Reader, packetVersion); +#endif + + TranslateAdditionalProperties(translator, _eventType, _buildEvent); + } + else + { + _buildEvent = ReadEventFromStream(_eventType, translator); + ErrorUtilities.VerifyThrow(_buildEvent is not null, "Not Supported LoggingEventType {0}", _eventType.ToString()); + } + + _eventType = GetLoggingEventId(_buildEvent); + } + + /// + /// Returns whether to use the event's own serialization method, if found. + /// If false, defers to overridable implementations in and + /// . + /// + protected virtual bool EventCanSerializeItself(LoggingEventType eventType, MethodInfo methodInfo) + => methodInfo != null; + + /// + /// Translates additional properties that are not handled by the default serialization. + /// + protected virtual void TranslateAdditionalProperties(ITranslator translator, LoggingEventType eventType, BuildEventArgs buildEvent) + { + } + + #region Private Methods + + /// + /// Wrapper for Delegate.CreateDelegate with retries. + /// + /// + /// TODO: Investigate if it would be possible to use one of the overrides of CreateDelegate + /// that doesn't force the delegate to be closed over its first argument, so that we can + /// only create the delegate once per event type and cache it. + /// + private static Delegate CreateDelegateRobust(Type type, Object firstArgument, MethodInfo methodInfo) + { + Delegate delegateMethod = null; + + for (int i = 0; delegateMethod == null && i < 5; i++) + { + try + { +#if CLR2COMPATIBILITY + delegateMethod = Delegate.CreateDelegate(type, firstArgument, methodInfo); +#else + delegateMethod = methodInfo.CreateDelegate(type, firstArgument); +#endif + } + catch (FileLoadException) when (i < 5) + { + // Sometimes, in 64-bit processes, the fusion load of Microsoft.Build.Framework.dll + // spontaneously fails when trying to bind to the delegate. However, it seems to + // not repeat on additional tries -- so we'll try again a few times. However, if + // it keeps happening, it's probably a real problem, so we want to go ahead and + // throw to let the user know what's up. + } + } + + return delegateMethod; + } + + /// + /// Takes in a id (LoggingEventType as an int) and creates the correct specific logging class + /// + private BuildEventArgs GetBuildEventArgFromId() + { + return _eventType switch + { + LoggingEventType.BuildErrorEvent => new BuildErrorEventArgs(null, null, null, -1, -1, -1, -1, null, null, null), + LoggingEventType.BuildFinishedEvent => new BuildFinishedEventArgs(null, null, false), + LoggingEventType.BuildMessageEvent => new BuildMessageEventArgs(null, null, null, MessageImportance.Normal), + LoggingEventType.BuildStartedEvent => new BuildStartedEventArgs(null, null), + LoggingEventType.BuildWarningEvent => new BuildWarningEventArgs(null, null, null, -1, -1, -1, -1, null, null, null), + LoggingEventType.ProjectFinishedEvent => new ProjectFinishedEventArgs(null, null, null, false), + LoggingEventType.ProjectStartedEvent => new ProjectStartedEventArgs(null, null, null, null, null, null), + LoggingEventType.TargetStartedEvent => new TargetStartedEventArgs(null, null, null, null, null), + LoggingEventType.TargetFinishedEvent => new TargetFinishedEventArgs(null, null, null, null, null, false), + LoggingEventType.TaskStartedEvent => new TaskStartedEventArgs(null, null, null, null, null), + LoggingEventType.TaskFinishedEvent => new TaskFinishedEventArgs(null, null, null, null, null, false), + LoggingEventType.TaskCommandLineEvent => new TaskCommandLineEventArgs(null, null, MessageImportance.Normal), + LoggingEventType.ResponseFileUsedEvent => new ResponseFileUsedEventArgs(null), + +#if !TASKHOST // MSBuildTaskHost is targeting Microsoft.Build.Framework.dll 3.5 + LoggingEventType.AssemblyLoadEvent => new AssemblyLoadBuildEventArgs(), + LoggingEventType.TaskParameterEvent => new TaskParameterEventArgs(0, null, null, true, default), + LoggingEventType.ProjectEvaluationStartedEvent => new ProjectEvaluationStartedEventArgs(), + LoggingEventType.ProjectEvaluationFinishedEvent => new ProjectEvaluationFinishedEventArgs(), + LoggingEventType.ProjectImportedEvent => new ProjectImportedEventArgs(), + LoggingEventType.TargetSkipped => new TargetSkippedEventArgs(), + LoggingEventType.Telemetry => new TelemetryEventArgs(), + LoggingEventType.ExtendedCustomEvent => new ExtendedCustomBuildEventArgs(), + LoggingEventType.ExtendedBuildErrorEvent => new ExtendedBuildErrorEventArgs(), + LoggingEventType.ExtendedBuildWarningEvent => new ExtendedBuildWarningEventArgs(), + LoggingEventType.ExtendedBuildMessageEvent => new ExtendedBuildMessageEventArgs(), + LoggingEventType.ExtendedCriticalBuildMessageEvent => new ExtendedCriticalBuildMessageEventArgs(), + LoggingEventType.ExternalProjectStartedEvent => new ExternalProjectStartedEventArgs(null, null, null, null, null), + LoggingEventType.ExternalProjectFinishedEvent => new ExternalProjectFinishedEventArgs(null, null, null, null, false), + LoggingEventType.CriticalBuildMessage => new CriticalBuildMessageEventArgs(null, null, null, -1, -1, -1, -1, null, null, null), + LoggingEventType.MetaprojectGenerated => new MetaprojectGeneratedEventArgs(null, null, null), + LoggingEventType.PropertyInitialValueSet => new PropertyInitialValueSetEventArgs(), + LoggingEventType.PropertyReassignment => new PropertyReassignmentEventArgs(), + LoggingEventType.UninitializedPropertyRead => new UninitializedPropertyReadEventArgs(), + LoggingEventType.GeneratedFileUsedEvent => new GeneratedFileUsedEventArgs(), + LoggingEventType.BuildCheckMessageEvent => new BuildCheckResultMessage(), + LoggingEventType.BuildCheckWarningEvent => new BuildCheckResultWarning(), + LoggingEventType.BuildCheckErrorEvent => new BuildCheckResultError(), + LoggingEventType.BuildCheckAcquisitionEvent => new BuildCheckAcquisitionEventArgs(), + LoggingEventType.BuildCheckTracingEvent => new BuildCheckTracingEventArgs(), + LoggingEventType.EnvironmentVariableReadEvent => new EnvironmentVariableReadEventArgs(), + LoggingEventType.BuildSubmissionStartedEvent => new BuildSubmissionStartedEventArgs(), + LoggingEventType.BuildCanceledEvent => new BuildCanceledEventArgs("Build canceled."), + LoggingEventType.WorkerNodeTelemetryEvent => new WorkerNodeTelemetryEventArgs(), +#endif + _ => throw new InternalErrorException("Should not get to the default of GetBuildEventArgFromId ID: " + _eventType) + }; + } + + /// + /// Based on the type of the BuildEventArg to be wrapped + /// generate an Id which identifies which concrete type the + /// BuildEventArg is. + /// + /// Argument to get the type Id for + /// An enumeration entry which represents the type + private LoggingEventType GetLoggingEventId(BuildEventArgs eventArg) + { + Type eventType = eventArg.GetType(); + if (eventType == typeof(BuildMessageEventArgs)) + { + return LoggingEventType.BuildMessageEvent; + } + else if (eventType == typeof(TaskCommandLineEventArgs)) + { + return LoggingEventType.TaskCommandLineEvent; + } +#if !TASKHOST + else if (eventType == typeof(TaskParameterEventArgs)) + { + return LoggingEventType.TaskParameterEvent; + } +#endif + else if (eventType == typeof(ProjectFinishedEventArgs)) + { + return LoggingEventType.ProjectFinishedEvent; + } + else if (eventType == typeof(ProjectStartedEventArgs)) + { + return LoggingEventType.ProjectStartedEvent; + } + else if (eventType == typeof(ExternalProjectStartedEventArgs)) + { + return LoggingEventType.ExternalProjectStartedEvent; + } + else if (eventType == typeof(ExternalProjectFinishedEventArgs)) + { + return LoggingEventType.ExternalProjectFinishedEvent; + } + +#if !TASKHOST + else if (eventType == typeof(ProjectEvaluationFinishedEventArgs)) + { + return LoggingEventType.ProjectEvaluationFinishedEvent; + } + else if (eventType == typeof(ProjectEvaluationStartedEventArgs)) + { + return LoggingEventType.ProjectEvaluationStartedEvent; + } + else if (eventType == typeof(ProjectImportedEventArgs)) + { + return LoggingEventType.ProjectImportedEvent; + } + else if (eventType == typeof(TargetSkippedEventArgs)) + { + return LoggingEventType.TargetSkipped; + } + else if (eventType == typeof(TelemetryEventArgs)) + { + return LoggingEventType.Telemetry; + } + else if (eventType == typeof(AssemblyLoadBuildEventArgs)) + { + return LoggingEventType.AssemblyLoadEvent; + } + else if (eventType == typeof(ExtendedCustomBuildEventArgs)) + { + return LoggingEventType.ExtendedCustomEvent; + } + else if (eventType == typeof(ExtendedBuildErrorEventArgs)) + { + return LoggingEventType.ExtendedBuildErrorEvent; + } + else if (eventType == typeof(ExtendedBuildWarningEventArgs)) + { + return LoggingEventType.ExtendedBuildWarningEvent; + } + else if (eventType == typeof(ExtendedBuildMessageEventArgs)) + { + return LoggingEventType.ExtendedBuildMessageEvent; + } + else if (eventType == typeof(CriticalBuildMessageEventArgs)) + { + return LoggingEventType.CriticalBuildMessage; + } + else if (eventType == typeof(ExtendedCriticalBuildMessageEventArgs)) + { + return LoggingEventType.ExtendedCriticalBuildMessageEvent; + } + else if (eventType == typeof(MetaprojectGeneratedEventArgs)) + { + return LoggingEventType.MetaprojectGenerated; + } + else if (eventType == typeof(PropertyInitialValueSetEventArgs)) + { + return LoggingEventType.PropertyInitialValueSet; + } + else if (eventType == typeof(PropertyReassignmentEventArgs)) + { + return LoggingEventType.PropertyReassignment; + } + else if (eventType == typeof(UninitializedPropertyReadEventArgs)) + { + return LoggingEventType.UninitializedPropertyRead; + } + else if (eventType == typeof(GeneratedFileUsedEventArgs)) + { + return LoggingEventType.GeneratedFileUsedEvent; + } + else if (eventType == typeof(BuildCheckResultMessage)) + { + return LoggingEventType.BuildCheckMessageEvent; + } + else if (eventType == typeof(BuildCheckResultWarning)) + { + return LoggingEventType.BuildCheckWarningEvent; + } + else if (eventType == typeof(BuildCheckResultError)) + { + return LoggingEventType.BuildCheckErrorEvent; + } + else if (eventType == typeof(BuildCheckAcquisitionEventArgs)) + { + return LoggingEventType.BuildCheckAcquisitionEvent; + } + else if (eventType == typeof(BuildCheckTracingEventArgs)) + { + return LoggingEventType.BuildCheckTracingEvent; + } + else if (eventType == typeof(EnvironmentVariableReadEventArgs)) + { + return LoggingEventType.EnvironmentVariableReadEvent; + } + else if (eventType == typeof(BuildSubmissionStartedEventArgs)) + { + return LoggingEventType.BuildSubmissionStartedEvent; + } + else if (eventType == typeof(BuildCanceledEventArgs)) + { + return LoggingEventType.BuildCanceledEvent; + } + else if (eventType == typeof(WorkerNodeTelemetryEventArgs)) + { + return LoggingEventType.WorkerNodeTelemetryEvent; + } +#endif + else if (eventType == typeof(TargetStartedEventArgs)) + { + return LoggingEventType.TargetStartedEvent; + } + else if (eventType == typeof(TargetFinishedEventArgs)) + { + return LoggingEventType.TargetFinishedEvent; + } + else if (eventType == typeof(TaskStartedEventArgs)) + { + return LoggingEventType.TaskStartedEvent; + } + else if (eventType == typeof(TaskFinishedEventArgs)) + { + return LoggingEventType.TaskFinishedEvent; + } + else if (eventType == typeof(BuildFinishedEventArgs)) + { + return LoggingEventType.BuildFinishedEvent; + } + else if (eventType == typeof(BuildStartedEventArgs)) + { + return LoggingEventType.BuildStartedEvent; + } + else if (eventType == typeof(BuildWarningEventArgs)) + { + return LoggingEventType.BuildWarningEvent; + } + else if (eventType == typeof(BuildErrorEventArgs)) + { + return LoggingEventType.BuildErrorEvent; + } + else if (eventType == typeof(ResponseFileUsedEventArgs)) + { + return LoggingEventType.ResponseFileUsedEvent; + } + else + { + return LoggingEventType.CustomEvent; + } + } + + /// + /// Given a build event that is presumed to be 2.0 (due to its lack of a "WriteToStream" method) and its + /// LoggingEventType, serialize that event to the stream. + /// + /// + /// Override to customize serialization per-assembly without relying on compile directives. + /// + protected virtual void WriteEventToStream(BuildEventArgs buildEvent, LoggingEventType eventType, ITranslator translator) + { + string message = buildEvent.Message; + string helpKeyword = buildEvent.HelpKeyword; + string senderName = buildEvent.SenderName; + + translator.Translate(ref message); + translator.Translate(ref helpKeyword); + translator.Translate(ref senderName); + + // It is essential that you translate in the same order during writing and reading + switch (eventType) + { + case LoggingEventType.BuildMessageEvent: + WriteBuildMessageEventToStream((BuildMessageEventArgs)buildEvent, translator); + break; + case LoggingEventType.ResponseFileUsedEvent: + WriteResponseFileUsedEventToStream((ResponseFileUsedEventArgs)buildEvent, translator); + break; + case LoggingEventType.TaskCommandLineEvent: + WriteTaskCommandLineEventToStream((TaskCommandLineEventArgs)buildEvent, translator); + break; + case LoggingEventType.BuildErrorEvent: + WriteBuildErrorEventToStream((BuildErrorEventArgs)buildEvent, translator); + break; + case LoggingEventType.BuildWarningEvent: + WriteBuildWarningEventToStream((BuildWarningEventArgs)buildEvent, translator); + break; + default: + ErrorUtilities.ThrowInternalError("Not Supported LoggingEventType {0}", eventType.ToString()); + break; + } + } + + #region Writes to Stream + + /// + /// Write Build Warning Log message into the translator + /// + private void WriteBuildWarningEventToStream(BuildWarningEventArgs buildWarningEventArgs, ITranslator translator) + { + string code = buildWarningEventArgs.Code; + translator.Translate(ref code); + + int columnNumber = buildWarningEventArgs.ColumnNumber; + translator.Translate(ref columnNumber); + + int endColumnNumber = buildWarningEventArgs.EndColumnNumber; + translator.Translate(ref endColumnNumber); + + int endLineNumber = buildWarningEventArgs.EndLineNumber; + translator.Translate(ref endLineNumber); + + string file = buildWarningEventArgs.File; + translator.Translate(ref file); + + int lineNumber = buildWarningEventArgs.LineNumber; + translator.Translate(ref lineNumber); + + string subCategory = buildWarningEventArgs.Subcategory; + translator.Translate(ref subCategory); + } + + /// + /// Write a Build Error message into the translator + /// + private void WriteBuildErrorEventToStream(BuildErrorEventArgs buildErrorEventArgs, ITranslator translator) + { + string code = buildErrorEventArgs.Code; + translator.Translate(ref code); + + int columnNumber = buildErrorEventArgs.ColumnNumber; + translator.Translate(ref columnNumber); + + int endColumnNumber = buildErrorEventArgs.EndColumnNumber; + translator.Translate(ref endColumnNumber); + + int endLineNumber = buildErrorEventArgs.EndLineNumber; + translator.Translate(ref endLineNumber); + + string file = buildErrorEventArgs.File; + translator.Translate(ref file); + + int lineNumber = buildErrorEventArgs.LineNumber; + translator.Translate(ref lineNumber); + + string subCategory = buildErrorEventArgs.Subcategory; + translator.Translate(ref subCategory); + } + + /// + /// Write Task Command Line log message into the translator + /// + private void WriteTaskCommandLineEventToStream(TaskCommandLineEventArgs taskCommandLineEventArgs, ITranslator translator) + { + MessageImportance importance = taskCommandLineEventArgs.Importance; + translator.TranslateEnum(ref importance, (int)importance); + + string commandLine = taskCommandLineEventArgs.CommandLine; + translator.Translate(ref commandLine); + + string taskName = taskCommandLineEventArgs.TaskName; + translator.Translate(ref taskName); + } + + /// + /// Write a "standard" Message Log the translator + /// + private void WriteBuildMessageEventToStream(BuildMessageEventArgs buildMessageEventArgs, ITranslator translator) + { + MessageImportance importance = buildMessageEventArgs.Importance; + translator.TranslateEnum(ref importance, (int)importance); + } + + /// + /// Write a response file used log message into the translator + /// + private void WriteResponseFileUsedEventToStream(ResponseFileUsedEventArgs responseFileUsedEventArgs, ITranslator translator) + { + string filePath = responseFileUsedEventArgs.ResponseFilePath; + + translator.Translate(ref filePath); + +#if !CLR2COMPATIBILITY + DateTime timestamp = responseFileUsedEventArgs.RawTimestamp; + translator.Translate(ref timestamp); +#endif + } + + #endregion + + #region Reads from Stream + + /// + /// Given a build event that is presumed to be 2.0 (due to its lack of a "ReadFromStream" method) and its + /// LoggingEventType, read that event from the stream. + /// + /// + /// Override to customize serialization per-assembly without relying on compile directives. + /// + protected virtual BuildEventArgs ReadEventFromStream(LoggingEventType eventType, ITranslator translator) + { + string message = null; + string helpKeyword = null; + string senderName = null; + + translator.Translate(ref message); + translator.Translate(ref helpKeyword); + translator.Translate(ref senderName); + + return eventType switch + { + LoggingEventType.TaskCommandLineEvent => ReadTaskCommandLineEventFromStream(translator, message, helpKeyword, senderName), + LoggingEventType.BuildErrorEvent => ReadTaskBuildErrorEventFromStream(translator, message, helpKeyword, senderName), + LoggingEventType.BuildMessageEvent => ReadBuildMessageEventFromStream(translator, message, helpKeyword, senderName), + LoggingEventType.ResponseFileUsedEvent => ReadResponseFileUsedEventFromStream(translator, message, helpKeyword, senderName), + LoggingEventType.BuildWarningEvent => ReadBuildWarningEventFromStream(translator, message, helpKeyword, senderName), + _ => null, + }; + } + + /// + /// Read and reconstruct a BuildWarningEventArgs from the stream + /// + private BuildWarningEventArgs ReadBuildWarningEventFromStream(ITranslator translator, string message, string helpKeyword, string senderName) + { + string code = null; + translator.Translate(ref code); + + int columnNumber = -1; + translator.Translate(ref columnNumber); + + int endColumnNumber = -1; + translator.Translate(ref endColumnNumber); + + int endLineNumber = -1; + translator.Translate(ref endLineNumber); + + string file = null; + translator.Translate(ref file); + + int lineNumber = -1; + translator.Translate(ref lineNumber); + + string subCategory = null; + translator.Translate(ref subCategory); + + BuildWarningEventArgs buildEvent = + new BuildWarningEventArgs( + subCategory, + code, + file, + lineNumber, + columnNumber, + endLineNumber, + endColumnNumber, + message, + helpKeyword, + senderName); + + return buildEvent; + } + + /// + /// Read and reconstruct a BuildErrorEventArgs from the stream + /// + private BuildErrorEventArgs ReadTaskBuildErrorEventFromStream(ITranslator translator, string message, string helpKeyword, string senderName) + { + string code = null; + translator.Translate(ref code); + + int columnNumber = -1; + translator.Translate(ref columnNumber); + + int endColumnNumber = -1; + translator.Translate(ref endColumnNumber); + + int endLineNumber = -1; + translator.Translate(ref endLineNumber); + + string file = null; + translator.Translate(ref file); + + int lineNumber = -1; + translator.Translate(ref lineNumber); + + string subCategory = null; + translator.Translate(ref subCategory); + + BuildErrorEventArgs buildEvent = + new BuildErrorEventArgs( + subCategory, + code, + file, + lineNumber, + columnNumber, + endLineNumber, + endColumnNumber, + message, + helpKeyword, + senderName); + + return buildEvent; + } + + /// + /// Read and reconstruct a TaskCommandLineEventArgs from the stream + /// + private TaskCommandLineEventArgs ReadTaskCommandLineEventFromStream(ITranslator translator, string message, string helpKeyword, string senderName) + { + MessageImportance importance = MessageImportance.Normal; + translator.TranslateEnum(ref importance, (int)importance); + + string commandLine = null; + translator.Translate(ref commandLine); + + string taskName = null; + translator.Translate(ref taskName); + + TaskCommandLineEventArgs buildEvent = new TaskCommandLineEventArgs(commandLine, taskName, importance); + return buildEvent; + } + + /// + /// Read and reconstruct a BuildMessageEventArgs from the stream + /// + private BuildMessageEventArgs ReadBuildMessageEventFromStream(ITranslator translator, string message, string helpKeyword, string senderName) + { + MessageImportance importance = MessageImportance.Normal; + + translator.TranslateEnum(ref importance, (int)importance); + + BuildMessageEventArgs buildEvent = new BuildMessageEventArgs(message, helpKeyword, senderName, importance); + return buildEvent; + } + + private ResponseFileUsedEventArgs ReadResponseFileUsedEventFromStream(ITranslator translator, string message, string helpKeyword, string senderName) + { + string responseFilePath = String.Empty; + translator.Translate(ref responseFilePath); + ResponseFileUsedEventArgs buildEvent = new ResponseFileUsedEventArgs(responseFilePath); + +#if !CLR2COMPATIBILITY + DateTime timestamp = default; + translator.Translate(ref timestamp); + buildEvent.RawTimestamp = timestamp; +#endif + + return buildEvent; + } + + #endregion + + #endregion + } +} diff --git a/src/MSBuildTaskHost/MSBuild/MSBuild.ico b/src/MSBuildTaskHost/MSBuild/MSBuild.ico new file mode 100644 index 0000000000000000000000000000000000000000..f70202a070eeb17080734396fa8c9fe35073384f GIT binary patch literal 92547 zcmb5WgLfv+^9K6Hwrz8>v2EK8{4*RXOry4w(U2`&FA;M_Yb&(bEancG-{r% zr@E_W000mG4B)>F1V9WR2nPVtzOMxZ|0h!m0{~6m_jq~#Crfez0P7O~05kLdeQOIwXPar%02o_!Q z(II*JTG#l_+9H#`7j{*2MH_B=&+@-t^vvm7`=0Aq=fTNN7XSZ0(wG|m5d2QDHi(_TGY=7nZ;7hK~J zYYqALoL1b_;nJUK-Ypz-LV2dzvZqx@ATvRQxHUtClP_usSaqNLtcQ>BF6 zQB%%+yBYyQ<}0qe1_MO2H&;!yq!oc?`bI=lfa|~;Q(*)-NsEfArPA(#*g_Hc-zyk= zrCM`#OW~+wPzNyR=l<5?<>cdg+*N;MydA4(zR~Jcit$A;m2R(s`rX15B}ATirzR|> zxb_c9Lqpx~vc`SO!vvL z=np1URO6u~0hX(R0>;5&ceWy}>)F0FX49Yy?*G0C%2%9#0}vB53L^j*s%J9yYTBv* z24!Gjkt=uubhCM|efeoX^^kXp1YV!cuXOqn@_CC&ppL8wqLaOnF#Ak;!><7n2tZW;Xj=fKXnZ=`T^ z{%gHr-1KapOjb@G*omGMwSSHAXUFeT_miyzG(&npwc&#dwXWj4F9ta}Km&W%a|lbC z=F+%ErZ_*RBOJT`cLs90=D&`A)mpo46!nw*hJge<=eR0|ifOwxp8*k^xJl(Uy`QNo z<5voU;*$nQnm~(0&<_iT6b4AABb;FTg;LjG+U6jE<*@%REA2SmMxVCU%O2XJi{l*%IH3 zr4^+a!GG#)yc>3BBotiaC+;VRS%QJB{zaheBWL4}?61wIQgllL%(!v(&s&&8Q$zX9 z+Ld&3B6tIm6Cr%|>ul~v`Nn%`U*GwDolO{}8Q*`oCEX2Q0R0By-Z8uq#tvW=!qfCx zP&omLiM3Ao03AL_Cf$faiVNP2&n_7y!C+E)pUyh}3(_QhVI|*u%S@>ihvbxRsOURWT5AMCo2&Fm#fG9XSKW2LUkwYdN=r*C)D=TZlk)%jV`TuTDpr2(% zO{E4#SipU34nzU3bgnmCFDT?zimR=^b%sD6@9%x8xVS^8mO-)viETv&f;G4&bEP|H$oYatFIZCmrq@If4V?2PG7G7GV)=y$MmD65JqB4Pj3x5Yz5qdu*ncv4%}q@ z79B$@Sb10k0~s7H&=2P&4RrFB5w`lb!!F*zNHMU}E2VwgLW2gueDwW(7wRha^vf^m zOipRQmsg9E-OnOfE7!addNox8DNc0dB9JA$!8az$F8iV3@>sz5Y&22)F###X3f$mk z#b7IK=#2PUEL1xt7CL;C3Gb9fL$_&>px7#&4?59MWmy$SbPu&QvA)TO&bKA!y+gY$ zl)KY^<`&@lO5c!AyKNAr0rJnan*kQHay)3EnswjCPRy$o?zgb+b73&&8EPWzgKEUz zh);r^?jVETqHv=snXy;>RW;ulp5uLTKPLKZmC4k$wv{RmL%hlaiRJ*2FV4(N90xub zIwDN@^GfW}fbvcWw-F!2ug2$Ya<7v19=v%sD@YcGi~W|c>nsRPz*W2$RZvtF5W(se zX<pR~uf?~K-ZLW~-~QF-sLcQ9@24=Et?jo==OviT(kP4G&z(Zm z>v`Mn!07`B;_2L~E!Mb0hn6!^QD=t3_2Eg7L7R&{-7a$plR)S#6 z--UwkKSz?iY2^8O4Jzmbe;3D8^xvDc!L#Z5g~3T~Q~?rv1;3Ln{7PTnt~%A7PTjVG zDPT}}(N`?rx%+j~#dFgua-l}={dC>oN?ZvEHNH;H@}p%O*#J2TnuD?x2@hasw#@sZ zH;M?jK^4DK_qko9*;~(z@@f4agHoi8^>z5TcX4-P1>rD*W0C(Zu&Ob>D<5(J`1tq8 zh)wx;tD$9h*tUg~EZGd^l$wYQ?@GcjV!ER*=VHW5;NbMaMb_0Wq%|UfQF4V)g&@D; z{z>CCB#zv;Nsgc0{fy@i>QI@|$bOg7`uc90hF0(UOf(cOJP~%`roqTRIuk+?UI6q! zR6SM!;~bdjw47p-gEcJPovh}GK7dgZ;&B#&^tQ?>5ph>7RC@JHryaz4#uv(s)Rug5 zmp7nOBy=^Fy*r=Q>DQ6G-om^8vk~v>Nk-(zcK&~gR5tJ9@AKC#y~ct^@(6;fF+YHO&>2L<|!#2y_vmMpIy8%+E>~XwmG34moZV<1olD zvG(skh(CMD8zg`(@?UlNYS>%!yWfE%3uXDTs;U$uNBj z65756CoxJuef)WVn$uv(FY2X8~Ak${q&OAeU9Ba=OY5#c|mV= z!v1bR^kNl@uO`BXH()*?lKB0WD;4V#vjE?2`W2#-m-KVa3bhRtI@iYrG4E@i%;WA) zN95ma(Qleie!b#gA{69l$C?k$OtTL=#+x+rGto6jkk1f6l>T1V)z1h7xUT4;1AZy! zEh&5$Y$t~kaW^0;SSbJNl0^qZ0{^ZD^tcH=tj$v@&$dBn2NyDQ{=G*(Ahf)EvfGVs5mrgZSXPb0F15EL*-J?+v$m2_(f+o9+hJ_{NhT1&R;Uu~V7ES=^- zINDB)&@lu=OyeQp;Xjryn$-)V9+B&;9FTzc&(|D*RssJ{lP(;Kr}JZh72P%V^>6QIN5SS0I*XXz zN&Yyr;kLn~t#I3VBH$ist+M#VV*w`(OaY}jDFk*=!P*}5H+D#&WYX7_mH2*^Ee~cY z%I~RZUO5h3#OOt^CFLji6rZ^%%Fr*EDCca%PUX0+P`J!9=l?v}O0$B?C zmwiogrNS?5itqUe|QM; zndkrf5!&~PH22^)WY`l0isW;6YVg{ku{ImN^|d4_UA8!UL=)8B9xKObk8{xZv3rh9{;B_ zDE-+-oq}#!jQ)$=SNDmy(|7X*LzL{WeH~U;fYV_Z_W}ht~PG`6V^*Of3LXwP*j~eHxs>dEoq$5UKoen>bRcxN5E>RNXIh`hd3pjs@i{5$+U=}zXVf{0cczkBBH>&FVaZyz;c zIq}Gp$u7^Lyg`yvUp-hXBdwD_~B^2pYI7QFBiB|ScU>==}=BD_Y#gTa9IMr&+`KQ#$OMH z>mSHm?N5|bw-_K#Q%g%9eIHW-y!EM@K@8#lhX%v zK}fBXiqLXF9KFvz-Pqycvp}yUL>{!E&9?#=qP(Gkr--(#q6}qd0_^=SHwwfL2_rV< zSGkT$q87&nJV-`yLovVo7?5ZYDaHC0XFW`LKt$dO$uv85?uDbkFJh%=Hu7KZMtgd2 zKSa~V6wMHy&;y^$cPhr__M-jomP&)(i@t3n@L~=)7BA?4RLh_1FE^#`0@oS>`too0 zHCSbF@fpB59+HEI$>^caTcjW&3jY>Y-#k%>1p?VNU3nc0b;fMe*}52C%ecozK!Vly zuE(j|v0LFg$BFye%(66IFwJsB`)B+NUA4)_sQxA+UV#5Tc4+K9)~>Pf*BoK@E`ojk z)n*q*C!K~GI_E#b7n!f~yxzmz*sH1NRJm{4gEV(-UsigBl8BT>MpDORL}jpuwNyF5 zZQKfZJL;C;moU^EMHY#V0&o(JQ@15|&zIi`v@SWb70fU-g#PiYNG~MU5@Y7n{!8oJ)y;m6;k-lgvBoq zS)Ty6K8asV-DDJ$FFnpUuXfwqbzJRHe(3xEX+HjQ2jv_4?5AB!ZL)H5yJ+z$Lt{yM2AXx4UzfZDd^(L#feR*I+iwa_t8h~P>Th^z@^h)@wo;sP5#fr z(<)gTb5H@k?@zl=>-PSx(0;U8sYENRu>Ud7v|{O%J+7yL_DO2OGIJJVfb=Vj?aX9; zO33(6<#8WDiSj{rdA}At8hJ`m#z`)qx_>MREa<20dzu&&>FPc!e9q~EIFRnhzPW<0 zP})&>qu7!$zFzaydcAp+&gWCbz>Z2M*_v@_sK10E5HL_KxiX3orfO?fE~B;_Xba*i}J% z-Uq&&-wmCzV@Pv_u1l6`B|=FbGaQ60&m-W(Pf0&K)=siKJR+}FTabG29u($##^&s zm_Y;;z_y>mEumUZTVt!}n-VRYHhFPt;p~dWy7$nrfVvB_bVbotOVMCvha;qmqQEuyyJ5E7pUzW>uL)wdG4nqHhb_TzYN_Q9X)DsA#n zR}wY@7=h}%*92+ab;#v!>gt^?lRC{=lai`kvH+QLEZun zpF~?tC~dt*usb^8a|Bj8JsiWDhMH}H)@)+P>E)pFt=(Gb#}dM}5~!fVbB^8{TX)Ck#I2{9emml%1p-R1=M+Io2@#TL*OyG|ju5yXvIFtb zFutdY3Oz5y_CYLcY@s^#J+4Jv4W7|ETi+gug#V-V_ERQp!0OQbJIJf#%hd$G`@w1` z*R?g1_cq2ZOTxk8LP(T)YcA9BL#K8b$dNi}3e*BpiM{@hP^c=6U%_v|_(7H6OiE

_IUd@%TZ$y6f~qh&pJB$Snh6@YoXsjjlCKzWn5?`;(O?&dNP>u*8ll` zHpo2Us2=?v?|3!P`0c?>`*GQ$BUcfyfVoU8*wM9X&WEOdo#dnnNoab)3Av|^C(m3- z2jv8M`&6$@24? z?~OhUCt9eNWkKNQH;NKI01%03-F+eqN%nyL#+~W(D|LAo!9(vFX5^&oHLxP^r1Qqt z3nR(RFtpfIiX$E$4^KVIF-Yx{u>JX8B%=#~lQ%jf3v+my!XCrVbD@8mn|nRF)Yig`jg z8dQ62O_amW)vZxm{KM_fE4w> zFcmwcm^psz=Zj!1sTr!U-c90}XqeGhTR@+E!kEfIfU9k|gAoJG3&BGE#&dKYf6&+U zPl5KRvc?9pu{Z*c&2#-scvS!N1D!lYiBhTlzbyR99poVLxw0bs z6PAW&-);HLkOl%x-!+T9nhZ<%JJtW%)PE?`JKqHhC>-kgw1}ZLoW}2e@`gu6BbU&W zQ)4txozBr8+3O&QAtIJnc+uWbG;j(+(7{*<(aMj7_mpEvOB0PGuB7KTS?Zb)bYp~` zDjs4{g7PNFfBzyynQ%x**7}TcH^dEj{jhpQDIQ69VrM>IZAY8aXMApdO*?-8>-&@k zR(%~3*}Xvz0D-F+5=6e}-zpqBoJ!~D8kk{|;DWLZy@)b<*9fMf%=lcd) zVY9D7St+sill*tw4`KT5aeQDFdZ}1gvfWJRAr0b+y`2YHb1Tz$Qyx^IS*_u@cScj! zGvuwp?b39gXfnTIBbQ}#2VN9L>@2~^$$Fu6Y+8$)QnetVV{z)`D0psHCT|sJImF|3 z(SO;t9hZ?I_qw;8t~Wimyh{u&FeS!V+0vn!*v0=^!@-5zoPV1JNbJ?Ug9WN z5P5qMJNgR6S*DzqdbfvPDnFoI-`EX!*ava`%bg}g+3PxwWe4UB1T@9Pl7|>jf6dl) zyJg__^Xj7~_~jL>&R=ToRy#d46$5w^F5vUG9byQC*&USgUKr)mr`A@Dj&Yekx}@ht z0N&qH_XCd#%Aud-Ai>A3tX@kyNe>rj1mb}>59+*i2Z8Se+I zG#HS8zw-$q>hOR-1IEqMtUnPwkpxkOd@}wIS^)gx%SZ+%`W;3<4}HqtDm{q_3Q6io zGAu{Ll9@p=Y@*5oJapI%mnplC{<=Q2uf65%hSS%;jt(x~x051|-B7GnQ_#fL{smBn zw^!l2NInl@PV)&Auq8Si2sL$)Ep!r9K0B`8n+~V}36h4ZtzK87 zP=vkCn`jlIjeO_PdA|knEBpQl3}IP^@oHO4^>)2a_uK%8*Fe)X^69YDTCdUH6i6sa zC|Zr^1g_uF*k~$|!t-yQ5_J-Mzjhux${*-(Oy|`EsX?C-PrK#h-`TbVMXT%5C?w_c zb-!orTZGIK#`0i5@gxlV ze$5%1o0~6Q(bD_z*LVNM#t9jj%j;<6kz?k8kAx3_Hsx$NR}n4pZ2te&0>txwb)EM< zI@2@u8*+9h=AACDaXia_TtzkQ3zCXL*I4^|GFvzv4OeCxU>kET*&zD%s&Ll`fJY09 z=GqTFZuWq0WsnxwY~`iJj*&<_F)F){OQqZtCVJc284&!-`Z=c;7fNTu5Av>&5PMBZ zFReOZ?~x#qb6x*BO&d2P9D-)H&CqolXy8xvfABrc>oJ@8)rm7XISI>dvf{uAn9V`S z_7rMHJa-Kh$g5)E{n@655`4uCAAXH1vOHD<0Doi5AJ<%OeQcLeLO1A%T3Ou_TNl3q z#J{qUwtEzeY)k|)GPUlSEhZwvuo+|KWD9zsX5=SN7kV!l2$Q@TSbR7nyaW(=|gV%?1??@6ujZCv2B%=;2^Y&PP3J^DX8zcKll zOx=LNF?HVzhCPYs7&?M*esmjpR$#il*lFytz_I>92`Eob4GbK#vn;*B9>X3j0u*-? zWVM5;j%vUzA{ykw+)?epAxAP+wl@3_|7KN@Y_5i==qcMJ8V>k>x;`IWjq|gFpba{y za|PbrLW`4;i;F3viuASS<7w!!)Wo5x;3euxkyZktC1T3ING~K0L|=rPcVUDJ^gMFM ziNPAda{#t#TOaXuV?9J&aM!+HSq;|DrG7fs)Aw@%@neME@XpT5pJL^7Q(`yi)64P7 zx2%T|?OErppv_Nr(q7NB`lPK%pwxX^nUUNzX)S~L-iLeQW{P`{HTHkhvh-s49-n$b z&ceWUHdfW9^Kh)zFu)I)_?@2t~4xm zD9{aL>VG%TXcHdTiNzHsCE}II3!95Z_qK8n54-}bp=1BjJ_VutpgJJx`u6p1P01o$A3!rM4uKRmO~YnaN}nro z7|3G^EzO29VG@;Hp3gyodGd`cEI|G3fbN&Slce^Z_OknORYLpNB%M`j3ekuOwj_wMyqDNKibW$oPNpCKT zGhL3`V^?b|!2U!s35T<#3Ri3AZ+Ls+w>vN%p$a0qoObyx0NaSpnV^L*)>@nCd(n&Q z1m_)p{;P`_nXq1Z8d<(|J_Nn6t7sD0ASa^Wmtfxhc%?dBu4fpBNV>hMD<1>6b$UzCtFChg#Y7=&WkhF|o2e|3=Mon%C)=ialOD(c z<+kf&zhg&oFx3;YjCVr*F$y^-KUdc5lA3)0&zP)Z61MVt1zkA7XLAU~2R8)6hF}8~ ze}Cu&TXpMOHHYjKcOq>|>%JcRG4>%01T+)!M-Cgl-%nfgL@)e_F~h}hJv_;0{gp{~ z56jjWxUc`Q&D~17rHjVEA^m5h=cm>Ibl3 zp;-#NGq&u0su3uw%-R3@nM`ZVT-5N$xbH`yIr9++*>~sPL?glpxkHL&$pV5Yd{yn1 zwRcSU1#(z0B_opUb=64E*nAi${S~-k`w^J>UVi=e(YvX1li7f-Ifr|)OY**77xc?Y zSISOVE-(tND*;L=q>-2w8lXH6NCd2x8szoVtx??Ps{aei2%e=$`Fi*sr;S4S?GwD% z^8>FKb}|2t)}K~?)s;iH%RMhS+AUs++{RSKc%Mp|GXuGJyG98<3PMLTWwfgQj?&TX zD*+f1EgOxSYp&+BS*Loln24rXVYs9G>|}ga?+Ka9mLur_rb`*#p(FQTT;5g~Sn2Ik z<8N6u9V|r&>ij98kaT$my`7k?hQqeLH=t4jri_8KRjn&gD@!VmsRhDAi3jW+WChR0 zV%2(y{T!YsT`~i=F6v!jMreh_Hza`W!%L2H-@ZP!mq3*=`Q7nEGP0pCtXVLscsRyv z1!5&jS)U`xz0!TXAf~6>=9*5Ha;ZT-nC5v6>c{Oz29phhlW5)Sn6nFTR{EM2F?mW! z95orKA2g^5q%fzgm{#|{(>$_~0AXMwzzsl|KK99BYKTwA3Y&x11IgRf)$ayOj8de* zq6%Y*-$}?KV-#{ec2g-5u3h$H(>urwJO)X@CFHOi#bQB;8Rvoixgk&P&;J? zs6)$%D2ZU)8$H z?J`b8R}ztpCYG}T{}+x96&T?ZvZEME_x|@Y9{{hJ;s?{&`#;!VvW7Z{sm+H>y_d4S zwaA&Gv_w!rboC=NfQW{+ElpXD(vg#6I;ez3haWY*7WzB1rQqg?l5{Q_; zGCuz_A7)E=CLi4m!86Qq>zt-+OAvuSa|~h35<-HXt4E~s`$ynva=q*@WR7+I8o{-X zn5nJTn@Em8&_rijXDnRv|I*3r3Kin+#wYSY{2QtPh8R{(Vvi|3l0q5@4eq{c2jM~V zFIqPcpRHWqDG4Q@n0>E~w%K-*0U6|Jvr$LjChNLi-2twV&HR+*p&IL$=AdZM*ILMx zpaLBm@}dZ#Z(S2((8GyuO59Lfs;Q$K3N3Bv+|r9KPH&%?M}bLzuI&c5ncV}~w%^Fr zjl_ydpXB%$N>ktJ;SI;|*;Nku_e)S)y}dcz?jUOBNQ(LgF@i2@CbTeGFDjxah9>a8$UgbfX5WhZssl(SWko)MUR82H=vPR6-?J}CP! z0nQglpI11AeL4f~R+=^f_aS?l*1Vlw>Ndj!u>2(IJT@46?6?cq1Mj7B!w_}}>TF|s z@euQW6)|HF2{&IO3>sRuxOtxKJbopB11~|3Qs9MPzxmWAOa<1@`1SNSF$4@AO-id;(deW=J7?BmaCweBhY<@PDB9c&7BnLs9Ib zcSHXnu}j3TQC@r4CrheqSziP6U3v^~gfo!_$BlqKnqzlp@#zZcU7+GE|M_A z!J3*HVxBctz+3}EI<41bEqSof(AsZ3e$FIRaR(ZJ&W&W0iCUHYbzxWR4T5Z&DZZ~8 zBLXhFh!|@0oK$TAPg>%sf%3<lt-!4C^R2Q+7mi7^ulK=zG^;W+nd6gbpbhnBkz5KV91mO;>gOU0~YD(?n-uZJL zXf%|Pkr)e#viY33+5SFsy{ZZfaQrFk&#PnhyzKSqOJz?t@^#+H?xuk|aekJ1<*Z;~ zL`MQBlp5A9q^OEg4pN&DV86gAmR`dcpPU#@7cs)ckaWnjC|LYCI-_FHiy)roXP_))i*L$8VoIABf`N${3SwUAPIPZ;#u!||7H92^Hl48EF(E6UAld- zTHJ7sPqR2g1~R^98;V_^GYFsijNso!vU$P2K_0^ zyT3BJ#u*lgy>@dB?WXl`;}fLF-`AgD^wS8goGx=Brls+iMQ z!G@ZD^o$IUSmqKW!!0dYDZqn(R7Nhl$l`@g-3Yy|ty5#_OrJuy$0TD^fR~YD#P?j$ zDDV_y%}CG_V3F-X7Od`uu5FR9&ylTbO@GZDWm8-7G;$5H4BHU4WXaPBugb5j&{!u6 z+ap8oqtmEnds?P`PqR1YA*WSur$$ebNQ@CyIoX@#zS{?!aX%Zi;ptWW&5$CDMKRBE z@6nu!AvJ?xs!zPOy}~jo+fQ%Gdlt!80lizpR9~A)zd)6VsZb)9SffyJeUMQ>a``ZD zosNMVO7c3`Jiq1s*;T)?_W)P*?L`(REF!cqrhM}JybdR@$5Pcz%pMI}IqaBYwhM?T zI>By~uo(Wi=skbr;f@9B^=wRr$<6{0MpW zy{NUGL^?pkysL$;$?Cw0@v|j@&7dxKq1A z5D^MerC@<>J?;jNToYRB5a75P^0=xO*wio^_8;*^Mm}hp*qQ9=P$LTwQbJq0;?y>- zbm~}?{N_d?0W`-x!%%Ws-=yb)qep_1J@&huv!P6yGb?8EH$=TIg2CIqTU4P$sq`tl z9N@>%;fhHa$WtczR{aqiTd+bEZaWgx+q{A&jTi=tpwlE5uA7ER4Dp0hwv?W;a#0Y^ z8Abq_TsG)Y(4zUHOU9Fsl6JY-4+*@c_LWi8cmRQdg{eFfrx(YU8?jJ z4Ds3`wH~@ITBg1j7?5U?a>gd(-Oh1Og{y8I{B-V@P$|+zo)Re!3bvyh2Ni+?mC8jp z&(N%SNS28KUh6vcDAebH&=GZmmp^9NS=P~{4mP^3w7U__g5cUInaA7*j=!r}P&2Rc zBzcwLM2V&DCWc-DQ)G4|L${+yrB{C+@ytIKdY3lL4d4|S9`}YTKM|VED<_20tz*Lh zF1pM(KBEo%p%8nYsea@tL+B=bsx*X38qY8y3P(*(ns1J{XE@zYYW zRzf#$wIKyhsR)#m!PNn*6Hv=c%A)wr^GR>funX6dncm4X%Z8e$rGp3w?2rN_c1>qs zPedR6_VRuyAh>v~w>CfvVpbP#u!3{C0Vp;8K7}kXR>w+p+Uoc!zNmQjuG9K69e=O! z(G8_@hOaA*nSij!IlU#veuR0NZd0ZRPn;C_)fih^jJ+IU?!y~AuMau+n^#xzOsqCB zK^arqu8v(w6w6$X?s68f@Lg82`S#sV?T5RSTCTsjm^u?3hLITxDEXgBR-8}ORF>7W z4#Y0c1=$Z$n2wt6A^!9jFShWR)?xY9?8!|N&B40%A0@=v2@5xf1 zf|1)Yjf02#^goE!AMCZL2TsuJ3|^lkb1j1bMV=tEq4?Suk^7D{&YdtA;Go#?aLkzC z`4-5T-O%QOPs&3?Y*<2Gqr#HcpFiiYV(DoisaA^<54x$$bWA)WBkoEHVN-FHDr$O9 zLh|nuR>`zSO-U{YUysKK{E#5$|1+B5bkC>>eRBqkhCy|l!Nf& z#TQo8nKBLCp2HnM$cIpsD01Rc>{zSxReZX;rmre&W|=MK8pkDe&_o7T6NYM!5BF70 zMnC+JSv$f^81-0T_)VV3g`Qug_w_(#FG)kemnpHQSfPN=7H=z)9=ZTFWc%JoRb2Ru;tQ1_zArB&wcD2{QG$VU09o zj!MYdy7@7$gRdlMvq-K-rIxz>CL)~ZGix5z3Z4gK)v=Z$Ryen=6bq6JA5seD)^N&> z{72z%Y= zriVih%omo~`W3cB4(S)UXe(d6OI%I8LWqNV&EPD}costN{F>pyr(iAuWGP{3KC{dW zZrnMfZQG_gg!nX;H2Ig*8l!eyT@M)TaMgE&iXgBk9Xc4?pY;mysn{V_T_>#1O+5sm z!kDv&E~mWp&!sdMt2}z(b^EDVxYNAhbA|CVFx912Ypsw2BJILTza5#r&;}oi|M(OV zAbOh@RdN3Vcvo-5B^y{WxDfa`lI(ZDMT>lg=jI2b@hm1y4$l;v@F`$$VCS%Uxn)Vf zJTF>Wj*mCw_0l1Gro9$Z!k2eJXekDr3*bk=z5*S4kou#toMEh?LvCH;h?{kEm2dju zH;yM~(s$)lkG34mGBpyTI?5a*!lF-8>TPfbb#&1m`SB{KB_k-Y8s4s09BxxOqc9sg zVW)HO4IK+ob?nwlRaCB~0?I1}A^)kLC6Pz(sc5(q%(p0JdBZwq3gF4236HPV5fP_W zaegI8h$S(@IR}m{)!{_8W3Xx$q{>WHI#X#JE;I@g2$VeLUj#h8x7L=3ysRZr+kX`R zD#=3pU}l4t|3#40&J7Y+Tvg{jh=Z^ne#&7tCwp(GYYW(s26##K(XQ3DH zBx*Vb*N{_DO)V)Co(H_WmAq0G*6NIc!Mf}|_2yo8U=L(-)s;$j-*di_Oj4CJPkOw< z1|8P&<9H@!4;kdK2^YlYC{&Ht{eeaHrOJ~jq}G{G{w1y`grctLozp!#^U9U{ZAo&F zk%jq9dR0E`^TXRcRV>>*l%>=iG>!rO1T%0jnmnWcyHf1T)d^ePBA|vrYFr46veUD; z#@v8=Sbn;8U5%6Nr20#L2v@`_ke? zKv4L+ozyUo!Yt1D_OnU7I}f)g`Fc(!Jfy`z=GTPNlSuqDe!sgWXPNo|sVNfT53SGK zMm0u{K!*z(<|YgXwFG9hx~C|@`01ug`?Iw)XixQr2LSX}xioY-Cvp<2f>U!b7*iI5 z^B5MfKSFz8;iM4~LnavPRByQ)LgZ11bnVP`Wur@+gYcf@Oy(Zth>OV&EC=z`4lG$M zTTdb1Q%d`#WFsrohrQw*OLBRhQqc!(FSt^Fk@!e+4GQn3Mq{Ze;2R|FR+c^@7w6BZ zO{VS;dreyAOj+z+qWS}3HW1>7YcCD*)@M)prG#7(N1|yW98($&K@{V|8!9jR$c`S; zPdvecpc2rf&r92_ROPf(83hZEEaf7w8v(0M2gWV}y!}ylo2;e0tN&L)Ts{l(zE{7luEXgR44IOmB!b?!%grN=51*I%T|0K zge6KJg1Bw))Y4PK7w8N+j?*7?NG+m5KBM*v?{oR#vShp5Grx8$(I!5#;GgWF-=*Gj z<=F|hyZrn<@Dwb=T@*PyWUsO@xa8(Ue*{061tg(ZZwl#Id zb9R%8$MV(2es+SLBmzvGuxsI3ambR||5V4gvDlt+$#rr4NHu#}p9^fecDpzFviUgI za@v*sJ12!kC4*w9;5C&4H?2UVH$ z(;yu3Tmt;tjqQFkF0P9~{mEe-rJS}(6l41$Z4X(h2~idXKrjNTMXY`rqSbrYMMPt1 zZ+P6Z_gr}~E6Vx-a3Cql1&jxZhbipIxYb=mwdYD%PjEwxr%`ff#qCYhv^aT&S5<=X z=Z=43_%EE~Bf587=!P(lLHdKtY6;~5MD>s90j|5|AP|8!) zeT67#*9o~t)U5)qHE}3rU`Zjc){yF8RTQ_ zr9uA?n3mIlJXJ8-x%x87hS790XhVZOi9mb@a!;`sr+_#Ea&@bv-$BQGYBdRwU0Uc5 zJJ;P^lO5wmPc{~&hwxB38+z?-n1WH++MxJS$`eLL1jnO;%39qE25mL;D~b+$=o6^o z4%`o$QBzPg47p?oMz|`v0u*?DBd^Vd+J2~%Yr4>@9^9&zamP=ImSiUt?%(@X&vE;NKw5(tC~PL^2U;$}uFw>!%dLDhb6w5Hs}m=x z9AIVYhhb({S#IFg&k`SN?RN;_`2!HL8il)SnQdJX3$qqpOHt8EXG}JeTA{}=fh>~R zG*1op%*Oc<=NkH4oy3f+GKUX@#ot@vua8Qmc*!9hgoG$-CyrwnB}q&x?Q(EA0zVU+ zoke~GLc?7v!R0~54G4s1i7RRp0E(|oTB5WJe*}GKyy?vDga^%mGA5W*({b*kalJuo718O@7Yln z?5vm{qYzN`4@FcG+vF=6JZmJY0a;snsdOo74|X2l2bJWYg5v_-L?0*%P<4j-bS2T^ z{FwlEtleX@o$~FquZ2$Rx=mfGeBl%+OQE(Z6=}+ zcL|YmLg0BirJZ%?%(JF)Qwbz~pyvmOyhXL|A1~R@?Z`k+VU3+U(G>$)@s{J9ID(+& z-40AVN6e}#`t>dibij|}7ls{C?_4Rzf7lDrxM=#D&xaAkHRoFwOorHBX;8y}2&SN% z3L(ZDCmb*qsI4w>HoLH9e<#2kBq9ozZ}BX9J}M_Gh4hWoO~-b>iXb`VXPE z+S<1xtAN@=6ALMlDpPD=gL7HxicN?pT81-V9%<80*EmJnx%(s?b3Lm3#V|jjl2fZ{ z>Rh#2a}Id4VAwiJO{G70?RsQ%P^F~1NDRe5qNvBTxi+}wD^CC{#&_Wlwr2h#X#FGBM0b);i`X zre=7!G4+qBnAN@RL9Ms>jRrvt6j`(7^?@=i?pVt(0P4Tps60Z$-3LHdZPvnfbZVk@ z6QQ(C10*m+o8^sK4;`i#wA6LIU=O$<$VlJ%J$N6zzvZC=zum*aQ_ql*_TZI}09U2A zotgy60`=t54SPy*`d0>SkeKZH<_hbm{bnz`Rw2N>7(d>sPU-q7?nc(=Go1bKoL#Mf zV?b%RW7!5nvLY!(#im-VgCY$c$rXHMGp3^Q_j-9?8_-=_!#cKYb_B8J#mx*;-K@JC z#wk9y4(_ko@Rid5{tFq$UhA#f&W%4+8tE^#rzJh9@F(&1_ey26SK(EKKIdO#>+b9; zy+s3YIYH*y%zQNoVY)lTHs3!A_}_m4#BB(15sB%rbgFBG84Ymv_8QL&*&1)|0toJg z!}5a$1)rrWI_|}zJ^lgapJ334QY5FpG-Z}KesSzDZ$B3B<^Hiht%&?*F@|EqRHIqt zVA2kvg+iWzBZH%Ytq5{1=Hj`RNjfgg9(eeQdC4AD&?5w$n+y@r4mMQ zzE)OLS5}4s9V&&A=W=6tzS48ZbE4hnqy@fE z9{Kqtyk~7`73vED&4kwVK}#gTf5{Dj-J62OC0pY7w7#t7jn)rNi>twmLG1~U-7iQ@ z_CKv}>!XH1Ye3?16GQuD1X{5bxi?7|74e&e8V!N&k(O5ce)<0*qB3&6(j1o?N}Okw zv?T>;zNfYBQy9v?+@CTpt&Ouy<7m|x%$P&TMqzAa6!DhMYF(#W!uQX70?=JU!BGXO zD(fOE6CT(4(T`>P;|VUbutgz>A_fyxb(3X}Mdypj;V`rLym~Cs)&$ff+AWNd ze5;j8YR40ZVwN>r2NUXk(fpUNHy@M`Dzz|wTMLyf8_qz*3?{`8{m;s$BX{9z#kop8 z0Tf5y6s(B0tx6k8%ICl3+zT7dN;hq(wjSn3=zS@VK9rk8W^9_a+~1@yqcKe_j;EN8 zw9olW!1SnV{Y>Y(_3Q6(ETiA>`>)uuIwxe^%a{;A*OJs`EQHMQlEFl-DWq8w|C<(R zG6`jkA>EVjw$&xPZO&wO4*5qJh>)l+125OZAS znF^;?a7lLs%Ce;}8U?Lq;fp%2RGg4gLqEQJ%6Hdp%De(zx0dvFlY?r+P+vD55_F6| z3{otaSv8ubk>+Cmd!T-}ZLTvgq;j*}bfO8Pyaz9$@cYVe3bL12V{!((uH>ebojiL6@4bltWZVcGsjr(gF zQzvr4W8wD154Iw?I2b5ehV)(Ey z-q5poVV}i6n+w7FAq1bmxmqf`G&X*|JyfGHE_Y*Uq(L%NNvdWgT4vi4sgj!{ zRNi9}fg;Gs1;W}EJ4iwb`6#_qM+yQhtX)JP6nuAAN3=PPGM--tF20@FyK;b7lNKF5sxh=@6=DHqAZ6J*-5?DixWx8Dq)!l3t%l z;k7Y-e@1Q0jl?)|+p3IrOP~}D4q+@M1T@B9|IPIPShy}co6)^vq@Y$N$&Kpu!;_(u zpZoV-|1v%Vpp3GIy-tSJ4I}IFyIC$YWh*Kdn&f)DYs*FMi#nbPq4Tg>vd9cKxy~S5 z?h9Vr(|uymeCH4X(6 z&%w}zwU_ynymdPjn-ggrZ}gr4?^jJ{HFY`u-95s?M{XLrUXPP=_tuae<>tq4ZG zA0U=iV*TUVx@5N^zH8OasQQs3P};q+8}Z7@SQJ3{X*r=!Pro}Y0P+8aX@?&Z#LsAX z9r}epYQZP1XYI>L(<#Og)XTT7<#k9Nwr~z0-_tn*M-7>TFP=u#?}S={;pUCuoPhxD zo(SkIMdg;9gx2%sLsB9dMHUzDZ@SDREUnn!V#3wp=t7}dzg%IsBV_1R(r5PHZ=jyb ztBIT}2EJAQ=3E1X!3Q|L5YFcHw_k8sY292xU-E21&;kb0Jq~Bd-YsXt4biv5>5}(E3x!2u0L8VibV)%|mQ1wLTx>9g&XP18<;)SPFRmBp;1I zwM@IQ}xuT+d>nMPQ7`Qw}+_VAg0!-wJ_UrAWq7?17Bf^B;;K zgiw3Fe=z?Tff`23Zbk?Bx=aEFQrKb~Kq=;oYAj1T47xkE)Xd)}jLZoxNb9mPY=BoM z{Dup(I}MiTAMa2^Yk~eOg?d zEv2o)>H^sc1yJZ~`&_A^1zuLYlhLCGJ_qqU!nQ|bfR>5R%34qEOF7B;QWtn3+6mLA zP_){HnX9sXZQ;|kVfLgoP>JIhQGl&KF-6roAA}#>6Amwim6bqhxAT^vs#P6TuhX*> zr5z6n7>^6!pBTk*VG1gRdfqlVx9SFimeLnXMz!Jl5>TXhiRBaYlP~pjeSr@7(jdx8 zADPt}!r)fX!kPgyii2!dQ#h%BvK3C@I2)?lcb9ZE-XMIOdSN6>p2jbosjzlK1vx0l zQ2-E`a2!ebat1}9)K+fmHQbu$7d$7xUJK7QA*k3OCsJ)9M5HMQp_>|>o)5-aNY3S- zjQ8FuI@UiFLLUj_YqL;y$-q(P!s3tU;h26GU~EwUepEtWp>#$y6y;!FjNGnc%o!C3 zfMQK}vQKbTB^H_gE7^kRz)JU970%?hown15fP|G6xA6vQOcx3uXtgb_4SxmL9FyW7 zM`XeN7e*9Rf?cynB?7CQ`z(1@sLa%^3AF$!fl#ul>Y+#qwr18of%e=k@0OJVA%ZJU zGYHgyit=(7Rh4+jOGVf8l+F5C;xmcBEIFk2yI6(pj{g;`y5*fMW4$g5HnzGzZ@0rLvz)kd3Cy(Mge zM$-ciWB?0Q(SI=*A2`^A)D|;ML|BwG@KH)gBQBmMNYY$_eu^U`DlPXrVqT*amNJ!X z&asHPVtyIN$HkfnmVg?XNY&^ks%tdn#Gbz-BjB7siCdIr;-O5c*h3Aj?$ z$`Z`*-29+kz3xwek4_Sb=#}@Ng9OI)Mxe1F0HFZ;f&hK(HJ+vWsQj$%gaS$EdLTjo z$~CsH|PN-syqLM+MXuGSLa)e=Qsqu})AnwFBEi=Et?}hieD(FT)?U z5S)10u%!8*aG1Z5=BTo^0FdjNf~e~Hn;xFO$d@UQp*Xlfhm;- z*M=*?d@OYY<{nhCF@`cKgjWEHDwG+3tlNj_Ya^757S~P>&aTC%^W!UH+YD3-a+G9r z5sR6QC96AJj3!$}UXbRqxg~6?EH+=D?oLpcO@L{XT=R1A#!4f9!Om7mL@2}VnQ=MM|n;| z!PVB>u|iB=x>qQT5)BDu2S;b!UaoKHwjXXm9kPpKYq!;N4V3~O%HT@ZUfVLpy#NkT z{WDM7p9KFaf}|3ZtSHh1^&S8rz5i<+=1M4vvNr2`IxG!0!TJXjYnQ8p@OVY(D5E@9 z_aljb3L40h6$2%*h{2=tnZ#w(V+=jjt&oRSK00B!3d|2LA+WAXQr2qgDZ0T!r1L(1 zMD`k?N&lP_8)Du%WAw3Q{-vB~t7kHvGRmT7CHG?RqrRRlMdS{2+m6rOsDtMD=QA7Y z=*JcYYz=dmx9?@8`GZeem zy5Q&O_XtXMFH{xz*MPgvvm;1EI%=Fpdajxv7pa@_O!-X4RU*MBN?83Xp^v_hhv2$W zy`liGg$gnW`C|QlC1uPn1%(4g4;T~n2W-rc(yrh)#-gHrK=1l48U51MXXrQ*M&^4b zOU(Y|>wK~HZ;FmhuS0EX9IM(Jnd}kc;%0O?u zRGOoLb{QzAc(jMiG`Eml!fvxt@2b;`3e5Utjb9mf?-!#Blmay-kUg}1!kMaiApDRAb8JER%3lBXx9RV zpWhYwQP8|r2)Umm_jr$-_1C{=?$Ze5a785Xa?5x)0K<5MmQJFo!(zt)88Z743u`Pf zQOv#35GsUFF_w)`8*Wv+W#$;xTO9$+g-TqwCJJUd6LzA); zRnrhbB;-K7c7vGHAogrwVOO^0-QYaMUI_7-aG{E}9q_p299z*Up(p9kO6&9yNWLbw z5p4?6ny6rZkUg=7AQVH@yK?=O`eh(KVJrTyQ|uRc5x^IfaP}R3=LMo#t%IH)n$Fig zw(Ts8dKJNS_Vfk(#b+Or#q*b{VH|n<1a5o73=aIKKZQMeckqG`%AR_T{(2a)!i)vh zE5)UBp7YYnJ3Hzp=OO^UvZ5!rIl)`G`w0RRkJDPOV&n)-R z`|9Ut^gGuWtJrmgiq;iC7tgPZQ3bFB2QW(Dfeg>PY<`yqA<5NmBO`^bz;_ji*WXGU zfK;m73Spf6Q?q>jN-aYByg1N#Wq6JcS8Z3Y+kq(IrJqpe3A`+wzl6W|>|^K(Kg;va zF5r{@_c!q1e|Qp0=P#K*o|KRGwbS1;o@x$kt$UZ+ti;kN>QB~uxD$l;s48^)W;H0T z?zsrUILBC01Hv}km4XW>tP?53>P=CQDe$fkQG65*pEpz_^gsoZ2jcTpj-NTTIP&?d zM+tb5d8QJIt2qKs0u%FOsa{8*1G!={PxoA)iLU>@1>Xmt!`6f&`PjsCnX0QFbaFe* z`X_M~i0`ng->jSDMaf1v#Xvz-WS?7IKJo3ZA6rxSb2;+(34H!fAHk#FKHeo8Q#>xl zlmtK3_BvcuzFnu=jHPhVK;(X3l3LR|zLPS|-4NoDm%31tR*p*-R^ zl|~Lb=5uVf_W#El1{`08adCvr8y0nTzvlm7pTT^3Sd>Iz>%RMzpLD&q6N_32A^6#E z&<$_d|6VwL?y5(%c>WT;{@@Sr$N%;r9Qoc!;{-Ai1iOkPXVvFa)P=(OPWRK-gsSK6 z=ZiGPpI0Q`W<(lKZSm5|Qb9%sRMSJS=E+|q<&dVaR%1xtQeej?p?v6}t~_59FrqUy z%uQbk#p1b3SEZz1PU*t=uCR`RIOsZp2FFGf&#CF&D8Kn`6xiI~m$R@I&tJlW zfAM`h_!m!N>HH;iAyIZS`^Emf1__Q7G%Go>oM7n8R-QBc4zG!J5I5pmgA(zGYO>R7 z#MHrhnsQH2iiM|6Z-^6{aM!#XxBCZnX3O0%v&0%G;|Z?>)fXaqxz3A$)d;5JppzX$WjzNhvhs?xTm6fN!K@*OE}g$5FC3qLxsT|o%P0oF zCS>C{1-1LV57{HpjY8l38+idWf~bI{B#vaA%Q#;OO8F)9x6k0xb$kSv#ZJt>EhAA+ z9u$*=hU?kyp;f*+2#3+pz>ar`(Yp`$<(4ClpTHMB{Rp0V;v{>agasN99~5>MKIaY` z-w@Lf4J0HTAfj}i>j>_OI7*OEiFrk8JQH)tS|?!3wHaq+yhR?7E|eyLH_s)7@05*9C`etE}mamkH<8!^peX&EDd-xv4_g*%4i7~bxr;) zf&SmfH^{s|?vpErt+XAuZ4q{ykGhzjv;+}kmX)-+Yusp(VCU-$J-?XIse-Ol+lqve z&?**}ZtCk_VWC^_srw(oGe0=P=#>x4tb++dOHnXlH1ZcXR*}Yd)8s4|#CP1@ac6=% zD055RMNxQqMltW|{mV5(6{PGXtwgOe*&yBTN>3~25MsLL8 zUpjXQ-}%O|u~JQ7SCV8Uw;03 z`08JNA7;6XijQB_>GVx1fu!?7&LUnO0!Ilou3V21R? z1a&njJ@0_4C=b-(^O(w!$4}x@_dkShf8!WdF0L*l;o(A|MRn^6RsT6KcG~ArI^(S@ zaB`}sByQ{1msz~Fq4pP{=I_je_r`C9&IJ?v{&OYiwOYD*YUV_%HYL<&S#E)DQk zUwC|M%zs@7!@HrmI8H%~`auB3wVALBXwUOqV-t#}J~oOR6oG6h1OSxvQ`S%w@1r)& zi&i{yEp6X5ir6NU@SkmaY`?V1ZLP5jEepJB5cZ4r@7bR*l-Y-#!JmKfJ2Ou`ev(4V z{CV$t?;?tcCWK%*F0N12RjYQ0sMvl!C&9g_laHw8%I$qQ%x${JNTmxHcpmP-X@7k2 z=$)1Dd(){{JacY5i{6T42OHXub=t?651uSy5UU zLV>YP*tH{}DJ?PErpeSY~Cde!VM+at&95ISO%I zQCy4XE@AeeAL&!yKXFZ;$5gWeNr58&1^R2T9J?B~F~;8w=XJw^{cCCk!{g(6E%;az zKw-Jv3#{NM5Wae;tFbW5zqB<}!T7Z3MI;1Y$^L_jRiwTq2?ApALm7v^_T{N3PU5L2 zPM-UjckiFM^WApc8|BV@l3ZzXV_?zd~4u#VjLLk#u(C3^<_}|PYCEoM1V)|XLnw3FwyI52Z&LC2 z>>4qCey_ubYDlDhNe<^My$yd|%ec1#cL!5Kbcn?wG#N$!W$c(2lKJ?C-&3Ys{YDx` z02%rQB7o6Qbtr3t%FK5jI))=poWPwwzhB?{)>|T3DBzxu+sNZde<{_HzN1+35r{N# z1EYQP>Wx9!rEqm_S&DoX*%#}%1#xHh#e^{UM^De;p|AcBi;LHS=o?+OPwn>4{g^($ zsin0pf5W{rG$s5gaKFmeJ37bv$v{AD>GSoaU#4}p5&|eiMpzI4#a&FvBN2mEO&Ma5 z#FmINZ@_DHl!PtvMBqeZNf98G@M2>iKq^b;FX3ihDn94xL8w> zuF=nAV0a|lfX-S@!mmjYSM!&{h!R1VSd>Cvzh~vw(56x;9RN6S{2XS#bqp^YU%1k= zT$8e6#|%abBTM-jJi+LUzhL?MtbX@w7yUTr{EY#)}grbX=a6p8ACB_YeAK~+C(;5*8G2={lMjKY4*wTa8+Yfgc zH22MV3O-X+g{)p4$m~PU;Cqiew<+d(s@aYm)7L{p%!024_cLL)7H^a@9oMb{PaG=K z)5e=xs^h$0=FY~oOvi=*j6f=1xSTw2T>ki^Q&^QtRozUBr?KY?lQRoj`Os|HNj3TU z=L1L)f=z({y5$E?oRAaGE#U3%yd7_U=WX!CB))*mgEIf$){rPe zAu-t*ww6lLRJ;J~7QI@xV&fS5dX%N}m+;Wv{6N0<$Z_2Hb9d;Q-*zips6&VPm_xn+ z>Nm;dFIsb)K_ z+i|mf8x89-1D)IG@&!r3^fm&8QR=~4?~k{=)m(taGdD7q!b;m81j`=a3Y}l@;%%C`;!r;cx!>NjdTS0`C0z{d(P=?Lu7T znR}Hd9+440M)$Jm3vX26!HVK$Rw5mIsh*u*f}TQHJao!bH z?dTk>+4n9IAOSrpdIF$Q2KjHdtvI)K7`i4KO~JgR5ng>6n+rgr+eTV&c!yj@t^|lT z;jRFHWUk9MRfR&ODL+5$inzL`n6uWV1s();_4eP*!~tBXSlCeB_RiPi?eDr>_gue2 z3_SweW$d3j`@AXuKw1x1*?|OW9r~s?`Bo{9Kk^)YbaYPM^>cUN z%|CUkv>x7Q;fVv~fV60l*P~f?^Ya_Rf1dPfTT?7Tr$gQ20#XzZK;~ZXy@!wE;cxv2 zmp750-!0pwcj1QX-hlAOsSo=}IT_-EC>}t4JY6w@@#p8{@8@IQ9?*|O^pV6C3ImEZ z3aDzURbb!PE2R%=BTltXfb^xzTLW+6^j|Qy7oQzhf&XQ)0d_5 zm+-Z}ei9FV>lxhjpS^8i-z|G)0yY&@S6r^7P6_S9thn$?4?_6VGjYSno<@+w?B%+xzz*vaKWBw7@k>FKNFM?~bku~!1 zZk@7p{t`a-r{9@*(@))scm3QQ*t2(MnV&X<*66tM(LDuPAF_Pnz`neLzyx2lyv3(d z1OcR$&Rx>I0Tj~!03ZNKL_t)q9X_(@1s?$R?7S6wcE1jSX$pBN+F3QahV@RhD%T+k zX2)-@7FbUic>8QK6woN;vxN{q3!rJu_jf-PfC)bLeuWPo3#-&}($2AGNhV|Xi}nnF zhxWY9@^FMNxRHRaHIbFNwROu6zJC%w`2I<}>8EbRTYviX3;S-_b3-*=3O~A>lNeV} zrY%KM3`~f>9ttwI7`O)*6g&<&qj&l)8k{ zOH&_+0SKMo=H35x1YN?+y~@hjGfVi|myZB|+;sCEy!N&moISI&W#(!3&etsL-TnHRMy3Ja#=a19cViKJLy&g$ z=v3a(k%1_st(w*E3VwK^kr&XtPmK`j$itRO1vZAgd@Pu~7~|lRt5~Wd0mOsK=L#$B zewQV>lnZ?zG<_Am68+!}3%;J8j=0* zcWzsl-ZnL}ytFdks@}MC{vwtZuf zrb}-Rp-yy(_W?wb4{xk_A@JE3g+ygBmho@mvb4B@l$l2;f8!d}H+`9Ew&RBD-mtK9 z`;9Xo5rlx=z$m!h|J=3A4oQ7^lEfa32D)&5?RwgC3tlX&SVUuZ^}k^A63Uo309l|5 zuiV?N?%9iT(|x8Bnus|?H3{f2ww?Oj6!<)+u}P*41JJEBDO;!%KmQi&*>x)f(>L%q z1^LA`_tZk?zJH}OYNfz1p|3lwl?gc(3UyfRQFYfQ34vJW!AYo2jpfG?#uEi-+Gqa% z_O7nCj`KJ_v*#R=B1P)cQb@@*DLWNvM1`Fn(AYrGs_0EF+9th;q_i=lph&En06=G}#~4T zk&}*OsH4&^(8|cjMDNhr%+!Mix}7m2%oN_m$vNuYc+mak2(>Nt&hv%l$Od@r+(<w0y;>yC9`~H;p!b&R-vcLtpK2MQFKuT&xk0! zkH9T)8n3j{UxoZ?OpI-9jlhV=SZ^BB`{pp-n+{osyqP8s`vybosx*1?dQCv?wd)MO zRH2uH_fsCB!oW847`QYm*ZX1jTQH^VN3VCdQpiaibzO)dW-xhi+5t z2F%x23yrGMRCvTxmVW#u!g1nu_pLizrhAg1bQxc`ys*X$J7FPTt-t~yyss#7M$9p% z7f%?Z^AsYocMMb{5)8rMP<4a*kpvtqp|$<{=5YMb8B9(bJ5XiR2kaB#c&creo~7I% zq`%(Mn{Qty6Z7*3N6rk<9e!Db2k=^g`Z)sh*3w%GLqPyToEE&io|K&Q<&O~(S&)=o zTmh{<4Xi)K?13t?t%sxnSwn$P0{{mCX-aF>m_M8zBR2}I>?#@SP2>2%PaT|@oI}d# zdv82j3kd*i5(NOsq5C$~m|C{V)_F@xlY+-(`Eh)#+#ob7zi!urN+S8!Y+TCa4KCHk zlHnNuqSxdXYglh6-u_Y5)LS9dekq>UnsfjRoxm;_2bM~{?8fSdDGXpcj-~z5GS-u5 z5x_2!ZZW=gWac!E&YVW4Gr?7b*eGQdWAecyoqWrbJZ?%{_2h#*zHXZztBx+>dB#}Z z_iZW)^5d^_`Mu`je|#CL2mH=o|IaT7e1LT(PB`q|$opD|)xZtJWjc-yq>NtnH_mdS z3y>(s4G4q zGWzufn!40IXi&HsoR`m%iuU3m^(6IazEM%w{Fr{M>J(ptJiBJF^3vaYUn2vT?|cRC zU55HG1S9p11H}VBm#~$NG3OR0D9~ z+1X`gTo-F68lJKIsUyu^^{qC6QmBy47@$4EGE^x|Mp7EAk-y=IsNg;$s2ZGF`_NX( z)YLSl(>xs}ayWv$B^>DW#^}V{qda?j9=-0^FivGxN~ajZ0|!pyfrF0w(lBC} z^xyP;txRRJNRC4loj`389XfavkDNM#qlahrv>_nsV0v;6#}7Y-vEHsd=)F>N-`(m{HhF5mD1I1|bmVH)5q>%w(kRlwj@R-5>kjWatLq%g@d(qvY!r zXt8UgC?o(#sL%3OeYLuB-_Ddeh#3~B0f9X=9Jqo22lS>7B?RbpdUix^@Bo^Y3?T^! zz|kXfci`hSEUzbj!W34@GlcOJ9b~J8{TU(#V12044d)OI*m*nYlzw+gfA)IgnES*6 z9(njv7$2Xk4?SWs-kZh)2T$Yh^a2;ul{hUnyFkjn$v~TX{i>qovAHgH51z)XpG8#> zH(v!xdZG=|#7AsaVmgrsG}i5$zt^!gBimvCp8oS=ZBIEHU za(Hei=epHG)WOWu96oX6F-%SzWi^Iz8?Js1Ww5r~vPO}K*JC*GCtFjsHos~Nye`0x z6(N1S3b1N$%#m#H&_q(OdR-`-mv0-#<-~Naj!kv?gO9PbvTX+7LVsbc0G&Ut$C_P#Ep5B;i=*@*}d$ql&@h)rz5)(D%GNnwqE4`L|KVK>_z7WuK7 z9|i{w6abDMp2fmLXD~Cpd-;Kh@q?H>@)!>6PvyH5Az8(fhU%^CM(52sP7YQ*@SVuH zCNG?$2PQ`i&D%7sbE6S{S}5~v3SA=GpQDB9F@at|Pc1DEr|!Q=w#@(lz=daLR{?s4 zk}qU3E+DN}hCYVO(i5N-JEi-y55QvnH1A)`>$Be#d%hxmknh8R00;}CKnh#s zpnmRYiOzB8NeZoeG6et!H|h3)!5AN(#HokQ;P}yb-bq7HPzQ%*9>gb(oW{Pf82}05 z$SZ}62Zf|?rk&V-WRkT2y z|E9s>q0ai^wKo@rTJ+N-+im~=;7^{NT`Wsng~j#km3;xvOBf8X=m5a6doV2kJl0Rf zLRyI94XC0~-+l-pJ>l6yVQ`*29I)5yWKWe5SRVhz?@Tsf{ehVybaM7IA3iv@t%#tY z4i4;}!`!hmJUw+BQBSkHz3By0BwWM_KrL{}faIiJjs#S3HKxz&>tnG{?$PTx_-ZQ8 zjZHKN_5cQ>QO(uI-%QypY$hu6^YFjK%-p|rmBpUDTYxaU{8W5IAB?gh#2(8 z>+`KYlz6N+Mn@0N;o%dH;n>mnp&Ekmv1uHdc@QU#pTU9YS#*jnMHJiK+>N;|WM!m^XHx(5%8-!{_Uotw?Fy8f6izk1`5=Lh}r?T{Te008jqZ@%+*fzEds zJP)LYrKr5mzUmd>!8M|Rh%#Wg2jvX9_MIUcfZ*OjJdi9_32%+GSP+PzOMtgR8n_X$ zF~UQo@wXmoef>6W-uyK_{Pp{|dGkY*rJB9 z8OQkeeiVf?j50B!G#W1p!SrXj{-b&IR2o|1X@7{UwY;Bh2^_H=5ScZO0seV{`&@~7&NkgU3avQ z0r7W*G47z74)@(?0mStCEL#4&PIQ8yONEC#Df&_MqC=ZzF)# zh-k4li6?fO@&mij0H}QV>nHl_9a;c*NuQvQ0Z_V`S@LV*?p4W?2TB`6!;n+S!?FP+ zhDVf6A#hU07{4$|g~IH{3eqynzB|lg$w{k5B}Js5Bsp)wY9@`Dw~~2_Pi$*46j0G; z0R>SR)w6R|Nx~a;p)s$nlUo7+C`mrE7|iSf`Z?86sw!fhvWpdhKB30oY=A3m_DE!2 zJx}L0rTQYF7{^LG(reugo_OVrh5mL{_iaXYT?o)5-~Q%1kE1NU3&OlXO$E>(uuLcd zK-`{q{1EqrJIAno1n@t9=SN$94e~5N1@^)}IBqA^Y>9yrVlEZ_$ei)XkVNHWHw35)c8?tYZVsE_O zs{@`e_e4}d`4oLkr$p*2Ez`M|-WFXix&{E1YB2f24wR*$vD$J6U9fxIiB z4v9)A9*U|Ogbf#k?+uZHm0B<32B0TZoVZE zVpkP4POQz{@YMT)fCC`5`-&!PsYb2I_~VfbK7 z0(^_;C5#c{_PQeGIrh3(Mg>-)JF-ASC<>Ae1CW!SR3??SlF>+9#2SO0j`mK6oKEai)2 zF;&qhJeK#W zDce36AYvs2Ng+Z4T?H~N*8ybgJSGBEfjx$<=l+?&nkvlK-I-8SvbD05^W%8kNF3ku z%2iH+iGl0rO`N;-uX8`W(>d%$894)>a;ZPN3gE(vSKql_fUYoj-bpOZ?zsBXtg28s z?Ryo*DtG9@^4ov7Tw7O`h#tulhLCdn&_k={cp;CXfLM+y@eI^!lQtXanJ5%z)brg8 zF+fy(aK+dtda&3e33&^yFq&>tBk@(q8bHn>c;yWHvm>DD1&zvP*DY5>_vGQ4+4g{Y z?drZw;vtule)jY4v_D~$O1kjsh(P^8GO`9h<LKBm_fThx;4V5jd9+&xsgoB~ky{8{6NxHw+mm4qzj>^o^5CWl2vl zc**aZ2>ZR};L7MOpkow|^EXg_04Pf(921mvZ(SKrykyF&QMIcK4Ie2evQHSI53C}b zKw4RzJ9*0nYr=YgG?WOE;iRlO08rv3#0`-mNHD)CrsixARZI;*@>`O=t1?#O|7bLm zu~4HKwyGJ6$Wb(>1f#(S#+3Q_?tWU>iMHKg$;cysC>9p<-(5tpzEq(2ZCz&wJh>;* zkl?NcCR}Ht{_@*jSiYOhH466>WVKw?+@znJd9XH3IUrp4m%VeewiY({CnC#5HoOV7NwS_=+4>N4$tU_U1*)1;8pi6Mv?0zU@{ zFG9!=!-Hs4^8LaHB#=O;@* zp^UgFagi8CJu+K8fzfvEjt!b;zXVn>QCSWhEAJt&lOdwmhv4iiY)tP2S19DWyVD(1 z(8HOL&96iKbDc{_DTx?XG!Tocv`d1{O>oE`L6M<@9Q2GK}r|>&P43)e&=br(qB|ycPNF5Z32iNezG&n%q zXnB&NdFo9GGO*bmw*jKYvazVMrP$J8{Q9!0x}LivVG?Z+ONrR8i5w`aySD)w^72)w z8jO1CFSjR|fdJNsaJ4suPw!>r2lluDP`PyFM4z~Lg268d?B`mvD?%uixV-(u_kZ&F z{_^UDk6FISrh25xNlc_Mt>x|0kpcBiwTD|2+w*lS;k=KPev#n_(HPH@hp}uMP0W){ zs%YmR>m9bsb{6HQ(hAdT3R#2fU_^S#HjaXif|4NGCsXvi9Jm>p54h?zf-%+<5I2Ps zb{`5_>Q2$ZZZ6;5O4*YkK$A#VaQ?;qyM2Iv0K&Wn%dj~A2XFtu@>U#Ay_|e=VoYOy zNujaE@-i1{de}L)3K|AT?qAf(6h}N-?5kJoZUxg47=-+N8oMmPR9HoXVR99L?zN&G zB8McNx-<^J&xK1vJr)8;0208Av+Mz$D+*z*PQFo{LM&e6%zz2vh;#PYHrc*J{6mz~ zf92oyw#To^ePaMr#1JgJ*nj6dP+S4%AeVf3dG(K$w(>Z+gJL7ZSmR?hdO%}h?TZh( z;=KSYY(9%y*C*IZg-L4)pdL>lgDP*TL9lxMq%cGl{+d0sRA2DC%t#!Ay>?ysM=!-= zJ2q+Y3~NR%g(+!)8>V1XstWANA8H~jUI*OpWY-uD=VLsdk+S470}*J2fU7URacb#f z-S82W`yvErl1u%QOXK+H2`)ca*lOi>=GzoepLc`nq#hd4JB1+xu4e1l?1u!=4k*Ch z*RL+OI;^4wi+5eSrTHK)zcug4Lfe$>V9LBRB4$!UHIh2WN=FV*y79 zT7sPV8Dj5&948QjHqDWeA!^S|yfiq%e$3zkVhmFo{jEW|sr9=1}{V+SI&4HO$BA$TDCg!H@9?`4HK6k&f( z@P++qs3xF<^qsNB;Kf&OoEoW4z3q_umID~3h-OF}Ergo*K7@R;qh1T6EvK$84H0jf zCGH(7TZC?2I~~((3TN9X#5ZNrwQ6_@pUsC<8PhwJuCE$ZLv0KCpnyU4gkU18Pe9b z9C6fVf&pv-feR)o!;C=W@p*oBG2S;r6!{0pLT#Od!f#s>$JKKL)O!P@q(R{OShf;Q z!w1tuqgkoy8FMh2>AS*v<3h6Kf&i9#9b8y?{nYA!+tdnL127aR0b}T+@+S?jc3_Tx z+~ZmewJpBZ#SYKQp!#X+${84&uSGOkH53SgL?|Y{G34!`BTA59^B2fjXc!at3i2}{ zZKwAzUw((-7KxD+~#&Gm^J zw}&{3kL^L1mSUL!NKMbcv9+-4HFQUS}`1zs9LO`<(X^d*8 zl93sL&W)cC8nUo(J#q+Mi=do{+MF2oOPvJs^W7;dyn3Tm{>@5@0EQrC^35dLP6ZK4 zi3oHXgU9w-bA$UH*RogdMs{8I&uDbdF@&;O$^9u?UrL}3p$D=wpf@dwA<4YFA{8YO zHAG0^zVR1OB(KE^c0LyKE4t6C0CS!sdU-`r)%I;-0N0CR?3q{IIQbu&xIL{!34cHU z00y*4L_t&pX$`#+5r-!R>nT&(IZ1f zS}nui5CRzHkD-E@2W9IB07}F7YELNcRUI+li{epT}3`vcY46t8{fHLEN zUHX=tQIs|W7>cCJ%JQEvaWWS`!0f8pNqKdyKPgh@SS-K_i<7BPO>tPPGXm*3cyDhy zjHqmY{fa0Yvn%pY|6Wq*xd(VvpK*YO-t*tj;8OHn%AH|j_yKd`kJZST_xH?%?YqWI z{oVSOol%q)0SrNy_^s`I33yaRx^~p-IL@NXjQ&^e++X7ExB<$dh!8dva6!e5HHaWP zIJoPWfFOzs;JBe+;)W;+2nfmm2_PV0=_G`NC7lM9u%|;7ar;mG@B3BN>C>I=bcap7 zck(=yI$h_SI`u78r%u)Pe&4A3*8O(qnE8{J^qlp`mM?xdy}`;4UTS(r(umTL7bV~F z&f8lqsGRxZpx^oawBUlFkKXk5#^ibX3fgwtmE7pPr5S_sPu_QJ=Lxs8PFgpj>4^iX zCbm7f-l^4HkNy3r5AS;NgRd%vKJr0n&x!x)ICJjoN50S5ar+s;;;(|2eck)I-6!4t z!}&R-_t#&!vBxXNr2Mx2+ik8~{hwQ}P8~Yw`rcQcl6w5{J#YB(50^L-2mE2kolRP9 zety~s&+l4#ak~rpJe_*;vw6q2%e?HVb2s@ao=Cr7&DK%nZSS9!Rdjyo#=k*A6Shy! z%KG~YlYacen=@N{mpSgmITP>uuiroB_`m+DVftH7kD4>!^T{8ceQUeQg2L|ZOsKK|@4&X~UEiB+km{P#_h&;8wt6JM%#&xS|(k7%>+u6JKPcF!HXo~X=w zZ*BMB?M=`B_?V5qd8Bm2MV0f1m9+F-Fk{*~XMg|av-TlhTpFgR0`yXZm&zQTe`5goQxx4C{kuBDIc=C|PKRo^E(~f<4$?w{~ z(!S{+O%FswWP3 zKdb-cr=2|dl|KFFBxRm+_a9EY=d;06dtNp5noAyh6Z>F&R-f0m^Mk*CXzNc;p3x!g zsmf>0_^{#PqItW9&Yb(3)Dyn(JrUgJd-;OJ$Coc`_SHG3pK{Wj>-?vT+xJ-K!41wl zsl`ZN!#CGn_qz@U-an^DNx#?rHv6xC`r}__Kb-tu$F=|Sm#-|}|4ep8Y47)s8F2cZ zclI~+FFFsrX#IW8run|h$6fRvZx#*6zTxvfCcoSFy%qT@epvZ^(&P(Mt||Pa@a%K{ zeA%G6C$1m-xB7QZKQSr0WOe;He|f4&)y1tp`QX~Wo|M`D%=FVno|e?8uw&`+dAAPR zffvqI7yqYk#+tu;c2oA}r>UZz6MFyd-d!o;H1-$A3QTNg(+1(wOX`z)@RFKo%Zr26AF{|-u?N*5B5Fd+ylovp7$4L^KpM2n0|c4 zj^%Am-FcF~VZT4u8#ucCu{W=Le)Vym6lMQ#T=R?SC6)hv`NQ}1TG;#EF7Gu-Iy;#> zzI9cnH9t-I?_;j$KJ$hP2L818nB&j=_V1%E?R4(c`}TY@<;C=YcP_neaO)+{(*N|*vl;*IzKjpPyYRJ>Pk-)&*)5*< zIP-!5GpbK3IPaK0-ZG$c;+Wr^F}E;q=5LN0)b6Ka7Yyk-tN;pe_JwTht9$nR4$Fe16TMcmCsdKi+xTwl{lB95rNg^I_R@M>py|^whM*B=1t*Q8~o9w%_CD z-8kvgw#R0cZkT>?%b(`V{>Qt|EM0it*yE3{r|M-o`Qy8O)+z@ryZWsEZ1rQtO_x37 zdwI={q)Aie-```r)3f9LImbTP{4Bg*8{csHjLTRyKO|E??4T3vaxB=F5xj{$R`xk5Twzw@*6%j*}LTuRj0TQE%Qe zb>)>0)NeU?dBvAYkNeI3v_FkYYw-C`f4jWFJL~Tn`tlROj)RiQ+J64sH#v{5ugK~# zbWdfs0q?zhd)Ea2U2~&Te{s%<-~Z*ZM_)_AUx)TLwwuu=b<_no#tSVe9qAbOyZQIu zzd7(r;6UKI{>lC}_f9Bj-8Z{9rO(!qR(A(VTHc-QZ;_f_+PqKwUqTy?L>owVar=Im zC2jjT{x*G8p!Gctk}7G{N0nUN+wr&Ptx8&?s^aFU69Orz^^Qavey%!;+xJf`xu$=h zqK9Ibi+orfp; zI}OV4cNnBVzvI7lfbx_6_Cmj+{)2W$=%@a-yjumR1JHlS-qG~;a7vnW2M@Zdk|y1= z16QObAFcKu>T^mu4bCd*I9M6_RmrsjoRaqUIbq@xbbRBl6GCGp?2RaQ|>^JiFUpG*6zvLS90p0(#{-Y20&G<+CkEB0< z{x9+mqdkld=o3HR*svr0)#Us>k7rIht4)n@n)3J35B*mh|785<_-FMWI^b_5v^ym& zdUNbUqW<@EIR1I{KTHQ2cXjwX(4?D#lyS7jhV3I?P2WE1HB~g=N$10}TB{GwY#o?> zPD+Z^slfH<|3?3*`yu)<{uv#R@vl`MN9ecm$M~oFKk4WA*F$Iwqdig|$cx746HU9O z9qF-Q$LLADN=Ho!Y#%jAZGUo-+Bk5urk^}uI=glD`^hQETK4u>n>G4|Eu?W14!m5zR0 zNqSP}MS~*f2M?y7)jGJo?LEPgj)NSI|I+`d0~r4ydxv9RNgJey_aF3M(=X$HPsjFu z{r*G052rJHOvE|zMfk+)9>=dUIxlJgS-y4UF3D^HP^cP~RTXgA-QF+GxGw*`y zR8cd~X6eWLr=)3gTD^Uu#&I~md&Xrx&tn;o(h;}b8$cLN; zw}||`dhMl6#stF~juYPN+;PL4FKv!3%LM;G_ju5+n0Af>56rR6r3~d8hmDO&*R1oe zQOla%u9$miI+)}6=U=Px8g|igjv{+6&0)NuF1T}NJx@rG{G`%xZ*JZBY^JkgT!xC4 za8B(&+Bp2_L#8iLo-F!XCslOW9r`^Gsppn2Hn%#V=SP?y;Qi3J`?^Hw-}&5I4(TJE zIA(H861t_F^xt>z^w0R)wQ9lDcd1XC-l}+<2p?o@xabD8xpCJ}zxUGYId=8Jl<{}M z^gsW$v-5e-jeG~`*T*K;v!rN-s3-35PUmzdeRy#<)IYy4-cO0Bon?{UF zsJ*0rNvFPQ+sIc~5BaK-K6ZAEmuE@6Thm{GUUW^U-keKs4)MU~eApNOzK{==7m3m@ z?HT`$Laug7{;uQALBG(k`Tpmkj{jbH&u)05%Io(8Y16cmW?eVww$HHj<2Rm#Yc}2g ztoo$E4Yl%tdf?6ry}Iww1L21`moWVkG`%|~ysLKUN-&$u49?4UwDM;*QP##@!`6dd?;vyF)>p19$t9QB})I!iJ*Jpdv0Qz z+;$s!_&qi(6((CV4+n#()Xc4+zoJzXYE|c%XvCxU<$T4#~W4Au<@cG_0YLo3)gIZ@VPMh7qslAwvC=d zI+eXwXqM~sJ@&V*PwJGqKy&l&_{ML1b&WC=7o)fx9%3ccn z-u%$>G3YmQBhc=ZwWV3!{TKu5BLCelPjhzR+06CvG#-;5NGN-0n%afjy#~MSJuLGk zsY9;U_j=EHkL5b^T$X$5mu(n_sDgxeBj<|uV-$LLv@3%eC8{{dpXbFn;v>0y8J=E`wsBxzvY3^3-TdR`uDv0 zA?ZgOBGRj#->(Xf!msg`d9P(XIJZmk51p|3?LB;^=ecsN=lNS69v^iK%x`pC*!b_t z+tBa+1|JfoAMHSUb=vcqv`hcC$FF(MPj`0P-=Xum48QlhHeHfj6FRSSxu2!u&!ms^HMZQn7UiZ4_2@cu zopQ`F*6+#PdU&6GrmjOjWAkGZqtj3QH+t`tHOuVxpPUCz5|;m@kJO&m-Q?g{=-I8y z+~4+^-MC)Pz45WWjx;dgdYgk+tdsYueII^zzu|Y?E#m%0o2U>kSWh53jSi zBYZ#|WuSZiqE)JL**eE0Rr!TaQq_uFr}B$+DmY`2ex~Ps*0tpyFGiQY)qkS{mTsGq zHJClOAPwm`LES`XoSScj@=)zoFZ!`_zL(>ED|%OCdRX zbk1Y7H}eyRd03e_FJ7fy6#xH3*E~ob657D9lxRLRV(uxNsxCX66uyar@*Pw zN$vi0iORlxSbX|D^7iVz9TO9#Kl2mPtMYVkUd%XhfNbA-5^na>3&2EiKJHLUv@{iT` zZF$1F=hm zlV(Xq->LJYd*ic{Yo*(M|3!{}R@Rngo7YAE_q{U*#y&sAxZ%a4xxfgd*o7-V8 zk^4j(<2>)WF#YG)fWDEa{P(>(7xc|l$kkr*LLWoNQI4|UjD>3dLh3r{Ml$1E6~}f> zH~V);=goUf)2_$(kld;71b!OA*OI2rQ=nVzpSR4Z%wFSEWUrCF4%!{eW!y17 zBzJkI>-Rdwc6q_t=yyab^BhY?A>>|rrSXucUGi6oUpDJ-ZV7m z>bjTC+Kz7>p>3^9-!HDiUigmp=O+heE+X9uG+UWFGRB9-chlD+=CV=dbD?o=z4ASf z_@2SaRy`AwcWr#Y*iZu7!a$R|S|uufUbjDM_4tZxjO|9>^}CJVEqaX8eO=RT=dqBv znZt+AW9?jyRI?>@?0`nvngX2*EY&N-YUJBOF~ zoXq3BGRHXQaIC8a{W%Ga@#IbJb+8w)G#q?>Q1%Zl50&T-C;?JFT`Lw~5R>v^oqMC4nZrsSmwv9lU(7d*$X?SvUSe@ohPB`MJDe2YZ-LAO(#zZv;v_3ipC>f3^1r(#vUv$F4F zdTjWG(@$OpeZG2lzok{KDF~8Yp?@86B+@Q@UCUk5oJXA(+DX5vUJv?_H2vfO(uQL7 z^|VjbvYz*6A5D24%Co?SFc1^_H)Wx`n$+o%e=r}a-g8N*Wql#&s`VChHvvWk0;%paZbzI^mEF* zx}Z4Yz^_o(6Xh@aG5UBKWS#|j1Ck1gv!M6me~o=SQM&(q-7k{^s^(w$$+|52IGv{- zBRQ6j(|GV*Qc``BpI~yi5amhu{nNw9Tj8?PkfWxp$dYhjI&;*-3>TWk=(L~o?^e7U zfA#;jXYGb*?U5uMQX|sfACR$KyllC zoq_iY2;QBTf8ae`yfg6LCCyW_xBL&Fo4zf8@69B>-_QL>!FRj(c4C_a-zj@j&EenS z%3fJV8?$>jL?5^gehoSfPPdqD!E+PSNPh;v^ZQfa%K-ii{1(sa;`!mrp;?bI*rgnC zjPBu}lXV0-4ebRCYrx+D_;u{3`GH?|{dW+9hDRq3;Hy0I|!&?>6lY+pF$D z>^-F(u67-%di=y|1GDe?EU+!|m+Ln23%mR(OmHiyPIlf7M zHMMl)YXSNn_#io@vzKPA&+xxO47&7R`d>jZ0`+F*Af2;q&KqvQlDM_ItE)UbVbNDnygty8-l~-W8$09ZwxOT^Ljvh3DBSSGhZ+~iXrtzPkeFzvI3Q`8Y3Q_5m@tHm}%-C%FC*?G| zQ_a7yV-204?|@wRQIPS<`VWZ20a~5*&=-pr{>!^%)P+WS?arsCjEC<4O(%2DvGpYo zjb48z$hQM9^BA8g-=fxi)Pjp^$3L>3`Sh946nNl}ucUB1n5EU^VKR?6SA2Qh{6*{- z_hu{l1@I(d<+aZM`baQ-1ESHpxdYy}(C5N-ebu4{oomQ5+CAQ~B^TWohJ&{6Bhn|w zmbPdz_nfQCyITAL025FAe<^(eSbY35z{w*SzW`BbnGajVWli7{;2?Y>UK0;wmNO;iI-apIdSLqm^rZ4f@cTTUa^ycyCwC4qG2soc_y*g~>kTel@cQ<=VE5%3&$IB`-j{>=qVbe8dTH|> z3;lKMzr@9du;La!ZZPSj8+dtMLs_ytOK*T50^solPj9j7xK88i?fp8hQKswjV%W)J zdB!%((__)=m22eqY-x@sS9>0qXu+6!apIB3#G`M2K2vSSd&{14vGWmFbaNh+Ryf4Zb`A7cT8L z*Sa`z-UGaOBxc;!&vV3|d-3IR&OQ@)EPZz6=y~r(mRWk@<$@{qV8w|6w`Jy7=&)sy z0~fB(kp|+--hC+YOkZ|RAbd+^?Z_1+CkxW$4;$t@<_;=1r#&zDX5zgKj+^=3H#O#2aNePExkk?O8sK4rvlbiN zul8+VWE#Ri3T{H<`z>yu61V~21;l=y*cf63Xh*&W{UDY;+kR^E$iBqyd!bGf^XLNfZA}rVRDU-sU642 zhj?__^Db`L;+H{_i|t04#y11&&0}CbD_o4L>%X9OT(80Ygkhfyt;F@#hU2EZtxTEQ zWaVqm)k(g-0-O{SE1GB#xJOMkXy& zjpI!azf24-_RM1UVb8c_y@!5w?ug_qT<$Z|xg2rQO zj3#ioAszP6s(r11PQh&%9kyez&?!2+UU0cfV)&Wx(n;N2+aCVQ<6#n`;jx=^4aC!9 zdw%cKd71m(p9kMSpR4`zzX-?S8b1@nCdQ)G>t}(om2nAubq}!iD|*MrAJ@Ry$D`Mt zxA<$w+ux*H!q`*dj>|vEa%9{UnQH$6#F<+%MAB<2zHV{1VA4mk?(hj;S}aYTqHAaVg|0Cjw_{eP;lFw&?o!YptaB zuwZKs5?@-8y*9(*sm-`#u;h`rTHp(<&kQfF*3Kb-Ei9e>xta_Ai{Abj?U;D#!W)Og z@3UT+h8>Scc;6_R+^d$Y83tN|qQB5x!O{Xd9Un)l{ookvFmQFiPy$<*`|xw&_&CTk zl5XN=mtEDhj`-mb0pKeSX}{ajFIdqpeB(8(=DWteZ}_gUxLK>e;tzv&-0}GcVJ9nBtZN0Dv*aBk{$5C{2PaFMsL<+SWFb?H6BUfC!OjU* z5O~S2-kn`LHWsoc&xwnTfe$_+cxWqUn^%9Ce^bTEynvZU$lS0}_f_rF$Y5fr!@z-S zT&%@L3NBP*#ROJ-nd{^}8i9R{pqJ0~bsk>B#?7|rXyn0_zGPrt zGednf9N%8O4%lCf^ESB0jSjGn2KNcvE3l8md>Z_t+Pi3_T6$N=|M}6N;b5LYyfen7 zEa|7lkAcRyLU+S4t|C+59f|WLogsWH@NU%}Z0n+%`X~I&elTr59QRbL1-^N0VbEfF zJy>3i<0g$FQ{s1lX)V}dQV7QitQ&EyfnN)@QSi*ZqBP()gTSv^It^Z3>#xRZdoatu zFaobD_}%Ia{+1NmE8f#$^;n=5*(=?&qr z0~Ljv!t87#>-aVPCf!we8z+3bVarVP&zavAY@SfLeoM-)k(PhA`IpH7rT@x3`PMX` z8}U26zT{5Q36g)81e7Ss%Lymdc`K8jjNC!_amc?LPB>|~+FB0;rTYg(PSh-j^ zDJwUMlJetUIdP;7ky4TVjUxpU4&pD6(m$E$Aa4EHZjpH2ZSTzhW+=#bz6Sdi^fQk2 zFFa;1ViKmiF-cn^Ca>VxOe|I%i@3~xMbBSc%-!OS15*)KFJNOTOH5s2T!`giJZ0i7 zHFgO&9%2uB28xeBT+D+}I^b4(2FD>WK_xCS@f#X*X4@~ZAb~g2u@EH=B4Z%}ck?r2 z>WD+3-M+-NCPq-l^dyE%$Me?w*Vr+(pL;zXU!>gg@i*=^{&3-F;>jQq3x~Lzj%mM+ zOB@!H6nIXJZX@o3VB%@}O)PO@QT;}LynCqbaZ_N}4x>&P4A)HHwj9REb=PD=v2lSz z)Nx_iej5|k#cP580rp=V3s}d(_Uez70dk?i?5W}SkTBId#%3h%81s4tuxDxq;=Cf> zEiqX&>@KK38mBWr;<}^%q7PU+Fz|#%e@y$S|AM{3J`i*$dU9oPkbOhi{?wF!i?M>O zJ>#|_UTcl^d-cbTpE*r6rs|XP+AF5{7j#fLh)t`ppHbp)8=iaVacLlp)TspDeZlb= z^?-{CNQ~8|i5)_WRTE#8xvM`|1LQFMbSSgGN0Lgja4`wQFm)0n4*iDRDTpAU>zG#m(Nva+zbhfaUvyYP`v z`yVh|wZ{64Z2!Uj*0C#r*}D8Liw_U8sr2fSl?P-L7rV0)cFz``r@}XnSWP z*5HB`cL_G9wzz|&BMhtIl>v^!`T=B8JNJ{4+}N5fUPj_;59wE`$13K_6yEMV?rdxBk(geh9j{cvD(je=l6R;V{Y8o zkOo6Tj0($8S6<(=m~kK%UC_}TQ^WeR?F)(UpLh|2^YCIj46dWb_(^(l?i#^+f`WB1 zcoWuvq~klX{hm6v0~5L8mLXB=vNo7j=iUBn`f)t|>$r-*dW2y)O#DKWdHK({j9&o{ z;o>?x@fdl(iNC1hK5FcUJ~nkn!+F?#8>u(8JZ}3LU(m%`Fy3Eyj6ud!WPCx!zcVoh z-I#>lYk}YJVl`Ng)MfD^JpWa+_qpM-Z7*}LOyahm7z=|LApU^337bbABW9qB(Qwb1 zd(Cf+NicP?%-*BxoE9ce%GTTN$YaVRUi&?G0uy%+_VpSUATjU+Q@}R9!lcjXICch~ zfU;dY2b_Y%AK09An`5_buReygpMBQ$TaQfQwcp?dG&Vru#(iM$0u~>DHk){Ih-K%E zJEyS+1~Xvl5&VH$C$8V+#T%fW$o5CmdztGQTH>`|?ENiHz~%-kpl$iJJ--(ZP~#q6 zk9!%Li^+Rla09kpQhx;4-Aa48J*kd8vbB|4Wn5{Z( z(s@qX-{*Ma6baw8-KX~JZtVkUk6p2NmBc{d{n6TQdCgqf?vY2l_Cx-Rs|6c(*qLkF zbDK*!?a##q8`sc|wS4Z

dz=T-(yqo>O8g32wmJahkXb+Sa5%?VL70X8XNkzKlZ& z_Fu877TfSpye!7VvbN>>7(>h2$I~9w+M&`O-oz3J*=1@w8S$k~TMBIt0H8(QkNv|ICCY-efrLYsbV zXBpqV-M0Tw^oMbpXxnUUoXxT5c;%uTiP1xzTl-()5@Om-YCBl5&u7fF+U(V78!GX5 zVB;DYd&%WLWD!sQ#h%y38bZvaGU(|3d0#NrjMy#P7()>l851)w(hhRH+C4YB*7#X5 zZCc?2i}vVY{UCwyM`9&So#&LjKTqO#a_nZ?X^U*^R`r(Q^NqDZ?uE%y87Y24zJ-~)>?}FvdBH-?$y{lNZ`L+idMz_&?U}jx; zUR&QloT?P)O+enImgfeyqGMFJ_If5x1LJat{Uz-5HacIu7d;M%eWt`|}2(xF|YwXLK*n#7lowxj*VPK!26`&asE#1XOWKbkr1$iVaQmhX%SVr>SE zjjxW4!M3B_5(|TV@@a1c8!OmZI;#ez1*tzrv)z%;I*?P;>f5zNi$k$Jw7%H52DBHm z{{3MmW@9-Z*EWD986U-6&y0NLk@#Kh+i+8ruP|V2`m9Z=%Xf)E;jz)A&0QRY!mY~r7^Aq?Jlhyt+mfPQ0NyM!~?KFJlJO(jr(Y8NZT{$dQkW-ws!uqYQ#J_ zP*_s`Xtd#QJ%e#84)_AehpWE-iS=l6p^;?i$0Y%4t#Qg*KagzJJ7yCV%H(I5%d*J z+nN-KU10Rjm9O4=v1^@X-R*Gr2;MOowm+j@3lt4}Qh!GoKhJER*!~b-tZiqEuX3?l zqR(z^_a^*)7Y#P;!@Qhx^W4_3 z9fbasTninfzh2!xbpM6^1#J7S=t&>>(f6)wTP5%6OVl=L`tSKGQwQ5%Oo751Gh?W0fJHutDGSYKqjq9xjc~ znKHW;S)2VM{Mcq`+ZEcdz?Uuk+QOD))3B$*p1&D^X?Tf~%cxANbSa#tJuH6RLKP}I^>(V$5#%iCo z;V^a^&%^g8{GGCF6YMf-d;SvmgY`c$UhR_W-&VzsOi;^j9u&rNd;hF++Npv@x4C-f zZJTXx%=Xb90k#yhSJ1vnwGUMK=(P2FbNcPvHu6>N=X1Qr?PmyTs}rG27?01F&`kuJ1+Tr;)Y=o-%z*y8^L2fE|I!S(hmjdxT97zfhxn;1Az;(`+M<#^naHJn=?|w@x`NFRq z{p!K5AAH?;_VVdJ(Ao&F-1z;IYwd65vLA^($vud~lDBdolE*#{GW!#M}Ij7i-El zO519aFZ{NFcKT}5348lImNNI8^;d`TGWeU}o;Ua0+fF8_jw*_u~BH!fj~1`sdZX+S+ILvO_uc-(V~s$V5<`z+sV9!vj1`_KvVUv%CUzEPPs`npgPu{} zXr~a%M;6<)vK4R?k^6*p-#pa5nDeW$sq+`JPnOdMD8~yV@t(rBR;S=y;Ir=o-8Mbu*4W2`FBR;8$-b%Yq3FJV;_sP?ub&kA{6zDr6~18= z%X7;#-*o$`L*p8J6iM5}=ZW3x+WYwzYDJ$%YRVgN?Q8n2IdnfR+>fsMGH<-~dFJ|@ zqz@YKP5iQGA0@K)Cca6q|HggKMK^l(Z_z%>7y6kgzIDWe!?{mOKPqTj7Tc%yHQ_e} z`-9eZN)`7t(Jt(*S-%tFlc3N2w$X+k`_Dk`n;rfLr*s=~jA$)AtuWe86VC5*=C3r6=HBu?e-LTJ+rZ7js zhEppAJ1uO~oUE`uh5R_lPs1Jye)A(b4gNj}v#*c$Q#taJk#9wQg3a;1lHah?lDtXR zfaJKQ)*RmkavuHyCC3~^a?ArH$NWihylW+g-H_y%r$`PvEy>Z&$Xwb9ABf*2>2?bp zXfHl$PgBgZu?@$(+~ni~=32rR*l7u$aIf$UIro^7ky4P3#6D91?|baqr1`JDdwAd| z#?aM!Gxuhgn7*>Fps|iUvrC)zsSk3G$e+ZXy>4KJ#J(ful9*}P7s8r1_l24RYm69Z z#Z9^&k-Z_Ty=QXm-8`Q$_Z*1<%Y7Bb$^=$eu%5DJ18nd?tglBh&X@XVLt7@#CvMin zDV2Q*&hvpc)z26D^_~vq{<4nQv|GaK=k__wZ5+mRJs(mZn!N|ar^^V@58QWK#*~!j zW8Xo?2}O)w_xbMkhkS>0MZCz=WbZSg9S72Z1s(H78Zo%U-uill=1Xf^_6!oYP5Ozs zwK0de?B~T#4IC;kr@)^Ic2@aPL(gA*Rd>O?^Z9F= zcMFR_NW5(B<-nPG_B7=AW}PCeE!=O5vUiE+rk94t(o?>>A9d%8%bMPyirV#cYKxUu z11B2#whI3qY}O+qlK*;*9WjBP&2+fF&UNgT`V8($9>yy%_j|X{3FU}^`$1R7ru3*Y|One#E{ote8tczMdzPVh>1=a`{BwUj}WONw)-sj4DDau}p zV3WA+DQmQdK_PK845mrSLGSbhvi@n-2Z;TV^*gRdiA_7^t6V?MALe;}gImJoZ}wCo zb3qo*5I4+y^nZ|i>UB|_F$(p9cFz7W`e8V!%!IGN?hSBfJpW}~_MUCe( zHi5z3a6MDv7BDtJB;E;g+#2g#T(bo3W7VLL-rMoHM*l+_D38eJ>orRF(g&V~>!uPb zK-K{DTC>I~MO(*;zV_#?M+znb^99CRHqVZxe`W8@3+$Wvsg5H8EQVl6z;C@yh4rc^ z>r~q^W<*`vi;5G$T)hM_p+y%PcP*t<;8S~9XHmH){lHi&A22ScQNaN zYt@>zeS%Tx$2wplm=IzRAnz=Lmk@lA#=-$p#%zLd&Uc|KX2(suWFHrCEyb1%Mb&AJrVruHpd>Clg@)`t{#enq@Pq^#@u zyDFz}7}o1NzGr3aN&DBunv?d|xkc7bzMl4}TF^e#eSTe^73+Fgv+->lUnM?%wVgig zuEqZ=)^E73M4Rg)y>=tMS@S$TO0^GGc`ogbW3~Tqmsk3VQmP8J46k0lCH+u*g{q%g zX;al~nEz%UqnzxdG^|0?--_juY?%5Uj>NiHr;~JCno8$$kZ2x@XLFt6G$bnN;aaB% zqztbY?HdP=IHW)8YtQ}eyec*8aIHB6Iu1;eZ%g_EYYlrpepk|#vl+fSVaIu>^uTUZ zf4kErj`oC7zxI!&=S1CufhIjt6J-$X7il9H`aak7i$ATf_T!rz@B7dkq-*w;TKs(l z@eRKHsmYn*KMH=N?E4Mt544$cieN*x^71ZfWusfw<||>t=lSLhJ);}eVY)_LhuQhe zl<|xaZ@+bI{hP1t?|jkZHudp2ZQWW@R%4vAwqo`h*4FRMX|F?n zKemi^OKTh48k#5Cd3xiTHeWifGrs#wd}mt+bMeJ9;#v5#fe##{O(VvI`QV{FoVz{| zV;)LBEasa|z5{i=H>$jQ5t|tPT6UW6JYHw7_4;M8^Gh?&sWH#fc0M6LB>4kII@_Ll zJ>;{5-+vzaAR7zT(&^>Ue+4{*B;SJ~FQQ9xQ6O1hgzd_)GLgEf`PR{QhyEiHO zxmXtu&d%nyid(<%$Ldd%`sF)A$CTrD2EV7AvX2(1iY#E&v5sxyp~(Iir-cq&V?$Ej)=d-I?r$%|k zh>Y6ivcYB8%f=j5zDvOqGl$f3SL|s<#xx1dSL=p7u3DAXt1_n`=4pg4nNv93SZ za?-<8m;2QYm4vOZB!z7-g{_}*Y=31Z9mnKED(g0Y2An^je-ny^naD5;3)&q?eBp_$J>EU`{buXqy0EMp!_GR-tSP&;)Z({J zpQjx)ZLaP5@w!WI@yr`T?|SW1jWOikcl@w_%o~ty){VIaY|~UZ&$Z!oW*;MREz-76 z;+S)Ozs$0MBy$P7 zUK;7w(X9_Ylk{9b$Nq)gr>xb9{VLws;u}QPhw@br@4of-jTIe;)jrMv7zH?cN~day3(cw-)l?Hl{! z)#x{7zGCY`T*<(G*xF$YPDw0Otg&D{Mr>b$m`|)46tY?8yS>Jm7vAsai#}UlMb?_m zSl0@=b0^xHfj+KhalO>*yMyuCqP)+8k?Sa2BZ3`nhS=1h9_tH)??J>#b8;Vf-i;6U zugh=F3i+IzgQ9?AGw;5rZeL2#`_?L&+vy~d;0cWQh8TiR{T_(AZ~5EQ!{ z*B%FPatgMT6h4>H*_M2JKvd=8+5vQPC?_?GaOjIEWF>ucX@#CBhE=>EaCXxH7)X+f7jFU-UyVA z(RQ18_!cf|(?{hrxkG;sd+{0^hnr%rw@E6OR^$)whyZa_cwfjIc2T37!`>0xvs`)c zO>TdbevUfq{rsks?@qiI?Dx#lPOyV`bb{b@mUPsyFXxOkI^4Gn++brYsYP$&+jn$ z@Dq%H8KW6*yPV@O_m6PA`EhOU@8V!Q7oq`5%tGkuPfgYtFAeMq@Tc~15T P@DosfiStvdht>ZF38vZ$ literal 0 HcmV?d00001 diff --git a/src/MSBuildTaskHost/MSBuild/NodeEndpointOutOfProcTaskHost.cs b/src/MSBuildTaskHost/MSBuild/NodeEndpointOutOfProcTaskHost.cs new file mode 100644 index 00000000000..9769d7b6b59 --- /dev/null +++ b/src/MSBuildTaskHost/MSBuild/NodeEndpointOutOfProcTaskHost.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Build.BackEnd; +using Microsoft.Build.Framework; +using Microsoft.Build.Internal; + +#nullable disable + +namespace Microsoft.Build.CommandLine +{ + ///

+ /// This is an implementation of INodeEndpoint for the out-of-proc nodes. It acts only as a client. + /// + internal class NodeEndpointOutOfProcTaskHost : NodeEndpointOutOfProcBase + { + internal bool _nodeReuse; + + #region Constructors and Factories + + /// + /// Instantiates an endpoint to act as a client. + /// + /// Whether node reuse is enabled. + /// The packet version supported by the parent. 1 if parent doesn't support version negotiation. + internal NodeEndpointOutOfProcTaskHost(bool nodeReuse, byte parentPacketVersion) + { + _nodeReuse = nodeReuse; + InternalConstruct(pipeName: null, parentPacketVersion); + } + + #endregion // Constructors and Factories + + /// + /// Returns the host handshake for this node endpoint + /// + protected override Handshake GetHandshake() => + new(CommunicationsUtilities.GetHandshakeOptions(taskHost: true, taskHostParameters: TaskHostParameters.Empty, nodeReuse: _nodeReuse)); + } +} diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs new file mode 100644 index 00000000000..ae77fb43e03 --- /dev/null +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs @@ -0,0 +1,455 @@ +// 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 FEATURE_APPDOMAIN +using System.Threading; +#endif +using System.Reflection; + +using Microsoft.Build.BackEnd; +using Microsoft.Build.Framework; +using Microsoft.Build.Shared; +#if !NET35 +using Microsoft.Build.Execution; +#endif + +#nullable disable + +namespace Microsoft.Build.CommandLine +{ + /// + /// Class for executing a task in an AppDomain + /// + [Serializable] + internal class OutOfProcTaskAppDomainWrapperBase +#if FEATURE_APPDOMAIN + : MarshalByRefObject +#endif + { + /// + /// This is the actual user task whose instance we will create and invoke Execute + /// + private ITask wrappedTask; + + +#if FEATURE_APPDOMAIN + /// + /// This is an appDomain instance if any is created for running this task + /// + /// + /// TaskAppDomain's non-serializability should never be an issue since even if we start running the wrapper + /// in a separate appdomain, we will not be trying to load the task on one side of the serialization + /// boundary and run it on the other. + /// + [NonSerialized] + private AppDomain _taskAppDomain; +#endif + + /// + /// Need to keep the build engine around in order to log from the task loader. + /// + private IBuildEngine buildEngine; + + /// + /// Need to keep track of the task name also so that we can log valid information + /// from the task loader. + /// + private string taskName; + +#if !NET35 + private HostServices _hostServices; +#endif + + /// + /// This is the actual user task whose instance we will create and invoke Execute + /// + public ITask WrappedTask + { + get { return wrappedTask; } + } + + /// + /// We have a cancel already requested + /// This can happen before we load the module and invoke execute. + /// + internal bool CancelPending + { + get; + set; + } + + /// + /// This is responsible for invoking Execute on the Task + /// Any method calling ExecuteTask must remember to call CleanupTask + /// + /// + /// We also allow the Task to have a reference to the BuildEngine by design + /// at ITask.BuildEngine + /// + /// The OutOfProcTaskHostNode as the BuildEngine + /// The name of the task to be executed + /// The path of the task binary + /// The path to the project file in which the task invocation is located. + /// The line in the project file where the task invocation is located. + /// The column in the project file where the task invocation is located. + /// The target name that invokes this task. + /// The AppDomainSetup that we want to use to launch our AppDomainIsolated tasks + /// Parameters that will be passed to the task when created + /// Task completion result showing success, failure or if there was a crash + internal OutOfProcTaskHostTaskResult ExecuteTask( + IBuildEngine oopTaskHostNode, + string taskName, + string taskLocation, + string taskFile, + int taskLine, + int taskColumn, + string targetName, + string projectFile, +#if FEATURE_APPDOMAIN + AppDomainSetup appDomainSetup, +#endif +#if !NET35 + HostServices hostServices, +#endif + IDictionary taskParams) + { + buildEngine = oopTaskHostNode; + this.taskName = taskName; + +#if FEATURE_APPDOMAIN + _taskAppDomain = null; +#endif +#if !NET35 + _hostServices = hostServices; +#endif + wrappedTask = null; + + LoadedType taskType = null; + try + { + TypeLoader typeLoader = new(TaskLoader.IsTaskClass); + taskType = typeLoader.Load( + taskName, + AssemblyLoadInfo.Create(null, taskLocation), + logWarning: (format, args) => { }, + useTaskHost: false, + taskHostParamsMatchCurrentProc: true); + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + // If it's a TargetInvocationException, we only care about the contents of the inner exception, + // so just save that instead. + Exception exceptionToReturn = e is TargetInvocationException ? e.InnerException : e; + + return new OutOfProcTaskHostTaskResult( + TaskCompleteType.CrashedDuringInitialization, + exceptionToReturn, + "TaskInstantiationFailureError", + [taskName, taskLocation, String.Empty]); + } + + OutOfProcTaskHostTaskResult taskResult; + if (taskType.HasSTAThreadAttribute) + { +#if FEATURE_APARTMENT_STATE + taskResult = InstantiateAndExecuteTaskInSTAThread( + oopTaskHostNode, + taskType, + taskName, + taskLocation, + taskFile, + taskLine, + taskColumn, + targetName, + projectFile, +#if FEATURE_APPDOMAIN + appDomainSetup, +#endif + taskParams); +#else + return new OutOfProcTaskHostTaskResult( + TaskCompleteType.CrashedDuringInitialization, + null, + "TaskInstantiationFailureNotSupported", + [taskName, taskLocation, typeof(RunInSTAAttribute).FullName]); +#endif + } + else + { + taskResult = InstantiateAndExecuteTask( + oopTaskHostNode, + taskType, + taskName, + taskLocation, + taskFile, + taskLine, + taskColumn, + targetName, + projectFile, +#if FEATURE_APPDOMAIN + appDomainSetup, +#endif + taskParams); + } + + return taskResult; + } + + /// + /// This is responsible for cleaning up the task after the OutOfProcTaskHostNode has gathered everything it needs from this execution + /// For example: We will need to hold on new AppDomains created until we finish getting all outputs from the task + /// Add any other cleanup tasks here. Any method calling ExecuteTask must remember to call CleanupTask. + /// + internal void CleanupTask() + { +#if FEATURE_APPDOMAIN + if (_taskAppDomain != null) + { + AppDomain.Unload(_taskAppDomain); + } + + TaskLoader.RemoveAssemblyResolver(); +#endif + wrappedTask = null; + } + +#if FEATURE_APARTMENT_STATE + /// + /// Execute a task on the STA thread. + /// + /// + /// STA thread launching code lifted from XMakeBuildEngine\BackEnd\Components\RequestBuilder\TaskBuilder.cs, ExecuteTaskInSTAThread method. + /// Any bug fixes made to this code, please ensure that you also fix that code. + /// + private OutOfProcTaskHostTaskResult InstantiateAndExecuteTaskInSTAThread( + IBuildEngine oopTaskHostNode, + LoadedType taskType, + string taskName, + string taskLocation, + string taskFile, + int taskLine, + int taskColumn, + string targetName, + string projectFile, +#if FEATURE_APPDOMAIN + AppDomainSetup appDomainSetup, +#endif + IDictionary taskParams) + { + ManualResetEvent taskRunnerFinished = new ManualResetEvent(false); + OutOfProcTaskHostTaskResult taskResult = null; + Exception exceptionFromExecution = null; + + try + { + ThreadStart taskRunnerDelegate = delegate () + { + try + { + taskResult = InstantiateAndExecuteTask( + oopTaskHostNode, + taskType, + taskName, + taskLocation, + taskFile, + taskLine, + taskColumn, + targetName, + projectFile, +#if FEATURE_APPDOMAIN + appDomainSetup, +#endif + taskParams); + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + exceptionFromExecution = e; + } + finally + { + taskRunnerFinished.Set(); + } + }; + + Thread staThread = new Thread(taskRunnerDelegate); + staThread.SetApartmentState(ApartmentState.STA); + staThread.Name = "MSBuild STA task runner thread"; + staThread.CurrentCulture = Thread.CurrentThread.CurrentCulture; + staThread.CurrentUICulture = Thread.CurrentThread.CurrentUICulture; + staThread.Start(); + + // TODO: Why not just Join on the thread??? + taskRunnerFinished.WaitOne(); + } + finally + { +#if CLR2COMPATIBILITY + taskRunnerFinished.Close(); +#else + taskRunnerFinished.Dispose(); +#endif + taskRunnerFinished = null; + } + + if (exceptionFromExecution != null) + { + // Unfortunately this will reset the callstack + throw exceptionFromExecution; + } + + return taskResult; + } +#endif + + /// + /// Do the work of actually instantiating and running the task. + /// + private OutOfProcTaskHostTaskResult InstantiateAndExecuteTask( + IBuildEngine oopTaskHostNode, + LoadedType taskType, + string taskName, + string taskLocation, + string taskFile, + int taskLine, + int taskColumn, + string targetName, + string projectFile, +#if FEATURE_APPDOMAIN + AppDomainSetup appDomainSetup, +#endif + IDictionary taskParams) + { +#if FEATURE_APPDOMAIN + _taskAppDomain = null; +#endif + wrappedTask = null; + + try + { +#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter + wrappedTask = TaskLoader.CreateTask( + taskType, + taskName, + taskFile, + taskLine, + taskColumn, + new TaskLoader.LogError(LogErrorDelegate), +#if FEATURE_APPDOMAIN + appDomainSetup, + // custom app domain assembly loading won't be available for task host + null, +#endif + true /* always out of proc */ +#if FEATURE_APPDOMAIN + , out _taskAppDomain +#endif + ); +#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter + +#if !NET35 + if (projectFile != null && _hostServices != null) + { + wrappedTask.HostObject = _hostServices.GetHostObject(projectFile, targetName, taskName); + } +#endif + + wrappedTask.BuildEngine = oopTaskHostNode; + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + Exception exceptionToReturn = e; + + // If it's a TargetInvocationException, we only care about the contents of the inner exception, + // so just save that instead. + if (e is TargetInvocationException) + { + exceptionToReturn = e.InnerException; + } + + return new OutOfProcTaskHostTaskResult( + TaskCompleteType.CrashedDuringInitialization, + exceptionToReturn, + "TaskInstantiationFailureError", + [taskName, taskLocation, String.Empty]); + } + + foreach (KeyValuePair param in taskParams) + { + try + { + PropertyInfo paramInfo = wrappedTask.GetType().GetProperty(param.Key, BindingFlags.Instance | BindingFlags.Public); + paramInfo.SetValue(wrappedTask, param.Value?.WrappedParameter, null); + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + return new OutOfProcTaskHostTaskResult( + TaskCompleteType.CrashedDuringInitialization, + // If it's a TargetInvocationException, we only care about the contents of the inner exception, so save that instead. + e is TargetInvocationException ? e.InnerException : e, + "InvalidTaskAttributeError", + [param.Key, param.Value.ToString(), taskName]); + } + } + + bool success = false; + try + { + if (CancelPending) + { + return new OutOfProcTaskHostTaskResult(TaskCompleteType.Failure); + } + + // If it didn't crash and return before now, we're clear to go ahead and execute here. + success = wrappedTask.Execute(); + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + return new OutOfProcTaskHostTaskResult(TaskCompleteType.CrashedDuringExecution, e); + } + + PropertyInfo[] finalPropertyValues = wrappedTask.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); + + IDictionary finalParameterValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (PropertyInfo value in finalPropertyValues) + { + // only record outputs + if (value.GetCustomAttributes(typeof(OutputAttribute), true).Length > 0) + { + try + { + finalParameterValues[value.Name] = value.GetValue(wrappedTask, null); + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + // If it's not a critical exception, we assume there's some sort of problem in the parameter getter -- + // so save the exception, and we'll re-throw once we're back on the main node side of the + // communications pipe. + finalParameterValues[value.Name] = e; + } + } + } + + return new OutOfProcTaskHostTaskResult(success ? TaskCompleteType.Success : TaskCompleteType.Failure, finalParameterValues); + } + + /// + /// Logs errors from TaskLoader + /// + private void LogErrorDelegate(string taskLocation, int taskLine, int taskColumn, string message, params object[] messageArgs) + { + buildEngine.LogErrorEvent(new BuildErrorEventArgs( + null, + null, + taskLocation, + taskLine, + taskColumn, + 0, + 0, + ResourceUtilities.FormatString(AssemblyResources.GetString(message), messageArgs), + null, + taskName)); + } + } +} diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs new file mode 100644 index 00000000000..aa7afd2efa4 --- /dev/null +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs @@ -0,0 +1,1296 @@ +// 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; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Threading; +using Microsoft.Build.BackEnd; +using Microsoft.Build.Execution; +using Microsoft.Build.Framework; +#if !CLR2COMPATIBILITY +using Microsoft.Build.Experimental.FileAccess; +#endif +using Microsoft.Build.Internal; +using Microsoft.Build.Shared; +#if FEATURE_APPDOMAIN +using System.Runtime.Remoting; +#endif + +#nullable disable + +namespace Microsoft.Build.CommandLine +{ + /// + /// This class represents an implementation of INode for out-of-proc node for hosting tasks. + /// + internal class OutOfProcTaskHostNode : +#if FEATURE_APPDOMAIN + MarshalByRefObject, +#endif + INodePacketFactory, INodePacketHandler, +#if CLR2COMPATIBILITY + IBuildEngine3 +#else + IBuildEngine10 +#endif + { + /// + /// Keeps a record of all environment variables that, on startup of the task host, have a different + /// value from those that are passed to the task host in the configuration packet for the first task. + /// These environments are assumed to be effectively identical, so the only difference between the + /// two sets of values should be any environment variables that differ between e.g. a 32-bit and a 64-bit + /// process. Those are the variables that this dictionary should store. + /// + /// - The key into the dictionary is the name of the environment variable. + /// - The Key of the KeyValuePair is the value of the variable in the parent process -- the value that we + /// wish to ensure is replaced by whatever the correct value in our current process is. + /// - The Value of the KeyValuePair is the value of the variable in the current process -- the value that + /// we wish to replay the Key value with in the environment that we receive from the parent before + /// applying it to the current process. + /// + /// Note that either value in the KeyValuePair can be null, as it is completely possible to have an + /// environment variable that is set in 32-bit processes but not in 64-bit, or vice versa. + /// + /// This dictionary must be static because otherwise, if a node is sitting around waiting for reuse, it will + /// have inherited the environment from the previous build, and any differences between the two will be seen + /// as "legitimate". There is no way for us to know what the differences between the startup environment of + /// the previous build and the environment of the first task run in the task host in this build -- so we + /// must assume that the 4ish system environment variables that this is really meant to catch haven't + /// somehow magically changed between two builds spaced no more than 15 minutes apart. + /// + private static IDictionary> s_mismatchedEnvironmentValues; + + /// + /// The endpoint used to talk to the host. + /// + private NodeEndpointOutOfProcTaskHost _nodeEndpoint; + + /// + /// The packet factory. + /// + private NodePacketFactory _packetFactory; + + /// + /// The event which is set when we receive packets. + /// + private AutoResetEvent _packetReceivedEvent; + + /// + /// The queue of packets we have received but which have not yet been processed. + /// + private Queue _receivedPackets; + + /// + /// The current configuration for this task host. + /// + private TaskHostConfiguration _currentConfiguration; + + /// + /// The saved environment for the process. + /// + private IDictionary _savedEnvironment; + + /// + /// The event which is set when we should shut down. + /// + private ManualResetEvent _shutdownEvent; + + /// + /// The reason we are shutting down. + /// + private NodeEngineShutdownReason _shutdownReason; + + /// + /// We set this flag to track a currently executing task + /// + private bool _isTaskExecuting; + + /// + /// The event which is set when a task has completed. + /// + private AutoResetEvent _taskCompleteEvent; + + /// + /// Packet containing all the information relating to the + /// completed state of the task. + /// + private TaskHostTaskComplete _taskCompletePacket; + + /// + /// Object used to synchronize access to taskCompletePacket + /// + private LockType _taskCompleteLock = new(); + + /// + /// The event which is set when a task is cancelled + /// + private ManualResetEvent _taskCancelledEvent; + + /// + /// The thread currently executing user task in the TaskRunner + /// + private Thread _taskRunnerThread; + + /// + /// This is the wrapper for the user task to be executed. + /// We are providing a wrapper to create a possibility of executing the task in a separate AppDomain + /// + private OutOfProcTaskAppDomainWrapper _taskWrapper; + + /// + /// Flag indicating if we should debug communications or not. + /// + private bool _debugCommunications; + + /// + /// Flag indicating whether we should modify the environment based on any differences we find between that of the + /// task host at startup and the environment passed to us in our initial task configuration packet. + /// + private bool _updateEnvironment; + + /// + /// An interim step between MSBuildTaskHostDoNotUpdateEnvironment=1 and the default update behavior: go ahead and + /// do all the updates that we would otherwise have done by default, but log any updates that are made (at low + /// importance) so that the user is aware. + /// + private bool _updateEnvironmentAndLog; + + /// + /// setting this to true means we're running a long-lived sidecar node. + /// + private bool _nodeReuse; + +#if !CLR2COMPATIBILITY + /// + /// The task object cache. + /// + private RegisteredTaskObjectCacheBase _registeredTaskObjectCache; +#endif + +#if FEATURE_REPORTFILEACCESSES + /// + /// The file accesses reported by the most recently completed task. + /// + private List _fileAccessData = new List(); +#endif + + /// + /// Constructor. + /// + public OutOfProcTaskHostNode() + { + // We don't know what the current build thinks this variable should be until RunTask(), but as a fallback in case there are + // communications before we get the configuration set up, just go with what was already in the environment from when this node + // was initially launched. + _debugCommunications = Traits.Instance.DebugNodeCommunication; + + _receivedPackets = new Queue(); + + // These WaitHandles are disposed in HandleShutDown() + _packetReceivedEvent = new AutoResetEvent(false); + _shutdownEvent = new ManualResetEvent(false); + _taskCompleteEvent = new AutoResetEvent(false); + _taskCancelledEvent = new ManualResetEvent(false); + + _packetFactory = new NodePacketFactory(); + + INodePacketFactory thisINodePacketFactory = (INodePacketFactory)this; + + thisINodePacketFactory.RegisterPacketHandler(NodePacketType.TaskHostConfiguration, TaskHostConfiguration.FactoryForDeserialization, this); + thisINodePacketFactory.RegisterPacketHandler(NodePacketType.TaskHostTaskCancelled, TaskHostTaskCancelled.FactoryForDeserialization, this); + thisINodePacketFactory.RegisterPacketHandler(NodePacketType.NodeBuildComplete, NodeBuildComplete.FactoryForDeserialization, this); + +#if !CLR2COMPATIBILITY + EngineServices = new EngineServicesImpl(this); +#endif + } + + #region IBuildEngine Implementation (Properties) + + /// + /// Returns the value of ContinueOnError for the currently executing task. + /// + public bool ContinueOnError + { + get + { + ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration during a BuildEngine callback!"); + return _currentConfiguration.ContinueOnError; + } + } + + /// + /// Returns the line number of the location in the project file of the currently executing task. + /// + public int LineNumberOfTaskNode + { + get + { + ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration during a BuildEngine callback!"); + return _currentConfiguration.LineNumberOfTask; + } + } + + /// + /// Returns the column number of the location in the project file of the currently executing task. + /// + public int ColumnNumberOfTaskNode + { + get + { + ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration during a BuildEngine callback!"); + return _currentConfiguration.ColumnNumberOfTask; + } + } + + /// + /// Returns the project file of the currently executing task. + /// + public string ProjectFileOfTaskNode + { + get + { + ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration during a BuildEngine callback!"); + return _currentConfiguration.ProjectFileOfTask; + } + } + + #endregion // IBuildEngine Implementation (Properties) + + #region IBuildEngine2 Implementation (Properties) + + /// + /// Stub implementation of IBuildEngine2.IsRunningMultipleNodes. The task host does not support this sort of + /// IBuildEngine callback, so error. + /// + public bool IsRunningMultipleNodes + { + get + { + LogErrorFromResource("BuildEngineCallbacksInTaskHostUnsupported"); + return false; + } + } + + #endregion // IBuildEngine2 Implementation (Properties) + + #region IBuildEngine7 Implementation + /// + /// Enables or disables emitting a default error when a task fails without logging errors + /// + public bool AllowFailureWithoutError { get; set; } = false; + #endregion + + #region IBuildEngine8 Implementation + + /// + /// Contains all warnings that should be logged as errors. + /// Non-null empty set when all warnings should be treated as errors. + /// + private ICollection WarningsAsErrors { get; set; } + + private ICollection WarningsNotAsErrors { get; set; } + + private ICollection WarningsAsMessages { get; set; } + + public bool ShouldTreatWarningAsError(string warningCode) + { + // Warnings as messages overrides warnings as errors. + if (WarningsAsErrors == null || WarningsAsMessages?.Contains(warningCode) == true) + { + return false; + } + + return (WarningsAsErrors.Count == 0 && WarningAsErrorNotOverriden(warningCode)) || WarningsAsMessages.Contains(warningCode); + } + + private bool WarningAsErrorNotOverriden(string warningCode) + { + return WarningsNotAsErrors?.Contains(warningCode) != true; + } + #endregion + + #region IBuildEngine Implementation (Methods) + + /// + /// Sends the provided error back to the parent node to be logged, tagging it with + /// the parent node's ID so that, as far as anyone is concerned, it might as well have + /// just come from the parent node to begin with. + /// + public void LogErrorEvent(BuildErrorEventArgs e) + { + SendBuildEvent(e); + } + + /// + /// Sends the provided warning back to the parent node to be logged, tagging it with + /// the parent node's ID so that, as far as anyone is concerned, it might as well have + /// just come from the parent node to begin with. + /// + public void LogWarningEvent(BuildWarningEventArgs e) + { + SendBuildEvent(e); + } + + /// + /// Sends the provided message back to the parent node to be logged, tagging it with + /// the parent node's ID so that, as far as anyone is concerned, it might as well have + /// just come from the parent node to begin with. + /// + public void LogMessageEvent(BuildMessageEventArgs e) + { + SendBuildEvent(e); + } + + /// + /// Sends the provided custom event back to the parent node to be logged, tagging it with + /// the parent node's ID so that, as far as anyone is concerned, it might as well have + /// just come from the parent node to begin with. + /// + public void LogCustomEvent(CustomBuildEventArgs e) + { + SendBuildEvent(e); + } + + /// + /// Stub implementation of IBuildEngine.BuildProjectFile. The task host does not support IBuildEngine + /// callbacks for the purposes of building projects, so error. + /// + public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs) + { + LogErrorFromResource("BuildEngineCallbacksInTaskHostUnsupported"); + return false; + } + + #endregion // IBuildEngine Implementation (Methods) + + #region IBuildEngine2 Implementation (Methods) + + /// + /// Stub implementation of IBuildEngine2.BuildProjectFile. The task host does not support IBuildEngine + /// callbacks for the purposes of building projects, so error. + /// + public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion) + { + LogErrorFromResource("BuildEngineCallbacksInTaskHostUnsupported"); + return false; + } + + /// + /// Stub implementation of IBuildEngine2.BuildProjectFilesInParallel. The task host does not support IBuildEngine + /// callbacks for the purposes of building projects, so error. + /// + public bool BuildProjectFilesInParallel(string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IDictionary[] targetOutputsPerProject, string[] toolsVersion, bool useResultsCache, bool unloadProjectsOnCompletion) + { + LogErrorFromResource("BuildEngineCallbacksInTaskHostUnsupported"); + return false; + } + + #endregion // IBuildEngine2 Implementation (Methods) + + #region IBuildEngine3 Implementation + + /// + /// Stub implementation of IBuildEngine3.BuildProjectFilesInParallel. The task host does not support IBuildEngine + /// callbacks for the purposes of building projects, so error. + /// + public BuildEngineResult BuildProjectFilesInParallel(string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IList[] removeGlobalProperties, string[] toolsVersion, bool returnTargetOutputs) + { + LogErrorFromResource("BuildEngineCallbacksInTaskHostUnsupported"); + return new BuildEngineResult(false, null); + } + + /// + /// Stub implementation of IBuildEngine3.Yield. The task host does not support yielding, so just go ahead and silently + /// return, letting the task continue. + /// + public void Yield() + { + return; + } + + /// + /// Stub implementation of IBuildEngine3.Reacquire. The task host does not support yielding, so just go ahead and silently + /// return, letting the task continue. + /// + public void Reacquire() + { + return; + } + + #endregion // IBuildEngine3 Implementation + +#if !CLR2COMPATIBILITY + #region IBuildEngine4 Implementation + + /// + /// Registers an object with the system that will be disposed of at some specified time + /// in the future. + /// + /// The key used to retrieve the object. + /// The object to be held for later disposal. + /// The lifetime of the object. + /// The object may be disposed earlier that the requested time if + /// MSBuild needs to reclaim memory. + public void RegisterTaskObject(object key, object obj, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection) + { + _registeredTaskObjectCache.RegisterTaskObject(key, obj, lifetime, allowEarlyCollection); + } + + /// + /// Retrieves a previously registered task object stored with the specified key. + /// + /// The key used to retrieve the object. + /// The lifetime of the object. + /// + /// The registered object, or null is there is no object registered under that key or the object + /// has been discarded through early collection. + /// + public object GetRegisteredTaskObject(object key, RegisteredTaskObjectLifetime lifetime) + { + return _registeredTaskObjectCache.GetRegisteredTaskObject(key, lifetime); + } + + /// + /// Unregisters a previously-registered task object. + /// + /// The key used to retrieve the object. + /// The lifetime of the object. + /// + /// The registered object, or null is there is no object registered under that key or the object + /// has been discarded through early collection. + /// + public object UnregisterTaskObject(object key, RegisteredTaskObjectLifetime lifetime) + { + return _registeredTaskObjectCache.UnregisterTaskObject(key, lifetime); + } + + #endregion + + #region IBuildEngine5 Implementation + + /// + /// Logs a telemetry event. + /// + /// The event name. + /// The list of properties associated with the event. + public void LogTelemetry(string eventName, IDictionary properties) + { + SendBuildEvent(new TelemetryEventArgs + { + EventName = eventName, + Properties = properties == null ? new Dictionary() : new Dictionary(properties), + }); + } + + #endregion + + #region IBuildEngine6 Implementation + + /// + /// Gets the global properties for the current project. + /// + /// An containing the global properties of the current project. + public IReadOnlyDictionary GetGlobalProperties() + { + return new Dictionary(_currentConfiguration.GlobalProperties); + } + + #endregion + + #region IBuildEngine9 Implementation + + public int RequestCores(int requestedCores) + { + // No resource management in OOP nodes + throw new NotImplementedException(); + } + + public void ReleaseCores(int coresToRelease) + { + // No resource management in OOP nodes + throw new NotImplementedException(); + } + + #endregion + + #region IBuildEngine10 Members + + [Serializable] + private sealed class EngineServicesImpl : EngineServices + { + private readonly OutOfProcTaskHostNode _taskHost; + + internal EngineServicesImpl(OutOfProcTaskHostNode taskHost) + { + _taskHost = taskHost; + } + + /// + /// No logging verbosity optimization in OOP nodes. + /// + public override bool LogsMessagesOfImportance(MessageImportance importance) => true; + + /// + public override bool IsTaskInputLoggingEnabled + { + get + { + ErrorUtilities.VerifyThrow(_taskHost._currentConfiguration != null, "We should never have a null configuration during a BuildEngine callback!"); + return _taskHost._currentConfiguration.IsTaskInputLoggingEnabled; + } + } + +#if FEATURE_REPORTFILEACCESSES + /// + /// Reports a file access from a task. + /// + /// The file access to report. + public void ReportFileAccess(FileAccessData fileAccessData) + { + _taskHost._fileAccessData.Add(fileAccessData); + } +#endif + } + + public EngineServices EngineServices { get; } + + #endregion + +#endif + + #region INodePacketFactory Members + + /// + /// Registers the specified handler for a particular packet type. + /// + /// The packet type. + /// The factory for packets of the specified type. + /// The handler to be called when packets of the specified type are received. + public void RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory, INodePacketHandler handler) + { + _packetFactory.RegisterPacketHandler(packetType, factory, handler); + } + + /// + /// Unregisters a packet handler. + /// + /// The packet type. + public void UnregisterPacketHandler(NodePacketType packetType) + { + _packetFactory.UnregisterPacketHandler(packetType); + } + + /// + /// Takes a serializer, deserializes the packet and routes it to the appropriate handler. + /// + /// The node from which the packet was received. + /// The packet type. + /// The translator containing the data from which the packet should be reconstructed. + public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITranslator translator) + { + _packetFactory.DeserializeAndRoutePacket(nodeId, packetType, translator); + } + + /// + /// Takes a serializer and deserializes the packet. + /// + /// The packet type. + /// The translator containing the data from which the packet should be reconstructed. + public INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator) + { + return _packetFactory.DeserializePacket(packetType, translator); + } + + /// + /// Routes the specified packet + /// + /// The node from which the packet was received. + /// The packet to route. + public void RoutePacket(int nodeId, INodePacket packet) + { + _packetFactory.RoutePacket(nodeId, packet); + } + + #endregion // INodePacketFactory Members + + #region INodePacketHandler Members + + /// + /// This method is invoked by the NodePacketRouter when a packet is received and is intended for + /// this recipient. + /// + /// The node from which the packet was received. + /// The packet. + public void PacketReceived(int node, INodePacket packet) + { + lock (_receivedPackets) + { + _receivedPackets.Enqueue(packet); + _packetReceivedEvent.Set(); + } + } + + #endregion // INodePacketHandler Members + + #region INode Members + + /// + /// Starts up the node and processes messages until the node is requested to shut down. + /// + /// The exception which caused shutdown, if any. + /// The reason for shutting down. + public NodeEngineShutdownReason Run(out Exception shutdownException, bool nodeReuse = false, byte parentPacketVersion = 1) + { +#if !CLR2COMPATIBILITY + _registeredTaskObjectCache = new RegisteredTaskObjectCacheBase(); +#endif + shutdownException = null; + + // Snapshot the current environment + _savedEnvironment = CommunicationsUtilities.GetEnvironmentVariables(); + + _nodeReuse = nodeReuse; + _nodeEndpoint = new NodeEndpointOutOfProcTaskHost(nodeReuse, parentPacketVersion); + _nodeEndpoint.OnLinkStatusChanged += new LinkStatusChangedDelegate(OnLinkStatusChanged); + _nodeEndpoint.Listen(this); + + WaitHandle[] waitHandles = [_shutdownEvent, _packetReceivedEvent, _taskCompleteEvent, _taskCancelledEvent]; + + while (true) + { + int index = WaitHandle.WaitAny(waitHandles); + switch (index) + { + case 0: // shutdownEvent + NodeEngineShutdownReason shutdownReason = HandleShutdown(); + return shutdownReason; + + case 1: // packetReceivedEvent + INodePacket packet = null; + + int packetCount = _receivedPackets.Count; + + while (packetCount > 0) + { + lock (_receivedPackets) + { + if (_receivedPackets.Count > 0) + { + packet = _receivedPackets.Dequeue(); + } + else + { + break; + } + } + + if (packet != null) + { + HandlePacket(packet); + } + } + + break; + case 2: // taskCompleteEvent + CompleteTask(); + break; + case 3: // taskCancelledEvent + CancelTask(); + break; + } + } + + // UNREACHABLE + } + #endregion + + /// + /// Dispatches the packet to the correct handler. + /// + private void HandlePacket(INodePacket packet) + { + switch (packet.Type) + { + case NodePacketType.TaskHostConfiguration: + HandleTaskHostConfiguration(packet as TaskHostConfiguration); + break; + case NodePacketType.TaskHostTaskCancelled: + _taskCancelledEvent.Set(); + break; + case NodePacketType.NodeBuildComplete: + HandleNodeBuildComplete(packet as NodeBuildComplete); + break; + } + } + + /// + /// Configure the task host according to the information received in the + /// configuration packet + /// + private void HandleTaskHostConfiguration(TaskHostConfiguration taskHostConfiguration) + { + ErrorUtilities.VerifyThrow(!_isTaskExecuting, "Why are we getting a TaskHostConfiguration packet while we're still executing a task?"); + _currentConfiguration = taskHostConfiguration; + + // Kick off the task running thread. + _taskRunnerThread = new Thread(new ParameterizedThreadStart(RunTask)); + _taskRunnerThread.Name = "Task runner for task " + taskHostConfiguration.TaskName; + _taskRunnerThread.Start(taskHostConfiguration); + } + + /// + /// The task has been completed + /// + private void CompleteTask() + { + ErrorUtilities.VerifyThrow(!_isTaskExecuting, "The task should be done executing before CompleteTask."); + if (_nodeEndpoint.LinkStatus == LinkStatus.Active) + { + TaskHostTaskComplete taskCompletePacketToSend; + + lock (_taskCompleteLock) + { + ErrorUtilities.VerifyThrowInternalNull(_taskCompletePacket, "taskCompletePacket"); + taskCompletePacketToSend = _taskCompletePacket; + _taskCompletePacket = null; + } + + _nodeEndpoint.SendData(taskCompletePacketToSend); + } + + _currentConfiguration = null; + + // If the task has been canceled, the event will still be set. + // If so, now that we've completed the task, we want to shut down + // this node -- with no reuse, since we don't know whether the + // task we canceled left the node in a good state or not. + if (_taskCancelledEvent.WaitOne(0)) + { + _shutdownReason = NodeEngineShutdownReason.BuildComplete; + _shutdownEvent.Set(); + } + } + + /// + /// This task has been cancelled. Attempt to cancel the task + /// + private void CancelTask() + { + // If the task is an ICancellable task in CLR4 we will call it here and wait for it to complete + // Otherwise it's a classic ITask. + + // Store in a local to avoid a race + var wrapper = _taskWrapper; + if (wrapper?.CancelTask() == false) + { + // Create a possibility for the task to be aborted if the user really wants it dropped dead asap + if (Environment.GetEnvironmentVariable("MSBUILDTASKHOSTABORTTASKONCANCEL") == "1") + { + // Don't bother aborting the task if it has passed the actual user task Execute() + // It means we're already in the process of shutting down - Wait for the taskCompleteEvent to be set instead. + if (_isTaskExecuting) + { +#if FEATURE_THREAD_ABORT + // The thread will be terminated crudely so our environment may be trashed but it's ok since we are + // shutting down ASAP. + _taskRunnerThread.Abort(); +#endif + } + } + } + } + + /// + /// Handles the NodeBuildComplete packet. + /// + private void HandleNodeBuildComplete(NodeBuildComplete buildComplete) + { + ErrorUtilities.VerifyThrow(!_isTaskExecuting, "We should never have a task in the process of executing when we receive NodeBuildComplete."); + + // Sidecar TaskHost will persist after the build is done. + if (_nodeReuse) + { + _shutdownReason = NodeEngineShutdownReason.BuildCompleteReuse; + } + else + { + // TaskHostNodes lock assemblies with custom tasks produced by build scripts if NodeReuse is on. This causes failures if the user builds twice. + _shutdownReason = buildComplete.PrepareForReuse && Traits.Instance.EscapeHatches.ReuseTaskHostNodes ? NodeEngineShutdownReason.BuildCompleteReuse : NodeEngineShutdownReason.BuildComplete; + } + _shutdownEvent.Set(); + } + + /// + /// Perform necessary actions to shut down the node. + /// + private NodeEngineShutdownReason HandleShutdown() + { + // Wait for the RunTask task runner thread before shutting down so that we can cleanly dispose all WaitHandles. + _taskRunnerThread?.Join(); + + using StreamWriter debugWriter = _debugCommunications + ? File.CreateText(string.Format(CultureInfo.CurrentCulture, Path.Combine(FileUtilities.TempFileDirectory, @"MSBuild_NodeShutdown_{0}.txt"), EnvironmentUtilities.CurrentProcessId)) + : null; + + debugWriter?.WriteLine("Node shutting down with reason {0}.", _shutdownReason); + +#if !CLR2COMPATIBILITY + _registeredTaskObjectCache.DisposeCacheObjects(RegisteredTaskObjectLifetime.Build); + _registeredTaskObjectCache = null; +#endif + + // On Windows, a process holds a handle to the current directory, + // so reset it away from a user-requested folder that may get deleted. + NativeMethodsShared.SetCurrentDirectory(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory); + + // Restore the original environment, best effort. + try + { + CommunicationsUtilities.SetEnvironment(_savedEnvironment); + } + catch (Exception ex) + { + debugWriter?.WriteLine("Failed to restore the original environment: {0}.", ex); + } + + if (_nodeEndpoint.LinkStatus == LinkStatus.Active) + { + // Notify the BuildManager that we are done. + _nodeEndpoint.SendData(new NodeShutdown(_shutdownReason == NodeEngineShutdownReason.Error ? NodeShutdownReason.Error : NodeShutdownReason.Requested)); + + // Flush all packets to the pipe and close it down. This blocks until the shutdown is complete. + _nodeEndpoint.OnLinkStatusChanged -= new LinkStatusChangedDelegate(OnLinkStatusChanged); + } + + _nodeEndpoint.Disconnect(); + + // Dispose these WaitHandles +#if CLR2COMPATIBILITY + _packetReceivedEvent.Close(); + _shutdownEvent.Close(); + _taskCompleteEvent.Close(); + _taskCancelledEvent.Close(); +#else + _packetReceivedEvent.Dispose(); + _shutdownEvent.Dispose(); + _taskCompleteEvent.Dispose(); + _taskCancelledEvent.Dispose(); +#endif + + return _shutdownReason; + } + + /// + /// Event handler for the node endpoint's LinkStatusChanged event. + /// + private void OnLinkStatusChanged(INodeEndpoint endpoint, LinkStatus status) + { + switch (status) + { + case LinkStatus.ConnectionFailed: + case LinkStatus.Failed: + _shutdownReason = NodeEngineShutdownReason.ConnectionFailed; + _shutdownEvent.Set(); + break; + + case LinkStatus.Inactive: + break; + + default: + break; + } + } + + /// + /// Task runner method + /// + private void RunTask(object state) + { + _isTaskExecuting = true; + OutOfProcTaskHostTaskResult taskResult = null; + TaskHostConfiguration taskConfiguration = state as TaskHostConfiguration; + IDictionary taskParams = taskConfiguration.TaskParameters; + + // We only really know the values of these variables for sure once we see what we received from our parent + // environment -- otherwise if this was a completely new build, we could lose out on expected environment + // variables. + _debugCommunications = taskConfiguration.BuildProcessEnvironment.ContainsValueAndIsEqual("MSBUILDDEBUGCOMM", "1", StringComparison.OrdinalIgnoreCase); + _updateEnvironment = !taskConfiguration.BuildProcessEnvironment.ContainsValueAndIsEqual("MSBuildTaskHostDoNotUpdateEnvironment", "1", StringComparison.OrdinalIgnoreCase); + _updateEnvironmentAndLog = taskConfiguration.BuildProcessEnvironment.ContainsValueAndIsEqual("MSBuildTaskHostUpdateEnvironmentAndLog", "1", StringComparison.OrdinalIgnoreCase); + WarningsAsErrors = taskConfiguration.WarningsAsErrors; + WarningsNotAsErrors = taskConfiguration.WarningsNotAsErrors; + WarningsAsMessages = taskConfiguration.WarningsAsMessages; + try + { + // Change to the startup directory + NativeMethodsShared.SetCurrentDirectory(taskConfiguration.StartupDirectory); + + if (_updateEnvironment) + { + InitializeMismatchedEnvironmentTable(taskConfiguration.BuildProcessEnvironment); + } + + // Now set the new environment + SetTaskHostEnvironment(taskConfiguration.BuildProcessEnvironment); + + // Set culture + Thread.CurrentThread.CurrentCulture = taskConfiguration.Culture; + Thread.CurrentThread.CurrentUICulture = taskConfiguration.UICulture; + + string taskName = taskConfiguration.TaskName; + string taskLocation = taskConfiguration.TaskLocation; +#if !CLR2COMPATIBILITY + TaskFactoryUtilities.RegisterAssemblyResolveHandlersFromManifest(taskLocation); +#endif + // We will not create an appdomain now because of a bug + // As a fix, we will create the class directly without wrapping it in a domain + _taskWrapper = new OutOfProcTaskAppDomainWrapper(); + + taskResult = _taskWrapper.ExecuteTask( + this as IBuildEngine, + taskName, + taskLocation, + taskConfiguration.ProjectFileOfTask, + taskConfiguration.LineNumberOfTask, + taskConfiguration.ColumnNumberOfTask, + taskConfiguration.TargetName, + taskConfiguration.ProjectFile, +#if FEATURE_APPDOMAIN + taskConfiguration.AppDomainSetup, +#endif +#if !NET35 + taskConfiguration.HostServices, +#endif + taskParams); + } + catch (ThreadAbortException) + { + // This thread was aborted as part of Cancellation, we will return a failure task result + taskResult = new OutOfProcTaskHostTaskResult(TaskCompleteType.Failure); + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + taskResult = new OutOfProcTaskHostTaskResult(TaskCompleteType.CrashedDuringExecution, e); + } + finally + { + try + { + _isTaskExecuting = false; + + IDictionary currentEnvironment = CommunicationsUtilities.GetEnvironmentVariables(); + currentEnvironment = UpdateEnvironmentForMainNode(currentEnvironment); + + taskResult ??= new OutOfProcTaskHostTaskResult(TaskCompleteType.Failure); + + lock (_taskCompleteLock) + { + _taskCompletePacket = new TaskHostTaskComplete( + taskResult, +#if FEATURE_REPORTFILEACCESSES + _fileAccessData, +#endif + currentEnvironment); + } + +#if FEATURE_APPDOMAIN + foreach (TaskParameter param in taskParams.Values) + { + // Tell remoting to forget connections to the parameter + RemotingServices.Disconnect(param); + } +#endif + + // Restore the original clean environment + CommunicationsUtilities.SetEnvironment(_savedEnvironment); + } + catch (Exception e) + { + lock (_taskCompleteLock) + { + // Create a minimal taskCompletePacket to carry the exception so that the TaskHostTask does not hang while waiting + _taskCompletePacket = new TaskHostTaskComplete( + new OutOfProcTaskHostTaskResult(TaskCompleteType.CrashedAfterExecution, e), +#if FEATURE_REPORTFILEACCESSES + _fileAccessData, +#endif + null); + } + } + finally + { +#if FEATURE_REPORTFILEACCESSES + _fileAccessData = new List(); +#endif + + // Call CleanupTask to unload any domains and other necessary cleanup in the taskWrapper + _taskWrapper.CleanupTask(); + + // The task has now fully completed executing + _taskCompleteEvent.Set(); + } + } + } + + /// + /// Set the environment for the task host -- includes possibly munging the given + /// environment somewhat to account for expected environment differences between, + /// e.g. parent processes and task hosts of different bitnesses. + /// + private void SetTaskHostEnvironment(IDictionary environment) + { + ErrorUtilities.VerifyThrowInternalNull(s_mismatchedEnvironmentValues, "mismatchedEnvironmentValues"); + IDictionary updatedEnvironment = null; + + if (_updateEnvironment) + { + foreach (KeyValuePair> variable in s_mismatchedEnvironmentValues) + { + string oldValue = variable.Value.Key; + string newValue = variable.Value.Value; + + // We don't check the return value, because having the variable not exist == be + // null is perfectly valid, and mismatchedEnvironmentValues stores those values + // as null as well, so the String.Equals should still return that they are equal. + string environmentValue = null; + environment.TryGetValue(variable.Key, out environmentValue); + + if (String.Equals(environmentValue, oldValue, StringComparison.OrdinalIgnoreCase)) + { + if (updatedEnvironment == null) + { + if (_updateEnvironmentAndLog) + { + LogMessageFromResource(MessageImportance.Low, "ModifyingTaskHostEnvironmentHeader"); + } + + updatedEnvironment = new Dictionary(environment, StringComparer.OrdinalIgnoreCase); + } + + if (newValue != null) + { + if (_updateEnvironmentAndLog) + { + LogMessageFromResource(MessageImportance.Low, "ModifyingTaskHostEnvironmentVariable", variable.Key, newValue, environmentValue ?? String.Empty); + } + + updatedEnvironment[variable.Key] = newValue; + } + else + { + updatedEnvironment.Remove(variable.Key); + } + } + } + } + + // if it's still null here, there were no changes necessary -- so just + // set it to what was already passed in. + if (updatedEnvironment == null) + { + updatedEnvironment = environment; + } + + CommunicationsUtilities.SetEnvironment(updatedEnvironment); + } + + /// + /// Given the environment of the task host at the end of task execution, make sure that any + /// processor-specific variables have been re-applied in the correct form for the main node, + /// so that when we pass this dictionary back to the main node, all it should have to do + /// is just set it. + /// + private IDictionary UpdateEnvironmentForMainNode(IDictionary environment) + { + ErrorUtilities.VerifyThrowInternalNull(s_mismatchedEnvironmentValues, "mismatchedEnvironmentValues"); + IDictionary updatedEnvironment = null; + + if (_updateEnvironment) + { + foreach (KeyValuePair> variable in s_mismatchedEnvironmentValues) + { + // Since this is munging the property list for returning to the parent process, + // then the value we wish to replace is the one that is in this process, and the + // replacement value is the one that originally came from the parent process, + // instead of the other way around. + string oldValue = variable.Value.Value; + string newValue = variable.Value.Key; + + // We don't check the return value, because having the variable not exist == be + // null is perfectly valid, and mismatchedEnvironmentValues stores those values + // as null as well, so the String.Equals should still return that they are equal. + string environmentValue = null; + environment.TryGetValue(variable.Key, out environmentValue); + + if (String.Equals(environmentValue, oldValue, StringComparison.OrdinalIgnoreCase)) + { + updatedEnvironment ??= new Dictionary(environment, StringComparer.OrdinalIgnoreCase); + + if (newValue != null) + { + updatedEnvironment[variable.Key] = newValue; + } + else + { + updatedEnvironment.Remove(variable.Key); + } + } + } + } + + // if it's still null here, there were no changes necessary -- so just + // set it to what was already passed in. + if (updatedEnvironment == null) + { + updatedEnvironment = environment; + } + + return updatedEnvironment; + } + + /// + /// Make sure the mismatchedEnvironmentValues table has been populated. Note that this should + /// only do actual work on the very first run of a task in the task host -- otherwise, it should + /// already have been populated. + /// + private void InitializeMismatchedEnvironmentTable(IDictionary environment) + { + if (s_mismatchedEnvironmentValues == null) + { + // This is the first time that we have received a TaskHostConfiguration packet, so we + // need to construct the mismatched environment table based on our current environment + // (assumed to be effectively identical to startup) and the environment we were given + // via the task host configuration, assumed to be effectively identical to the startup + // environment of the task host, given that the configuration packet is sent immediately + // after the node is launched. + s_mismatchedEnvironmentValues = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + foreach (KeyValuePair variable in _savedEnvironment) + { + string oldValue = variable.Value; + string newValue; + if (!environment.TryGetValue(variable.Key, out newValue)) + { + s_mismatchedEnvironmentValues[variable.Key] = new KeyValuePair(null, oldValue); + } + else + { + if (!String.Equals(oldValue, newValue, StringComparison.OrdinalIgnoreCase)) + { + s_mismatchedEnvironmentValues[variable.Key] = new KeyValuePair(newValue, oldValue); + } + } + } + + foreach (KeyValuePair variable in environment) + { + string newValue = variable.Value; + string oldValue; + if (!_savedEnvironment.TryGetValue(variable.Key, out oldValue)) + { + s_mismatchedEnvironmentValues[variable.Key] = new KeyValuePair(newValue, null); + } + else + { + if (!String.Equals(oldValue, newValue, StringComparison.OrdinalIgnoreCase)) + { + s_mismatchedEnvironmentValues[variable.Key] = new KeyValuePair(newValue, oldValue); + } + } + } + } + } + + /// + /// Sends the requested packet across to the main node. + /// + private void SendBuildEvent(BuildEventArgs e) + { + if (_nodeEndpoint?.LinkStatus == LinkStatus.Active) + { +#pragma warning disable SYSLIB0050 + // Types which are not serializable and are not IExtendedBuildEventArgs as + // those always implement custom serialization by WriteToStream and CreateFromStream. + if (!e.GetType().GetTypeInfo().IsSerializable && e is not IExtendedBuildEventArgs) +#pragma warning disable SYSLIB0050 + { + // log a warning and bail. This will end up re-calling SendBuildEvent, but we know for a fact + // that the warning that we constructed is serializable, so everything should be good. + LogWarningFromResource("ExpectedEventToBeSerializable", e.GetType().Name); + return; + } + + LogMessagePacketBase logMessage = new(new KeyValuePair(_currentConfiguration.NodeId, e)); + _nodeEndpoint.SendData(logMessage); + } + } + + /// + /// Generates the message event corresponding to a particular resource string and set of args + /// + private void LogMessageFromResource(MessageImportance importance, string messageResource, params object[] messageArgs) + { + ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration when we're trying to log messages!"); + + // Using the CLR 2 build event because this class is shared between MSBuildTaskHost.exe (CLR2) and MSBuild.exe (CLR4+) + BuildMessageEventArgs message = new BuildMessageEventArgs( + ResourceUtilities.FormatString(AssemblyResources.GetString(messageResource), messageArgs), + null, + _currentConfiguration.TaskName, + importance); + + LogMessageEvent(message); + } + + /// + /// Generates the error event corresponding to a particular resource string and set of args + /// + private void LogWarningFromResource(string messageResource, params object[] messageArgs) + { + ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration when we're trying to log warnings!"); + + // Using the CLR 2 build event because this class is shared between MSBuildTaskHost.exe (CLR2) and MSBuild.exe (CLR4+) + BuildWarningEventArgs warning = new BuildWarningEventArgs( + null, + null, + ProjectFileOfTaskNode, + LineNumberOfTaskNode, + ColumnNumberOfTaskNode, + 0, + 0, + ResourceUtilities.FormatString(AssemblyResources.GetString(messageResource), messageArgs), + null, + _currentConfiguration.TaskName); + + LogWarningEvent(warning); + } + + /// + /// Generates the error event corresponding to a particular resource string and set of args + /// + private void LogErrorFromResource(string messageResource) + { + ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration when we're trying to log errors!"); + + // Using the CLR 2 build event because this class is shared between MSBuildTaskHost.exe (CLR2) and MSBuild.exe (CLR4+) + BuildErrorEventArgs error = new BuildErrorEventArgs( + null, + null, + ProjectFileOfTaskNode, + LineNumberOfTaskNode, + ColumnNumberOfTaskNode, + 0, + 0, + AssemblyResources.GetString(messageResource), + null, + _currentConfiguration.TaskName); + + LogErrorEvent(error); + } + } +} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index a514cbcdcbf..2f8fe51e0c8 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -23,7 +23,7 @@ true - ..\MSBuild\MSBuild.ico + MSBuild\MSBuild.ico true @@ -37,227 +37,115 @@ - - GlobalUsings.cs - - - BuildEnvironmentHelper.cs - - - BuildEnvironmentState.cs - - - AssemblyNameComparer.cs - - - BuildEngineResult.cs - - - IBuildEngine3.cs - - - RunInSTAAtribute.cs - - - ITaskItem2.cs - - - IExtendedBuildEventArgs.cs - - - - - - - - CopyOnWriteDictionary.cs - - - - - - - ErrorUtilities.cs - - - SharedErrorUtilities.cs - - - EscapingUtilities.cs - - - ExceptionHandling.cs - - - FileUtilities.cs - - - SharedFileUtilities.cs - - - FileUtilitiesRegex.cs - - - INodeEndpoint.cs - - - INodePacket.cs - - - INodePacketFactory.cs - - - INodePacketHandler.cs - - - ITranslatable.cs - - - ITranslator.cs - - - - InternalErrorException.cs - - - InterningBinaryReader.cs - - - BinaryReaderFactory.cs - - - BinaryReaderExtensions.cs - - - BinaryWriterExtensions.cs - - - LogMessagePacketBase.cs - - - Modifiers.cs - - - - NativeMethodsShared.cs - - - AnsiDetector.cs - - - NodeBuildComplete.cs - - - NodeEndpointOutOfProcBase.cs - - - NodeEngineShutdownReason.cs - - - NodePacketFactory.cs - - - BinaryTranslator.cs - - - BuildExceptionBase.cs - - - BuildExceptionRemoteState.cs - - - BuildExceptionSerializationHelper.cs - - - GenericBuildTransferredException.cs - - - NodeShutdown.cs - - - ReadOnlyEmptyCollection.cs - - - ResourceUtilities.cs - - - StringBuilderCache.cs - - - SupportedOSPlatform.cs - - - TaskEngineAssemblyResolver.cs - - - TaskParameterTypeVerifier.cs - - - FrameworkTraits.cs - - - VisualStudioLocationHelper.cs - - - XMakeAttributes.cs - + + + - - ChangeWaves.cs - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - OutOfProcTaskHostTaskResult.cs - - - - - - - - - - - - - - - - - - - - - - - - - - OutOfProcTaskAppDomainWrapperBase.cs - - + - + $(AssemblyName).Strings.shared.resources Designer - + diff --git a/src/MSBuildTaskHost/Modifiers.cs b/src/MSBuildTaskHost/Modifiers.cs new file mode 100644 index 00000000000..5e17fc8b41d --- /dev/null +++ b/src/MSBuildTaskHost/Modifiers.cs @@ -0,0 +1,470 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +#if !CLR2COMPATIBILITY +using System.Collections.Frozen; +#else +using System.Collections.Generic; +#endif +using System.Diagnostics.CodeAnalysis; +using System.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Shared.FileSystem; + +#nullable disable + +namespace Microsoft.Build.Shared +{ + /// + /// This class contains utility methods for file IO. + /// + /// + /// Partial class in order to reduce the amount of sharing into different assemblies + /// + internal static partial class FileUtilities + { + /// + /// Encapsulates the definitions of the item-spec modifiers a.k.a. reserved item metadata. + /// + internal static class ItemSpecModifiers + { + internal const string FullPath = "FullPath"; + internal const string RootDir = "RootDir"; + internal const string Filename = "Filename"; + internal const string Extension = "Extension"; + internal const string RelativeDir = "RelativeDir"; + internal const string Directory = "Directory"; + internal const string RecursiveDir = "RecursiveDir"; + internal const string Identity = "Identity"; + internal const string ModifiedTime = "ModifiedTime"; + internal const string CreatedTime = "CreatedTime"; + internal const string AccessedTime = "AccessedTime"; + internal const string DefiningProjectFullPath = "DefiningProjectFullPath"; + internal const string DefiningProjectDirectory = "DefiningProjectDirectory"; + internal const string DefiningProjectName = "DefiningProjectName"; + internal const string DefiningProjectExtension = "DefiningProjectExtension"; + + // These are all the well-known attributes. + internal static readonly string[] All = + { + FullPath, + RootDir, + Filename, + Extension, + RelativeDir, + Directory, + RecursiveDir, // <-- Not derivable. + Identity, + ModifiedTime, + CreatedTime, + AccessedTime, + DefiningProjectFullPath, + DefiningProjectDirectory, + DefiningProjectName, + DefiningProjectExtension + }; + +#if CLR2COMPATIBILITY + private static readonly HashSet s_tableOfItemSpecModifiers = new HashSet(All, StringComparer.OrdinalIgnoreCase); + private static readonly HashSet s_tableOfDefiningProjectModifiers = new HashSet( + [ + DefiningProjectFullPath, + DefiningProjectDirectory, + DefiningProjectName, + DefiningProjectExtension, + ], StringComparer.OrdinalIgnoreCase); +#else + private static readonly FrozenSet s_tableOfItemSpecModifiers = FrozenSet.Create(StringComparer.OrdinalIgnoreCase, All); + private static readonly FrozenSet s_tableOfDefiningProjectModifiers = FrozenSet.Create(StringComparer.OrdinalIgnoreCase, + [ + DefiningProjectFullPath, + DefiningProjectDirectory, + DefiningProjectName, + DefiningProjectExtension, + ]); +#endif + + /// + /// Indicates if the given name is reserved for an item-spec modifier. + /// + internal static bool IsItemSpecModifier(string name) + { + if (name == null) + { + return false; + } + + // Could still be a case-insensitive match. + bool result = s_tableOfItemSpecModifiers.Contains(name); + + return result; + } + + /// + /// Indicates if the given name is reserved for one of the specific subset of itemspec + /// modifiers to do with the defining project of the item. + /// + internal static bool IsDefiningProjectModifier(string name) => s_tableOfDefiningProjectModifiers.Contains(name); + + /// + /// Indicates if the given name is reserved for a derivable item-spec modifier. + /// Derivable means it can be computed given a file name. + /// + /// Name to check. + /// true, if name of a derivable modifier + internal static bool IsDerivableItemSpecModifier(string name) + { + bool isItemSpecModifier = IsItemSpecModifier(name); + + if (isItemSpecModifier) + { + if (name.Length == 12) + { + if (name[0] == 'R' || name[0] == 'r') + { + // The only 12 letter ItemSpecModifier that starts with 'R' is 'RecursiveDir' + return false; + } + } + } + + return isItemSpecModifier; + } + + /// + /// Performs path manipulations on the given item-spec as directed. + /// Does not cache the result. + /// + internal static string GetItemSpecModifier(string currentDirectory, string itemSpec, string definingProjectEscaped, string modifier) + { + string dummy = null; + return GetItemSpecModifier(currentDirectory, itemSpec, definingProjectEscaped, modifier, ref dummy); + } + + /// + /// Performs path manipulations on the given item-spec as directed. + /// + /// Supported modifiers: + /// %(FullPath) = full path of item + /// %(RootDir) = root directory of item + /// %(Filename) = item filename without extension + /// %(Extension) = item filename extension + /// %(RelativeDir) = item directory as given in item-spec + /// %(Directory) = full path of item directory relative to root + /// %(RecursiveDir) = portion of item path that matched a recursive wildcard + /// %(Identity) = item-spec as given + /// %(ModifiedTime) = last write time of item + /// %(CreatedTime) = creation time of item + /// %(AccessedTime) = last access time of item + /// + /// NOTES: + /// 1) This method always returns an empty string for the %(RecursiveDir) modifier because it does not have enough + /// information to compute it -- only the BuildItem class can compute this modifier. + /// 2) All but the file time modifiers could be cached, but it's not worth the space. Only full path is cached, as the others are just string manipulations. + /// + /// + /// Methods of the Path class "normalize" slashes and periods. For example: + /// 1) successive slashes are combined into 1 slash + /// 2) trailing periods are discarded + /// 3) forward slashes are changed to back-slashes + /// + /// As a result, we cannot rely on any file-spec that has passed through a Path method to remain the same. We will + /// therefore not bother preserving slashes and periods when file-specs are transformed. + /// + /// Never returns null. + /// + /// The root directory for relative item-specs. When called on the Engine thread, this is the project directory. When called as part of building a task, it is null, indicating that the current directory should be used. + /// The item-spec to modify. + /// The path to the project that defined this item (may be null). + /// The modifier to apply to the item-spec. + /// Full path if any was previously computed, to cache. + /// The modified item-spec (can be empty string, but will never be null). + /// Thrown when the item-spec is not a path. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Pre-existing")] + internal static string GetItemSpecModifier(string currentDirectory, string itemSpec, string definingProjectEscaped, string modifier, ref string fullPath) + { + ErrorUtilities.VerifyThrow(itemSpec != null, "Need item-spec to modify."); + ErrorUtilities.VerifyThrow(modifier != null, "Need modifier to apply to item-spec."); + + string modifiedItemSpec = null; + + try + { + if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.FullPath, StringComparison.OrdinalIgnoreCase)) + { + if (fullPath != null) + { + return fullPath; + } + + if (currentDirectory == null) + { + currentDirectory = FileUtilities.CurrentThreadWorkingDirectory ?? String.Empty; + } + + modifiedItemSpec = GetFullPath(itemSpec, currentDirectory); + fullPath = modifiedItemSpec; + + ThrowForUrl(modifiedItemSpec, itemSpec, currentDirectory); + } + else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.RootDir, StringComparison.OrdinalIgnoreCase)) + { + GetItemSpecModifier(currentDirectory, itemSpec, definingProjectEscaped, ItemSpecModifiers.FullPath, ref fullPath); + + modifiedItemSpec = Path.GetPathRoot(fullPath); + + if (!FrameworkFileUtilities.EndsWithSlash(modifiedItemSpec)) + { + ErrorUtilities.VerifyThrow(FileUtilitiesRegex.StartsWithUncPattern(modifiedItemSpec), + "Only UNC shares should be missing trailing slashes."); + + // restore/append trailing slash if Path.GetPathRoot() has either removed it, or failed to add it + // (this happens with UNC shares) + modifiedItemSpec += Path.DirectorySeparatorChar; + } + } + else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.Filename, StringComparison.OrdinalIgnoreCase)) + { + // if the item-spec is a root directory, it can have no filename + if (IsRootDirectory(itemSpec)) + { + // NOTE: this is to prevent Path.GetFileNameWithoutExtension() from treating server and share elements + // in a UNC file-spec as filenames e.g. \\server, \\server\share + modifiedItemSpec = String.Empty; + } + else + { + // Fix path to avoid problem with Path.GetFileNameWithoutExtension when backslashes in itemSpec on Unix + modifiedItemSpec = Path.GetFileNameWithoutExtension(FrameworkFileUtilities.FixFilePath(itemSpec)); + } + } + else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.Extension, StringComparison.OrdinalIgnoreCase)) + { + // if the item-spec is a root directory, it can have no extension + if (IsRootDirectory(itemSpec)) + { + // NOTE: this is to prevent Path.GetExtension() from treating server and share elements in a UNC + // file-spec as filenames e.g. \\server.ext, \\server\share.ext + modifiedItemSpec = String.Empty; + } + else + { + modifiedItemSpec = Path.GetExtension(itemSpec); + } + } + else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.RelativeDir, StringComparison.OrdinalIgnoreCase)) + { + modifiedItemSpec = GetDirectory(itemSpec); + } + else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.Directory, StringComparison.OrdinalIgnoreCase)) + { + GetItemSpecModifier(currentDirectory, itemSpec, definingProjectEscaped, ItemSpecModifiers.FullPath, ref fullPath); + + modifiedItemSpec = GetDirectory(fullPath); + + if (NativeMethodsShared.IsWindows) + { + int length = -1; + if (FileUtilitiesRegex.StartsWithDrivePattern(modifiedItemSpec)) + { + length = 2; + } + else + { + length = FileUtilitiesRegex.StartsWithUncPatternMatchLength(modifiedItemSpec); + } + + if (length != -1) + { + ErrorUtilities.VerifyThrow((modifiedItemSpec.Length > length) && FrameworkFileUtilities.IsSlash(modifiedItemSpec[length]), + "Root directory must have a trailing slash."); + + modifiedItemSpec = modifiedItemSpec.Substring(length + 1); + } + } + else + { + ErrorUtilities.VerifyThrow(!string.IsNullOrEmpty(modifiedItemSpec) && FrameworkFileUtilities.IsSlash(modifiedItemSpec[0]), + "Expected a full non-windows path rooted at '/'."); + + // A full unix path is always rooted at + // `/`, and a root-relative path is the + // rest of the string. + modifiedItemSpec = modifiedItemSpec.Substring(1); + } + } + else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.RecursiveDir, StringComparison.OrdinalIgnoreCase)) + { + // only the BuildItem class can compute this modifier -- so leave empty + modifiedItemSpec = String.Empty; + } + else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.Identity, StringComparison.OrdinalIgnoreCase)) + { + modifiedItemSpec = itemSpec; + } + else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.ModifiedTime, StringComparison.OrdinalIgnoreCase)) + { + // About to go out to the filesystem. This means data is leaving the engine, so need + // to unescape first. + string unescapedItemSpec = EscapingUtilities.UnescapeAll(itemSpec); + + FileInfo info = FileUtilities.GetFileInfoNoThrow(unescapedItemSpec); + + if (info != null) + { + modifiedItemSpec = info.LastWriteTime.ToString(FileTimeFormat, null); + } + else + { + // File does not exist, or path is a directory + modifiedItemSpec = String.Empty; + } + } + else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.CreatedTime, StringComparison.OrdinalIgnoreCase)) + { + // About to go out to the filesystem. This means data is leaving the engine, so need + // to unescape first. + string unescapedItemSpec = EscapingUtilities.UnescapeAll(itemSpec); + + if (FileSystems.Default.FileExists(unescapedItemSpec)) + { + modifiedItemSpec = File.GetCreationTime(unescapedItemSpec).ToString(FileTimeFormat, null); + } + else + { + // File does not exist, or path is a directory + modifiedItemSpec = String.Empty; + } + } + else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.AccessedTime, StringComparison.OrdinalIgnoreCase)) + { + // About to go out to the filesystem. This means data is leaving the engine, so need + // to unescape first. + string unescapedItemSpec = EscapingUtilities.UnescapeAll(itemSpec); + + if (FileSystems.Default.FileExists(unescapedItemSpec)) + { + modifiedItemSpec = File.GetLastAccessTime(unescapedItemSpec).ToString(FileTimeFormat, null); + } + else + { + // File does not exist, or path is a directory + modifiedItemSpec = String.Empty; + } + } + else if (IsDefiningProjectModifier(modifier)) + { + if (String.IsNullOrEmpty(definingProjectEscaped)) + { + // We have nothing to work with, but that's sometimes OK -- so just return String.Empty + modifiedItemSpec = String.Empty; + } + else + { + if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.DefiningProjectDirectory, StringComparison.OrdinalIgnoreCase)) + { + // ItemSpecModifiers.Directory does not contain the root directory + modifiedItemSpec = Path.Combine( + GetItemSpecModifier(currentDirectory, definingProjectEscaped, null, ItemSpecModifiers.RootDir), + GetItemSpecModifier(currentDirectory, definingProjectEscaped, null, ItemSpecModifiers.Directory)); + } + else + { + string additionalModifier = null; + + if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.DefiningProjectFullPath, StringComparison.OrdinalIgnoreCase)) + { + additionalModifier = ItemSpecModifiers.FullPath; + } + else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.DefiningProjectName, StringComparison.OrdinalIgnoreCase)) + { + additionalModifier = ItemSpecModifiers.Filename; + } + else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.DefiningProjectExtension, StringComparison.OrdinalIgnoreCase)) + { + additionalModifier = ItemSpecModifiers.Extension; + } + else + { + ErrorUtilities.ThrowInternalError("\"{0}\" is not a valid item-spec modifier.", modifier); + } + + modifiedItemSpec = GetItemSpecModifier(currentDirectory, definingProjectEscaped, null, additionalModifier); + } + } + } + else + { + ErrorUtilities.ThrowInternalError("\"{0}\" is not a valid item-spec modifier.", modifier); + } + } + catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) + { + ErrorUtilities.ThrowInvalidOperation("Shared.InvalidFilespecForTransform", modifier, itemSpec, e.Message); + } + + return modifiedItemSpec; + } + + /// + /// Indicates whether the given path is a UNC or drive pattern root directory. + /// Note: This function mimics the behavior of checking if Path.GetDirectoryName(path) == null. + /// + /// + /// + private static bool IsRootDirectory(string path) + { + // Eliminate all non-rooted paths + if (!Path.IsPathRooted(path)) + { + return false; + } + + int uncMatchLength = FileUtilitiesRegex.StartsWithUncPatternMatchLength(path); + + // Determine if the given path is a standard drive/unc pattern root + if (FileUtilitiesRegex.IsDrivePattern(path) || + FileUtilitiesRegex.IsDrivePatternWithSlash(path) || + uncMatchLength == path.Length) + { + return true; + } + + // Eliminate all non-root unc paths. + if (uncMatchLength != -1) + { + return false; + } + + // Eliminate any drive patterns that don't have a slash after the colon or where the 4th character is a non-slash + // A non-slash at [3] is specifically checked here because Path.GetDirectoryName + // considers "C:///" a valid root. + if (FileUtilitiesRegex.StartsWithDrivePattern(path) && + ((path.Length >= 3 && path[2] != '\\' && path[2] != '/') || + (path.Length >= 4 && path[3] != '\\' && path[3] != '/'))) + { + return false; + } + + // There are some edge cases that can get to this point. + // After eliminating valid / invalid roots, fall back on original behavior. + return Path.GetDirectoryName(path) == null; + } + + /// + /// Temporary check for something like http://foo which will end up like c:\foo\bar\http://foo + /// We should either have no colon, or exactly one colon. + /// UNDONE: This is a minimal safe change for Dev10. The correct fix should be to make GetFullPath/NormalizePath throw for this. + /// + private static void ThrowForUrl(string fullPath, string itemSpec, string currentDirectory) + { + if (fullPath.IndexOf(':') != fullPath.LastIndexOf(':')) + { + // Cause a better error to appear + fullPath = Path.GetFullPath(Path.Combine(currentDirectory, itemSpec)); + } + } + } + } +} diff --git a/src/MSBuildTaskHost/NamedPipeUtil.cs b/src/MSBuildTaskHost/NamedPipeUtil.cs new file mode 100644 index 00000000000..0b85b05bacd --- /dev/null +++ b/src/MSBuildTaskHost/NamedPipeUtil.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using Microsoft.Build.Internal; + +namespace Microsoft.Build.Shared +{ + internal static class NamedPipeUtil + { + internal static string GetPlatformSpecificPipeName(int? processId = null) + { + if (processId is null) + { + processId = EnvironmentUtilities.CurrentProcessId; + } + + string pipeName = $"MSBuild{processId}"; + + return GetPlatformSpecificPipeName(pipeName); + } + + internal static string GetPlatformSpecificPipeName(string pipeName) + { + if (NativeMethodsShared.IsUnixLike) + { + // If we're on a Unix machine then named pipes are implemented using Unix Domain Sockets. + // Most Unix systems have a maximum path length limit for Unix Domain Sockets, with + // Mac having a particularly short one. Mac also has a generated temp directory that + // can be quite long, leaving very little room for the actual pipe name. Fortunately, + // '/tmp' is mandated by POSIX to always be a valid temp directory, so we can use that + // instead. +#if !CLR2COMPATIBILITY + return Path.Combine("/tmp", pipeName); +#else + // We should never get here. This would be a net35 task host running on unix. + ErrorUtilities.ThrowInternalError("Task host used on unix in retrieving the pipe name."); + return string.Empty; +#endif + } + else + { + return pipeName; + } + } + + internal static string GetRarNodePipeName(ServerNodeHandshake handshake) + => GetPlatformSpecificPipeName($"MSBuildRarNode-{handshake.ComputeHash()}"); + + internal static string GetRarNodeEndpointPipeName(ServerNodeHandshake handshake) + => GetPlatformSpecificPipeName($"MSBuildRarNodeEndpoint-{handshake.ComputeHash()}"); + } +} diff --git a/src/MSBuildTaskHost/NodeBuildComplete.cs b/src/MSBuildTaskHost/NodeBuildComplete.cs new file mode 100644 index 00000000000..25aa30675f7 --- /dev/null +++ b/src/MSBuildTaskHost/NodeBuildComplete.cs @@ -0,0 +1,83 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + /// + /// The NodeBuildComplete packet is used to indicate to a node that it should clean up its current build and + /// possibly prepare for node reuse. + /// + internal class NodeBuildComplete : INodePacket + { + /// + /// Flag indicating if the node should prepare for reuse after cleanup. + /// + private bool _prepareForReuse; + + /// + /// Constructor. + /// + public NodeBuildComplete(bool prepareForReuse) + { + _prepareForReuse = prepareForReuse; + } + + /// + /// Private constructor for translation + /// + private NodeBuildComplete() + { + } + + /// + /// Flag indicating if the node should prepare for reuse. + /// + public bool PrepareForReuse + { + [DebuggerStepThrough] + get + { return _prepareForReuse; } + } + + #region INodePacket Members + + /// + /// The packet type + /// + public NodePacketType Type + { + [DebuggerStepThrough] + get + { return NodePacketType.NodeBuildComplete; } + } + + #endregion + + #region INodePacketTranslatable Members + + /// + /// Translates the packet to/from binary form. + /// + /// The translator to use. + public void Translate(ITranslator translator) + { + translator.Translate(ref _prepareForReuse); + } + + /// + /// Factory for deserialization. + /// + internal static NodeBuildComplete FactoryForDeserialization(ITranslator translator) + { + NodeBuildComplete packet = new NodeBuildComplete(); + packet.Translate(translator); + return packet; + } + + #endregion + } +} diff --git a/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs b/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs new file mode 100644 index 00000000000..038c7949dbb --- /dev/null +++ b/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs @@ -0,0 +1,817 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +#if NET +using System.Collections.Frozen; +#endif +using System.Diagnostics.CodeAnalysis; +#if CLR2COMPATIBILITY +using Microsoft.Build.Shared.Concurrent; +#else +using System.Collections.Concurrent; +#endif +using System.Threading; +using Microsoft.Build.Internal; +using Microsoft.Build.Shared; +using System.IO.Pipes; +using System.IO; +using System.Collections.Generic; + +#if FEATURE_SECURITY_PERMISSIONS || FEATURE_PIPE_SECURITY +using System.Security.AccessControl; +#endif +#if FEATURE_PIPE_SECURITY && FEATURE_NAMED_PIPE_SECURITY_CONSTRUCTOR +using System.Security.Principal; + +#endif +#if NET451_OR_GREATER || NETCOREAPP +using System.Threading.Tasks; +#endif + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + /// + /// This is an implementation of INodeEndpoint for the out-of-proc nodes. It acts only as a client. + /// + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "It is expected to keep the stream open for the process lifetime")] + internal abstract class NodeEndpointOutOfProcBase : INodeEndpoint + { + #region Private Data + +#if NETCOREAPP2_1_OR_GREATER + /// + /// The amount of time to wait for the client to connect to the host. + /// + private const int ClientConnectTimeout = 60000; +#endif // NETCOREAPP2_1 + + /// + /// The size of the buffers to use for named pipes + /// + private const int PipeBufferSize = 131072; + + /// + /// The current communication status of the node. + /// + private LinkStatus _status; + + /// + /// The pipe client used by the nodes. + /// + private NamedPipeServerStream _pipeServer; + + // The following private data fields are used only when the endpoint is in ASYNCHRONOUS mode. + + /// + /// Object used as a lock source for the async data + /// + private object _asyncDataMonitor; + + /// + /// Set when a packet is available in the packet queue + /// + private AutoResetEvent _packetAvailable; + + /// + /// Set when the asynchronous packet pump should terminate + /// + private AutoResetEvent _terminatePacketPump; + + /// + /// True if this side is gracefully disconnecting. + /// In such case we have sent last packet to client side and we expect + /// client will soon broke pipe connection - unless server do it first. + /// + private bool _isClientDisconnecting; + + /// + /// The thread which runs the asynchronous packet pump + /// + private Thread _packetPump; + + /// + /// The factory used to create and route packets. + /// + private INodePacketFactory _packetFactory; + + /// + /// The asynchronous packet queue. + /// + /// + /// Operations on this queue must be synchronized since it is accessible by multiple threads. + /// Use a lock on the packetQueue itself. + /// + private ConcurrentQueue _packetQueue; + + /// + /// Per-node shared read buffer. + /// + private BinaryReaderFactory _sharedReadBuffer; + + /// + /// A way to cache a byte array when writing out packets + /// + private MemoryStream _packetStream; + + /// + /// A binary writer to help write into + /// + private BinaryWriter _binaryWriter; + + /// + /// Represents the version of the parent packet associated with the node instantiation. + /// + private byte _parentPacketVersion; + +#if NET + /// + /// The set of property names from handshake responsible for node version. + /// + private readonly FrozenSet _versionHandshakeGroup = [ + nameof(HandshakeComponents.FileVersionMajor), + nameof(HandshakeComponents.FileVersionMinor), + nameof(HandshakeComponents.FileVersionBuild), + nameof(HandshakeComponents.FileVersionPrivate)]; +#endif + +#endregion + + #region INodeEndpoint Events + + /// + /// Raised when the link status has changed. + /// + public event LinkStatusChangedDelegate OnLinkStatusChanged; + + #endregion + + #region INodeEndpoint Properties + + /// + /// Returns the link status of this node. + /// + public LinkStatus LinkStatus + { + get { return _status; } + } + + #endregion + + #region Properties + + #endregion + + #region INodeEndpoint Methods + + /// + /// Causes this endpoint to wait for the remote endpoint to connect + /// + /// The factory used to create packets. + public void Listen(INodePacketFactory factory) + { + ErrorUtilities.VerifyThrow(_status == LinkStatus.Inactive, "Link not inactive. Status is {0}", _status); + ErrorUtilities.VerifyThrowArgumentNull(factory, nameof(factory)); + _packetFactory = factory; + + InitializeAsyncPacketThread(); + } + + /// + /// Causes this node to connect to the matched endpoint. + /// + /// The factory used to create packets. + public void Connect(INodePacketFactory factory) + { + ErrorUtilities.ThrowInternalError("Connect() not valid on the out of proc endpoint."); + } + + /// + /// Shuts down the link + /// + public void Disconnect() + { + InternalDisconnect(); + } + + /// + /// Sends data to the peer endpoint. + /// + /// The packet to send. + public void SendData(INodePacket packet) + { + // PERF: Set up a priority system so logging packets are sent only when all other packet types have been sent. + if (_status == LinkStatus.Active) + { + EnqueuePacket(packet); + } + } + + /// + /// Called when we are about to send last packet to finalize graceful disconnection with client. + /// + public void ClientWillDisconnect() + { + _isClientDisconnecting = true; + } + + #endregion + + #region Construction + + /// + /// Instantiates an endpoint to act as a client. + /// + internal void InternalConstruct(string pipeName = null, byte parentPacketVersion = 1) + { + _status = LinkStatus.Inactive; + _asyncDataMonitor = new object(); + _sharedReadBuffer = InterningBinaryReader.CreateSharedBuffer(); + + _packetStream = new MemoryStream(); + _binaryWriter = new BinaryWriter(_packetStream); + _parentPacketVersion = parentPacketVersion; + + pipeName ??= NamedPipeUtil.GetPlatformSpecificPipeName(); + +#if FEATURE_PIPE_SECURITY && FEATURE_NAMED_PIPE_SECURITY_CONSTRUCTOR + SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner; + PipeSecurity security = new PipeSecurity(); + + // Restrict access to just this account. We set the owner specifically here, and on the + // pipe client side they will check the owner against this one - they must have identical + // SIDs or the client will reject this server. This is used to avoid attacks where a + // hacked server creates a less restricted pipe in an attempt to lure us into using it and + // then sending build requests to the real pipe client (which is the MSBuild Build Manager.) + PipeAccessRule rule = new PipeAccessRule(identifier, PipeAccessRights.ReadWrite, AccessControlType.Allow); + security.AddAccessRule(rule); + security.SetOwner(identifier); + + _pipeServer = new NamedPipeServerStream( + pipeName, + PipeDirection.InOut, + 1, // Only allow one connection at a time. + PipeTransmissionMode.Byte, + PipeOptions.Asynchronous | PipeOptions.WriteThrough +#if FEATURE_PIPEOPTIONS_CURRENTUSERONLY + | PipeOptions.CurrentUserOnly +#endif + , + PipeBufferSize, // Default input buffer + PipeBufferSize, // Default output buffer + security, + HandleInheritability.None); +#else + _pipeServer = new NamedPipeServerStream( + pipeName, + PipeDirection.InOut, + 1, // Only allow one connection at a time. + PipeTransmissionMode.Byte, + PipeOptions.Asynchronous | PipeOptions.WriteThrough +#if FEATURE_PIPEOPTIONS_CURRENTUSERONLY + | PipeOptions.CurrentUserOnly +#endif + , + PipeBufferSize, // Default input buffer + PipeBufferSize); // Default output buffer +#endif + } + + #endregion + + /// + /// Returns the host handshake for this node endpoint. + /// + protected abstract Handshake GetHandshake(); + + /// + /// Updates the current link status if it has changed and notifies any registered delegates. + /// + /// The status the node should now be in. + protected void ChangeLinkStatus(LinkStatus newStatus) + { + ErrorUtilities.VerifyThrow(_status != newStatus, "Attempting to change status to existing status {0}.", _status); + CommunicationsUtilities.Trace("Changing link status from {0} to {1}", _status.ToString(), newStatus.ToString()); + _status = newStatus; + RaiseLinkStatusChanged(_status); + } + + /// + /// Invokes the OnLinkStatusChanged event in a thread-safe manner. + /// + /// The new status of the endpoint link. + private void RaiseLinkStatusChanged(LinkStatus newStatus) + { + OnLinkStatusChanged?.Invoke(this, newStatus); + } + + #region Private Methods + + /// + /// This does the actual work of changing the status and shutting down any threads we may have for + /// disconnection. + /// + private void InternalDisconnect() + { + ErrorUtilities.VerifyThrow(_packetPump.ManagedThreadId != Thread.CurrentThread.ManagedThreadId, "Can't join on the same thread."); + _terminatePacketPump.Set(); + _packetPump.Join(); +#if CLR2COMPATIBILITY + _terminatePacketPump.Close(); +#else + _terminatePacketPump.Dispose(); +#endif + _pipeServer.Dispose(); + _packetPump = null; + ChangeLinkStatus(LinkStatus.Inactive); + } + + #region Asynchronous Mode Methods + + /// + /// Adds a packet to the packet queue when asynchronous mode is enabled. + /// + /// The packet to be transmitted. + private void EnqueuePacket(INodePacket packet) + { + ErrorUtilities.VerifyThrowArgumentNull(packet, nameof(packet)); + ErrorUtilities.VerifyThrow(_packetQueue != null, "packetQueue is null"); + ErrorUtilities.VerifyThrow(_packetAvailable != null, "packetAvailable is null"); + _packetQueue.Enqueue(packet); + _packetAvailable.Set(); + } + + /// + /// Initializes the packet pump thread and the supporting events as well as the packet queue. + /// + private void InitializeAsyncPacketThread() + { + lock (_asyncDataMonitor) + { + _isClientDisconnecting = false; + _packetPump = new Thread(PacketPumpProc); + _packetPump.IsBackground = true; + _packetPump.Name = "OutOfProc Endpoint Packet Pump"; + _packetAvailable = new AutoResetEvent(false); + _terminatePacketPump = new AutoResetEvent(false); + _packetQueue = new ConcurrentQueue(); + _packetPump.Start(); + } + } + + /// + /// This method handles the asynchronous message pump. It waits for messages to show up on the queue + /// and calls FireDataAvailable for each such packet. It will terminate when the terminate event is + /// set. + /// + private void PacketPumpProc() + { + NamedPipeServerStream localPipeServer = _pipeServer; + + AutoResetEvent localPacketAvailable = _packetAvailable; + AutoResetEvent localTerminatePacketPump = _terminatePacketPump; + ConcurrentQueue localPacketQueue = _packetQueue; + + DateTime originalWaitStartTime = DateTime.UtcNow; + bool gotValidConnection = false; + while (!gotValidConnection) + { + gotValidConnection = true; + DateTime restartWaitTime = DateTime.UtcNow; + + // We only wait to wait the difference between now and the last original start time, in case we have multiple hosts attempting + // to attach. This prevents each attempt from resetting the timer. + TimeSpan usedWaitTime = restartWaitTime - originalWaitStartTime; + int waitTimeRemaining = Math.Max(0, CommunicationsUtilities.NodeConnectionTimeout - (int)usedWaitTime.TotalMilliseconds); + + try + { + // Wait for a connection +#if FEATURE_APM + IAsyncResult resultForConnection = localPipeServer.BeginWaitForConnection(null, null); + CommunicationsUtilities.Trace("Waiting for connection {0} ms...", waitTimeRemaining); + bool connected = resultForConnection.AsyncWaitHandle.WaitOne(waitTimeRemaining, false); +#else + Task connectionTask = localPipeServer.WaitForConnectionAsync(); + CommunicationsUtilities.Trace("Waiting for connection {0} ms...", waitTimeRemaining); + bool connected = connectionTask.Wait(waitTimeRemaining); +#endif + if (!connected) + { + CommunicationsUtilities.Trace("Connection timed out waiting a host to contact us. Exiting comm thread."); + ChangeLinkStatus(LinkStatus.ConnectionFailed); + return; + } + + CommunicationsUtilities.Trace("Parent started connecting. Reading handshake from parent"); +#if FEATURE_APM + localPipeServer.EndWaitForConnection(resultForConnection); +#endif + + // The handshake protocol is a series of int exchanges. The host sends us a each component, and we + // verify it. Afterwards, the host sends an "End of Handshake" signal, to which we respond in kind. + // Once the handshake is complete, both sides can be assured the other is ready to accept data. + Handshake handshake = GetHandshake(); + try + { + HandshakeComponents handshakeComponents = handshake.RetrieveHandshakeComponents(); + + int index = 0; + foreach (var component in handshakeComponents.EnumerateComponents()) + { + + if (!_pipeServer.TryReadIntForHandshake( + byteToAccept: index == 0 ? (byte?)CommunicationsUtilities.handshakeVersion : null, /* this will disconnect a < 16.8 host; it expects leading 00 or F5 or 06. 0x00 is a wildcard */ +#if NETCOREAPP2_1_OR_GREATER + ClientConnectTimeout, /* wait a long time for the handshake from this side */ +#endif + out HandshakeResult result)) + { + CommunicationsUtilities.Trace($"Handshake failed with error: {result.ErrorMessage}"); + } + + if (!IsHandshakePartValid(component, result.Value, index)) + { + CommunicationsUtilities.Trace( + "Handshake failed. Received {0} from host for {1} but expected {2}. Probably the host is a different MSBuild build.", + result.Value, + component.Key, + component.Value); + _pipeServer.WriteIntForHandshake(index + 1); + gotValidConnection = false; + break; + } + + index++; + } + + if (gotValidConnection) + { + // To ensure that our handshake and theirs have the same number of bytes, receive and send a magic number indicating EOS. + + if ( +#if NETCOREAPP2_1_OR_GREATER + _pipeServer.TryReadEndOfHandshakeSignal(false, ClientConnectTimeout, out HandshakeResult _)) /* wait a long time for the handshake from this side */ +#else + _pipeServer.TryReadEndOfHandshakeSignal(false, out HandshakeResult _)) +#endif + { + // Send supported PacketVersion after EndOfHandshakeSignal + // Based on this parent node decides how to communicate with the child. + if (_parentPacketVersion >= 2) + { + _pipeServer.WriteIntForHandshake(Handshake.PacketVersionFromChildMarker); // Marker: PacketVersion follows + _pipeServer.WriteIntForHandshake(NodePacketTypeExtensions.PacketVersion); + CommunicationsUtilities.Trace("Sent PacketVersion: {0}", NodePacketTypeExtensions.PacketVersion); + } + + CommunicationsUtilities.Trace("Successfully connected to parent."); + _pipeServer.WriteEndOfHandshakeSignal(); +#if FEATURE_SECURITY_PERMISSIONS + // We will only talk to a host that was started by the same user as us. Even though the pipe access is set to only allow this user, we want to ensure they + // haven't attempted to change those permissions out from under us. This ensures that the only way they can truly gain access is to be impersonating the + // user we were started by. + WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent(); + WindowsIdentity clientIdentity = null; + localPipeServer.RunAsClient(delegate () { clientIdentity = WindowsIdentity.GetCurrent(true); }); + + if (clientIdentity == null || !String.Equals(clientIdentity.Name, currentIdentity.Name, StringComparison.OrdinalIgnoreCase)) + { + CommunicationsUtilities.Trace("Handshake failed. Host user is {0} but we were created by {1}.", (clientIdentity == null) ? "" : clientIdentity.Name, currentIdentity.Name); + gotValidConnection = false; + continue; + } +#endif + } + } + } + catch (IOException e) + { + // We will get here when: + // 1. The host (OOP main node) connects to us, it immediately checks for user privileges + // and if they don't match it disconnects immediately leaving us still trying to read the blank handshake + // 2. The host is too old sending us bits we automatically reject in the handshake + // 3. We expected to read the EndOfHandshake signal, but we received something else + CommunicationsUtilities.Trace("Client connection failed but we will wait for another connection. Exception: {0}", e.Message); + + gotValidConnection = false; + } + catch (InvalidOperationException) + { + gotValidConnection = false; + } + + if (!gotValidConnection) + { + if (localPipeServer.IsConnected) + { + localPipeServer.Disconnect(); + } + continue; + } + + ChangeLinkStatus(LinkStatus.Active); + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + CommunicationsUtilities.Trace("Client connection failed. Exiting comm thread. {0}", e); + if (localPipeServer.IsConnected) + { + localPipeServer.Disconnect(); + } + + ExceptionHandling.DumpExceptionToFile(e); + ChangeLinkStatus(LinkStatus.Failed); + return; + } + } + + RunReadLoop( + new BufferedReadStream(_pipeServer), + _pipeServer, + localPacketQueue, localPacketAvailable, localTerminatePacketPump); + + CommunicationsUtilities.Trace("Ending read loop"); + + try + { + if (localPipeServer.IsConnected) + { +#if NET // OperatingSystem.IsWindows() is new in .NET 5.0 + if (OperatingSystem.IsWindows()) +#endif + { + localPipeServer.WaitForPipeDrain(); + } + + localPipeServer.Disconnect(); + } + } + catch (Exception) + { + // We don't really care if Disconnect somehow fails, but it gives us a chance to do the right thing. + } + } + + /// + /// Method to verify that the handshake part received from the host matches the expected values. + /// + private bool IsHandshakePartValid(KeyValuePair component, int handshakePart, int index) + { + if (handshakePart == component.Value) + { + return true; + } + +#if NET + // Check if this is a valid NET task host exception + bool isAllowedMismatch = false; + + if (component.Key == nameof(HandshakeComponents.Options)) + { + // NET Task host allows MSBuild.exe to connect to it even if they have bitness mismatch. + // 0x00FFFFFF is the handshake version included in component, the rest is the node type. + isAllowedMismatch = IsAllowedBitnessMismatch(component.Value, handshakePart); + } + else + { + isAllowedMismatch = _versionHandshakeGroup.Contains(component.Key) && component.Value == Handshake.NetTaskHostHandshakeVersion; + } + + if (isAllowedMismatch) + { + CommunicationsUtilities.Trace("Handshake for NET Host. Child host {0} for {1}.", handshakePart, component.Key); + return true; + } +#endif + CommunicationsUtilities.Trace( + "Handshake failed. Received {0} from host for {1} but expected {2}. Probably the host is a different MSBuild build.", + handshakePart, + component.Key, + component.Value); + + return false; + } + +#if NET + /// + /// NET Task host allows MSBuild.exe to connect to it even if they have bitness mismatch. + /// 0x00FFFFFF is the handshake version included in component, the rest is the node type. + /// + private bool IsAllowedBitnessMismatch(int expectedOptions, int receivedOptions) + { + var expectedNodeType = (HandshakeOptions)(expectedOptions & 0x00FFFFFF); + var receivedNodeType = (HandshakeOptions)(receivedOptions & 0x00FFFFFF); + + // not X64 or Arm64 means we are running on x86 + bool receivedIsX86 = !Handshake.IsHandshakeOptionEnabled(receivedNodeType, HandshakeOptions.X64) && + !Handshake.IsHandshakeOptionEnabled(receivedNodeType, HandshakeOptions.Arm64); + + bool expectedIsX64 = Handshake.IsHandshakeOptionEnabled(expectedNodeType, HandshakeOptions.X64); + + return receivedIsX86 && expectedIsX64; + } +#endif + + private void RunReadLoop( + BufferedReadStream localReadPipe, + NamedPipeServerStream localWritePipe, + ConcurrentQueue localPacketQueue, + AutoResetEvent localPacketAvailable, + AutoResetEvent localTerminatePacketPump) + { + // Ordering of the wait handles is important. The first signaled wait handle in the array + // will be returned by WaitAny if multiple wait handles are signaled. We prefer to have the + // terminate event triggered so that we cannot get into a situation where packets are being + // spammed to the endpoint and it never gets an opportunity to shutdown. + CommunicationsUtilities.Trace("Entering read loop."); + byte[] headerByte = new byte[5]; + ITranslator writeTranslator = null; +#if NET451_OR_GREATER + Task readTask = localReadPipe.ReadAsync(headerByte, 0, headerByte.Length, CancellationToken.None); +#elif NETCOREAPP + Task readTask = localReadPipe.ReadAsync(headerByte.AsMemory(), CancellationToken.None).AsTask(); +#else + IAsyncResult result = localReadPipe.BeginRead(headerByte, 0, headerByte.Length, null, null); +#endif + + // Ordering is important. We want packetAvailable to supercede terminate otherwise we will not properly wait for all + // packets to be sent by other threads which are shutting down, such as the logging thread. + WaitHandle[] handles = new WaitHandle[] + { +#if NET451_OR_GREATER || NETCOREAPP + ((IAsyncResult)readTask).AsyncWaitHandle, +#else + result.AsyncWaitHandle, +#endif + localPacketAvailable, + localTerminatePacketPump, + }; + + bool exitLoop = false; + do + { + int waitId = WaitHandle.WaitAny(handles); + switch (waitId) + { + case 0: + { + int bytesRead = 0; + try + { +#if NET451_OR_GREATER || NETCOREAPP + bytesRead = readTask.ConfigureAwait(false).GetAwaiter().GetResult(); +#else + bytesRead = localReadPipe.EndRead(result); +#endif + } + catch (Exception e) + { + // Lost communications. Abort (but allow node reuse) + CommunicationsUtilities.Trace("Exception reading from server. {0}", e); + ExceptionHandling.DumpExceptionToFile(e); + ChangeLinkStatus(LinkStatus.Inactive); + exitLoop = true; + break; + } + + if (bytesRead != headerByte.Length) + { + // Incomplete read. Abort. + if (bytesRead == 0) + { + if (_isClientDisconnecting) + { + CommunicationsUtilities.Trace("Parent disconnected gracefully."); + // Do not change link status to failed as this could make node think connection has failed + // and recycle node, while this is perfectly expected and handled race condition + // (both client and node is about to close pipe and client can be faster). + } + else + { + CommunicationsUtilities.Trace("Parent disconnected abruptly."); + ChangeLinkStatus(LinkStatus.Failed); + } + } + else + { + CommunicationsUtilities.Trace("Incomplete header read from server. {0} of {1} bytes read", bytesRead, headerByte.Length); + ChangeLinkStatus(LinkStatus.Failed); + } + + exitLoop = true; + break; + } + + // Check if this packet has an extended header that includes a version part. + byte rawType = headerByte[0]; + bool hasExtendedHeader = NodePacketTypeExtensions.HasExtendedHeader(rawType); + NodePacketType packetType = hasExtendedHeader ? NodePacketTypeExtensions.GetNodePacketType(rawType) : (NodePacketType)rawType; + + byte parentVersion = 0; + if (hasExtendedHeader) + { + parentVersion = NodePacketTypeExtensions.ReadVersion(localReadPipe); + } + + try + { + ITranslator readTranslator = BinaryTranslator.GetReadTranslator(localReadPipe, _sharedReadBuffer); + + // parent sends a packet version that is already negotiated during handshake. + readTranslator.NegotiatedPacketVersion = parentVersion; + _packetFactory.DeserializeAndRoutePacket(0, packetType, readTranslator); + } + catch (Exception e) + { + // Error while deserializing or handling packet. Abort. + CommunicationsUtilities.Trace("Exception while deserializing packet {0}: {1}", packetType, e); + ExceptionHandling.DumpExceptionToFile(e); + ChangeLinkStatus(LinkStatus.Failed); + exitLoop = true; + break; + } + +#if NET451_OR_GREATER + readTask = localReadPipe.ReadAsync(headerByte, 0, headerByte.Length, CancellationToken.None); +#elif NETCOREAPP + readTask = localReadPipe.ReadAsync(headerByte.AsMemory(), CancellationToken.None).AsTask(); +#else + result = localReadPipe.BeginRead(headerByte, 0, headerByte.Length, null, null); +#endif + +#if NET451_OR_GREATER || NETCOREAPP + handles[0] = ((IAsyncResult)readTask).AsyncWaitHandle; +#else + handles[0] = result.AsyncWaitHandle; +#endif + } + + break; + + case 1: + case 2: + try + { + // Write out all the queued packets. + INodePacket packet; + while (localPacketQueue.TryDequeue(out packet)) + { + var packetStream = _packetStream; + packetStream.SetLength(0); + + // Re-use writeTranslator; we clear _packetStream but never replace it. + // If _packetStream is ever reassigned, set writeTranslator = null first. + writeTranslator ??= BinaryTranslator.GetWriteTranslator(packetStream); + + packetStream.WriteByte((byte)packet.Type); + + // Pad for packet length + _binaryWriter.Write(0); + + // Reset the position in the write buffer. + packet.Translate(writeTranslator); + + int packetStreamLength = (int)packetStream.Position; + + // Now write in the actual packet length + packetStream.Position = 1; + _binaryWriter.Write(packetStreamLength - 5); + + localWritePipe.Write(packetStream.GetBuffer(), 0, packetStreamLength); + } + } + catch (Exception e) + { + // Error while deserializing or handling packet. Abort. + CommunicationsUtilities.Trace("Exception while serializing packets: {0}", e); + ExceptionHandling.DumpExceptionToFile(e); + ChangeLinkStatus(LinkStatus.Failed); + exitLoop = true; + break; + } + + if (waitId == 2) + { + CommunicationsUtilities.Trace("Disconnecting voluntarily"); + ChangeLinkStatus(LinkStatus.Failed); + exitLoop = true; + } + + break; + + default: + ErrorUtilities.ThrowInternalError("waitId {0} out of range.", waitId); + break; + } + } + while (!exitLoop); + } + +#endregion + +#endregion + } +} diff --git a/src/MSBuildTaskHost/NodeEngineShutdownReason.cs b/src/MSBuildTaskHost/NodeEngineShutdownReason.cs new file mode 100644 index 00000000000..dc4f0f3069c --- /dev/null +++ b/src/MSBuildTaskHost/NodeEngineShutdownReason.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +namespace Microsoft.Build.Execution +{ + #region Enums + /// + /// Reasons for a node to shutdown. + /// + public enum NodeEngineShutdownReason + { + /// + /// The BuildManager sent a command instructing the node to terminate. + /// + BuildComplete, + + /// + /// The BuildManager sent a command instructing the node to terminate, but to restart for reuse. + /// + BuildCompleteReuse, + + /// + /// The communication link failed. + /// + ConnectionFailed, + + /// + /// The NodeEngine caught an exception which requires the Node to shut down. + /// + Error, + } + #endregion +} diff --git a/src/MSBuildTaskHost/NodePacketFactory.cs b/src/MSBuildTaskHost/NodePacketFactory.cs new file mode 100644 index 00000000000..478c88310eb --- /dev/null +++ b/src/MSBuildTaskHost/NodePacketFactory.cs @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Microsoft.Build.Shared; + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + /// + /// Implementation of INodePacketFactory as a helper class for classes which expose this interface publicly. + /// + internal class NodePacketFactory : INodePacketFactory + { + /// + /// Mapping of packet types to factory information. + /// + private Dictionary _packetFactories; + + /// + /// Constructor + /// + public NodePacketFactory() + { + _packetFactories = new Dictionary(); + } + + #region INodePacketFactory Members + + /// + /// Registers a packet handler + /// + public void RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory, INodePacketHandler handler) + { + _packetFactories[packetType] = new PacketFactoryRecord(handler, factory); + } + + /// + /// Unregisters a packet handler. + /// + public void UnregisterPacketHandler(NodePacketType packetType) + { + _packetFactories.Remove(packetType); + } + + /// + /// Creates and routes a packet with data from a binary stream. + /// + public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITranslator translator) + { + // PERF: Not using VerifyThrow to avoid boxing of packetType in the non-error case + if (!_packetFactories.TryGetValue(packetType, out PacketFactoryRecord record)) + { + ErrorUtilities.ThrowInternalError("No packet handler for type {0}", packetType); + } + + INodePacket packet = record.DeserializePacket(translator); + record.RoutePacket(nodeId, packet); + } + + /// + /// Creates a packet with data from a binary stream. + /// + public INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator) + { + // PERF: Not using VerifyThrow to avoid boxing of packetType in the non-error case + if (!_packetFactories.TryGetValue(packetType, out PacketFactoryRecord record)) + { + ErrorUtilities.ThrowInternalError("No packet handler for type {0}", packetType); + } + + return record.DeserializePacket(translator); + } + + /// + /// Routes the specified packet. + /// + public void RoutePacket(int nodeId, INodePacket packet) + { + // PERF: Not using VerifyThrow to avoid boxing of packetType in the non-error case + if (!_packetFactories.TryGetValue(packet.Type, out PacketFactoryRecord record)) + { + ErrorUtilities.ThrowInternalError("No packet handler for type {0}", packet.Type); + } + + record.RoutePacket(nodeId, packet); + } + + #endregion + + /// + /// A record for a packet factory + /// + private class PacketFactoryRecord + { + /// + /// The handler to invoke when the packet is deserialized. + /// + private readonly INodePacketHandler _handler; + + /// + /// The method used to construct a packet from a translator stream. + /// + private readonly NodePacketFactoryMethod _factoryMethod; + + /// + /// Constructor. + /// + public PacketFactoryRecord(INodePacketHandler handler, NodePacketFactoryMethod factoryMethod) + { + _handler = handler; + _factoryMethod = factoryMethod; + } + + /// + /// Creates a packet from a binary stream. + /// + public INodePacket DeserializePacket(ITranslator translator) => _factoryMethod(translator); + + /// + /// Routes the packet to the correct destination. + /// + public void RoutePacket(int nodeId, INodePacket packet) => _handler.PacketReceived(nodeId, packet); + } + } +} diff --git a/src/MSBuildTaskHost/NodeShutdown.cs b/src/MSBuildTaskHost/NodeShutdown.cs new file mode 100644 index 00000000000..9ce9426799e --- /dev/null +++ b/src/MSBuildTaskHost/NodeShutdown.cs @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + /// + /// Reasons why the node shut down. + /// + internal enum NodeShutdownReason + { + /// + /// The node shut down because it was requested to shut down. + /// + Requested, + + /// + /// The node shut down because of an error. + /// + Error, + + /// + /// The node shut down because the connection failed. + /// + ConnectionFailed, + } + + /// + /// Implementation of INodePacket for the packet informing the build manager than a node has shut down. + /// This is the last packet the BuildManager will receive from a Node, and as such can be used to trigger + /// any appropriate cleanup behavior. + /// + internal class NodeShutdown : INodePacket + { + /// + /// The reason the node shut down. + /// + private NodeShutdownReason _reason; + + /// + /// The exception - if any. + /// + private Exception _exception; + + /// + /// Constructor + /// + public NodeShutdown(NodeShutdownReason reason) + : this(reason, null) + { + } + + /// + /// Constructor + /// + public NodeShutdown(NodeShutdownReason reason, Exception e) + { + _reason = reason; + _exception = e; + } + + /// + /// Constructor for deserialization + /// + private NodeShutdown() + { + } + + #region INodePacket Members + + /// + /// Returns the packet type. + /// + public NodePacketType Type + { + get { return NodePacketType.NodeShutdown; } + } + + #endregion + + /// + /// The reason for shutting down. + /// + public NodeShutdownReason Reason + { + get { return _reason; } + } + + /// + /// The exception, if any. + /// + public Exception Exception + { + get { return _exception; } + } + + #region INodePacketTranslatable Members + + /// + /// Serializes or deserializes a packet. + /// + public void Translate(ITranslator translator) + { + translator.TranslateEnum(ref _reason, (int)_reason); + translator.TranslateException(ref _exception); + } + + /// + /// Factory method for deserialization + /// + internal static NodeShutdown FactoryForDeserialization(ITranslator translator) + { + NodeShutdown shutdown = new NodeShutdown(); + shutdown.Translate(translator); + return shutdown; + } + + #endregion + } +} diff --git a/src/MSBuildTaskHost/OutOfProcTaskHostTaskResult.cs b/src/MSBuildTaskHost/OutOfProcTaskHostTaskResult.cs new file mode 100644 index 00000000000..25c00061733 --- /dev/null +++ b/src/MSBuildTaskHost/OutOfProcTaskHostTaskResult.cs @@ -0,0 +1,136 @@ +// 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; + +using Microsoft.Build.BackEnd; + +#nullable disable + +namespace Microsoft.Build.Shared +{ + /// + /// A result of executing a target or task. + /// + internal class OutOfProcTaskHostTaskResult + { + /// + /// Constructor + /// + internal OutOfProcTaskHostTaskResult(TaskCompleteType result) + : this(result, null /* no final parameters */, null /* no exception */, null /* no exception message */, null /* and no args to go with it */) + { + // do nothing else + } + + /// + /// Constructor + /// + internal OutOfProcTaskHostTaskResult(TaskCompleteType result, IDictionary finalParams) + : this(result, finalParams, null /* no exception */, null /* no exception message */, null /* and no args to go with it */) + { + // do nothing else + } + + /// + /// Constructor + /// + internal OutOfProcTaskHostTaskResult(TaskCompleteType result, Exception taskException) + : this(result, taskException, null /* no exception message */, null /* and no args to go with it */) + { + // do nothing else + } + + /// + /// Constructor + /// + internal OutOfProcTaskHostTaskResult(TaskCompleteType result, Exception taskException, string exceptionMessage, string[] exceptionMessageArgs) + : this(result, null /* no final parameters */, taskException, exceptionMessage, exceptionMessageArgs) + { + // do nothing else + } + + /// + /// Constructor + /// + internal OutOfProcTaskHostTaskResult(TaskCompleteType result, IDictionary finalParams, Exception taskException, string exceptionMessage, string[] exceptionMessageArgs) + { + // If we're returning a crashing result, we should always also be returning the exception that caused the crash, although + // we may not always be returning an accompanying message. + if (result == TaskCompleteType.CrashedDuringInitialization || + result == TaskCompleteType.CrashedDuringExecution || + result == TaskCompleteType.CrashedAfterExecution) + { + ErrorUtilities.VerifyThrowInternalNull(taskException); + } + + if (exceptionMessage != null) + { + ErrorUtilities.VerifyThrow( + result == TaskCompleteType.CrashedDuringInitialization || + result == TaskCompleteType.CrashedDuringExecution || + result == TaskCompleteType.CrashedAfterExecution, + "If we have an exception message, the result type should be 'crashed' of some variety."); + } + + if (exceptionMessageArgs?.Length > 0) + { + ErrorUtilities.VerifyThrow(exceptionMessage != null, "If we have message args, we need a message."); + } + + Result = result; + FinalParameterValues = finalParams; + TaskException = taskException; + ExceptionMessage = exceptionMessage; + ExceptionMessageArgs = exceptionMessageArgs; + } + + /// + /// The overall result of the task execution. + /// + public TaskCompleteType Result + { + get; + private set; + } + + /// + /// Dictionary of the final values of the task parameters + /// + public IDictionary FinalParameterValues + { + get; + private set; + } + + /// + /// The exception thrown by the task during initialization or execution, + /// if any. + /// + public Exception TaskException + { + get; + private set; + } + + /// + /// The name of the resource representing the message to be logged along with the + /// above exception. + /// + public string ExceptionMessage + { + get; + private set; + } + + /// + /// The arguments to be used when formatting ExceptionMessage + /// + public string[] ExceptionMessageArgs + { + get; + private set; + } + } +} diff --git a/src/MSBuildTaskHost/ReadOnlyEmptyCollection.cs b/src/MSBuildTaskHost/ReadOnlyEmptyCollection.cs new file mode 100644 index 00000000000..128480dca68 --- /dev/null +++ b/src/MSBuildTaskHost/ReadOnlyEmptyCollection.cs @@ -0,0 +1,145 @@ +// 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; +using System.Collections.Generic; +using Microsoft.Build.Shared; + +#nullable disable + +namespace Microsoft.Build.Collections +{ + /// + /// A read-only wrapper over an empty collection. + /// + /// + /// Thus this is an omission from the BCL. + /// + /// Type of element in the collection + internal class ReadOnlyEmptyCollection : ICollection, ICollection + { + /// + /// Backing live collection + /// + private static ReadOnlyEmptyCollection s_instance; + + /// + /// Private default constructor as this is a singleton + /// + private ReadOnlyEmptyCollection() + { + } + + /// + /// Get the instance + /// + public static ReadOnlyEmptyCollection Instance + { + get + { + if (s_instance == null) + { + s_instance = new ReadOnlyEmptyCollection(); + } + + return s_instance; + } + } + + /// + /// Pass through for underlying collection + /// + public int Count + { + get { return 0; } + } + + /// + /// Returns true. + /// + public bool IsReadOnly + { + get { return true; } + } + + /// + /// Whether collection is synchronized + /// + bool ICollection.IsSynchronized + { + get { return false; } + } + + /// + /// Sync root + /// + object ICollection.SyncRoot + { + get { return this; } + } + + /// + /// Prohibited on read only collection: throws + /// + public void Add(T item) + { + ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); + } + + /// + /// Prohibited on read only collection: throws + /// + public void Clear() + { + ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); + } + + /// + /// Pass through for underlying collection + /// + public bool Contains(T item) + { + return false; + } + + /// + /// Pass through for underlying collection + /// + public void CopyTo(T[] array, int arrayIndex) + { + } + + /// + /// Prohibited on read only collection: throws + /// + public bool Remove(T item) + { + ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); + return false; + } + + /// + /// Get an enumerator over an empty collection + /// + public IEnumerator GetEnumerator() + { + yield break; + } + + /// + /// Get an enumerator over an empty collection + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// ICollection version of CopyTo + /// + void ICollection.CopyTo(Array array, int index) + { + } + } +} diff --git a/src/MSBuildTaskHost/ReadOnlyEmptyDictionary.cs b/src/MSBuildTaskHost/ReadOnlyEmptyDictionary.cs new file mode 100644 index 00000000000..46b1b2738e9 --- /dev/null +++ b/src/MSBuildTaskHost/ReadOnlyEmptyDictionary.cs @@ -0,0 +1,329 @@ +// 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; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Build.Shared; + +#nullable disable + +namespace Microsoft.Build.Collections +{ + /// + /// A special singleton enumerable that enumerates a read-only empty dictionary + /// + /// Key + /// Value + internal class ReadOnlyEmptyDictionary : IDictionary, IReadOnlyDictionary, IDictionary + { + /// + /// The single instance + /// + private static readonly Dictionary s_backing = new Dictionary(); + + /// + /// The single instance + /// + private static ReadOnlyEmptyDictionary s_instance; + + /// + /// Private default constructor as this is a singleton + /// + private ReadOnlyEmptyDictionary() + { + } + + /// + /// Get the instance + /// + public static ReadOnlyEmptyDictionary Instance + { + get + { + if (s_instance == null) + { + s_instance = new ReadOnlyEmptyDictionary(); + } + + return s_instance; + } + } + + /// + /// Empty returns zero + /// + public int Count + { + get { return 0; } + } + + /// + /// Returns true + /// + public bool IsReadOnly + { + get { return true; } + } + + /// + /// Gets empty collection + /// + public ICollection Keys => +#if CLR2COMPATIBILITY + new K[0]; +#else + Array.Empty(); +#endif + + /// + /// Gets empty collection + /// + public ICollection Values => +#if CLR2COMPATIBILITY + new V[0]; +#else + Array.Empty(); +#endif + + /// + /// Is it fixed size + /// + public bool IsFixedSize + { + get { return true; } + } + + /// + /// Not synchronized + /// + public bool IsSynchronized + { + get { return false; } + } + + /// + /// No sync root + /// + public object SyncRoot + { + get { return null; } + } + + /// + /// Keys + /// + ICollection IDictionary.Keys + { + get { return (ICollection)((IDictionary)this).Keys; } + } + + /// + /// Values + /// + ICollection IDictionary.Values + { + get { return (ICollection)((IDictionary)this).Values; } + } + + /// + /// Keys + /// + IEnumerable IReadOnlyDictionary.Keys + { + get { return Keys; } + } + + /// + /// Values + /// + IEnumerable IReadOnlyDictionary.Values + { + get { return Values; } + } + + /// + /// Indexer + /// + public object this[object key] + { + get + { + return ((IDictionary)this)[(K)key]; + } + + set + { + ((IDictionary)this)[(K)key] = (V)value; + } + } + + /// + /// Get returns null as read-only + /// Set is prohibited and throws. + /// + public V this[K key] + { + get + { + // Trigger KeyNotFoundException + return new Dictionary()[key]; + } + + set + { + ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); + } + } + + /// + /// Pass through for underlying collection + /// + public void Add(K key, V value) + { + ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); + } + + /// + /// Empty returns false + /// + public bool ContainsKey(K key) + { + return false; + } + + /// + /// Prohibited on read only collection: throws + /// + public bool Remove(K key) + { + ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); + return false; + } + + /// + /// Empty returns false + /// + public bool TryGetValue(K key, out V value) + { + value = default(V); + return false; + } + + /// + /// Prohibited on read only collection: throws + /// + public void Add(KeyValuePair item) + { + ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); + } + + /// + /// Prohibited on read only collection: throws + /// + public void Clear() + { + ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); + } + + /// + /// Empty returns false + /// + public bool Contains(KeyValuePair item) + { + return false; + } + + /// + /// Empty does nothing + /// + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + } + + /// + /// Prohibited on read only collection: throws + /// + public bool Remove(KeyValuePair item) + { + ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); + return false; + } + + /// + /// Get empty enumerator + /// + public IEnumerator> GetEnumerator() + { + return Enumerable.Empty>().GetEnumerator(); + } + + /// + /// Get empty enumerator + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Add + /// + public void Add(object key, object value) + { + ((IDictionary)this).Add((K)key, (V)value); + } + + /// + /// Contains + /// + public bool Contains(object key) + { + return ((IDictionary)this).ContainsKey((K)key); + } + + /// + /// Enumerator + /// + IDictionaryEnumerator IDictionary.GetEnumerator() + { + return ((IDictionary)s_backing).GetEnumerator(); + } + + /// + /// Remove + /// + public void Remove(object key) + { + ((IDictionary)this).Remove((K)key); + } + + /// + /// CopyTo + /// + public void CopyTo(System.Array array, int index) + { + // Nothing to do + } + } +} + +#if NET35 +namespace System.Collections.Generic +{ + public interface IReadOnlyCollection : IEnumerable + { + int Count { get; } + } + + public interface IReadOnlyDictionary : IReadOnlyCollection> + { + TValue this[TKey key] { get; } + IEnumerable Keys { get; } + IEnumerable Values { get; } + bool ContainsKey(TKey key); + bool TryGetValue(TKey key, out TValue value); + } +} +#endif diff --git a/src/MSBuildTaskHost/ResourceUtilities.cs b/src/MSBuildTaskHost/ResourceUtilities.cs new file mode 100644 index 00000000000..9d1ccedb342 --- /dev/null +++ b/src/MSBuildTaskHost/ResourceUtilities.cs @@ -0,0 +1,481 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +#if !BUILDINGAPPXTASKS +using System.Resources; +using System.Diagnostics; +#endif +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +#if BUILDINGAPPXTASKS +namespace Microsoft.Build.AppxPackage.Shared +#else +namespace Microsoft.Build.Shared +#endif +{ + /// + /// This class contains utility methods for dealing with resources. + /// + internal static class ResourceUtilities + { + /// + /// Extracts the message code (if any) prefixed to the given string. + /// MSB\d\d\d\d):\s*(?.*)$" + /// Arbitrary codes match "^\s*(?[A-Za-z]+\d+):\s*(?.*)$" + /// ]]> + /// Thread safe. + /// + /// Whether to match only MSBuild error codes, or any error code. + /// The string to parse. + /// [out] The message code, or null if there was no code. + /// The string without its message code prefix, if any. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "Microsoft.Build.Shared.ResourceUtilities.#ExtractMessageCode(System.Boolean,System.String,System.String&)", Justification = "Unavoidable complexity")] + internal static string ExtractMessageCode(bool msbuildCodeOnly, string message, out string? code) + { +#if !BUILDINGAPPXTASKS + ErrorUtilities.VerifyThrowInternalNull(message); +#endif + + code = null; + int i = 0; + + while (i < message.Length && Char.IsWhiteSpace(message[i])) + { + i++; + } + +#if !BUILDINGAPPXTASKS + if (msbuildCodeOnly) + { + if ( + message.Length < i + 8 || + message[i] != 'M' || + message[i + 1] != 'S' || + message[i + 2] != 'B' || + message[i + 3] < '0' || message[i + 3] > '9' || + message[i + 4] < '0' || message[i + 4] > '9' || + message[i + 5] < '0' || message[i + 5] > '9' || + message[i + 6] < '0' || message[i + 6] > '9' || + message[i + 7] != ':') + { + return message; + } + + code = message.Substring(i, 7); + + i += 8; + } + else +#endif + { + int j = i; + for (; j < message.Length; j++) + { + char c = message[j]; + if (((c < 'a') || (c > 'z')) && ((c < 'A') || (c > 'Z'))) + { + break; + } + } + + if (j == i) + { + return message; // Should have been at least one letter + } + + int k = j; + + for (; k < message.Length; k++) + { + char c = message[k]; + if (c < '0' || c > '9') + { + break; + } + } + + if (k == j) + { + return message; // Should have been at least one digit + } + + if (k == message.Length || message[k] != ':') + { + return message; + } + + code = message.Substring(i, k - i); + + i = k + 1; + } + + while (i < message.Length && Char.IsWhiteSpace(message[i])) + { + i++; + } + + if (i < message.Length) + { + message = message.Substring(i); + } + + return message; + } + + /// + /// Retrieves the MSBuild F1-help keyword for the given resource string. Help keywords are used to index help topics in + /// host IDEs. + /// + /// Resource string to get the MSBuild F1-keyword for. + /// The MSBuild F1-help keyword string. + private static string GetHelpKeyword(string resourceName) + => "MSBuild." + resourceName; + +#if !BUILDINGAPPXTASKS + /// + /// Retrieves the contents of the named resource string. + /// + /// Resource string name. + /// Resource string contents. + internal static string GetResourceString(string resourceName) + => AssemblyResources.GetString(resourceName); + + /// + /// Loads the specified string resource and formats it with the arguments passed in. If the string resource has an MSBuild + /// message code and help keyword associated with it, they too are returned. + /// + /// PERF WARNING: calling a method that takes a variable number of arguments is expensive, because memory is allocated for + /// the array of arguments -- do not call this method repeatedly in performance-critical scenarios. + /// + /// This method is thread-safe. + /// [out] The MSBuild message code, or null. + /// [out] The MSBuild F1-help keyword for the host IDE, or null. + /// Resource string to load. + /// Optional arguments for formatting the resource string. + /// The formatted resource string. + internal static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName, params object?[]? args) + { + helpKeyword = GetHelpKeyword(resourceName); + + // NOTE: the AssemblyResources.GetString() method is thread-safe + return ExtractMessageCode(true /* msbuildCodeOnly */, FormatString(GetResourceString(resourceName), args), out code); + } + + // Overloads with 0-3 arguments to avoid array allocations. + + /// + /// Loads the specified string resource and formats it with the arguments passed in. If the string resource has an MSBuild + /// message code and help keyword associated with it, they too are returned. + /// + /// This method is thread-safe. + /// [out] The MSBuild message code, or null. + /// [out] The MSBuild F1-help keyword for the host IDE, or null. + /// Resource string to load. + /// The formatted resource string. + internal static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName) + { + helpKeyword = GetHelpKeyword(resourceName); + return ExtractMessageCode(true, GetResourceString(resourceName), out code); + } + + /// + /// Loads the specified string resource and formats it with the arguments passed in. If the string resource has an MSBuild + /// message code and help keyword associated with it, they too are returned. + /// + /// [out] The MSBuild message code, or null. + /// [out] The MSBuild F1-help keyword for the host IDE, or null. + /// Resource string to load. + /// Argument for formatting the resource string. + internal static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName, object? arg1) + { + helpKeyword = GetHelpKeyword(resourceName); + return ExtractMessageCode(true, FormatString(GetResourceString(resourceName), arg1), out code); + } + + /// + /// Loads the specified string resource and formats it with the arguments passed in. If the string resource has an MSBuild + /// message code and help keyword associated with it, they too are returned. + /// + /// [out] The MSBuild message code, or null. + /// [out] The MSBuild F1-help keyword for the host IDE, or null. + /// Resource string to load. + /// First argument for formatting the resource string. + /// Second argument for formatting the resource string. + internal static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName, object? arg1, object? arg2) + { + helpKeyword = GetHelpKeyword(resourceName); + return ExtractMessageCode(true, FormatString(GetResourceString(resourceName), arg1, arg2), out code); + } + + /// + /// Loads the specified string resource and formats it with the arguments passed in. If the string resource has an MSBuild + /// message code and help keyword associated with it, they too are returned. + /// + /// [out] The MSBuild message code, or null. + /// [out] The MSBuild F1-help keyword for the host IDE, or null. + /// Resource string to load. + /// First argument for formatting the resource string. + /// Second argument for formatting the resource string. + /// Third argument for formatting the resource string. + internal static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName, object? arg1, object? arg2, object? arg3) + { + helpKeyword = GetHelpKeyword(resourceName); + return ExtractMessageCode(true, FormatString(GetResourceString(resourceName), arg1, arg2, arg3), out code); + } + + /// + /// Looks up a string in the resources, and formats it with the arguments passed in. If the string resource has an MSBuild + /// message code and help keyword associated with it, they are discarded. + /// + /// PERF WARNING: calling a method that takes a variable number of arguments is expensive, because memory is allocated for + /// the array of arguments -- do not call this method repeatedly in performance-critical scenarios. + /// + /// This method is thread-safe. + /// Resource string to load. + /// Optional arguments for formatting the resource string. + /// The formatted resource string. + internal static string FormatResourceStringStripCodeAndKeyword(string resourceName, params object?[]? args) + => FormatResourceStringStripCodeAndKeyword(out _, out _, resourceName, args); + + // Overloads with 0-3 arguments to avoid array allocations. + + /// + /// Looks up a string in the resources. If the string resource has an MSBuild + /// message code and help keyword associated with it, they are discarded. + /// + /// This method is thread-safe. + /// Resource string to load. + /// The formatted resource string. + internal static string FormatResourceStringStripCodeAndKeyword(string resourceName) + => FormatResourceStringStripCodeAndKeyword(out _, out _, resourceName); + + /// + /// Looks up a string in the resources, and formats it with the argument passed in. If the string resource has an MSBuild + /// message code and help keyword associated with it, they are discarded. + /// + /// This method is thread-safe. + /// Resource string to load. + /// Argument for formatting the resource string. + /// The formatted resource string. + internal static string FormatResourceStringStripCodeAndKeyword(string resourceName, object? arg1) + => FormatResourceStringStripCodeAndKeyword(out _, out _, resourceName, arg1); + + /// + /// Looks up a string in the resources, and formats it with the arguments passed in. If the string resource has an MSBuild + /// message code and help keyword associated with it, they are discarded. + /// + /// This method is thread-safe. + /// Resource string to load. + /// First argument for formatting the resource string. + /// Second argument for formatting the resource string. + /// The formatted resource string. + internal static string FormatResourceStringStripCodeAndKeyword(string resourceName, object? arg1, object? arg2) + => FormatResourceStringStripCodeAndKeyword(out _, out _, resourceName, arg1, arg2); + + /// + /// Looks up a string in the resources, and formats it with the arguments passed in. If the string resource has an MSBuild + /// message code and help keyword associated with it, they are discarded. + /// + /// This method is thread-safe. + /// Resource string to load. + /// First argument for formatting the resource string. + /// Second argument for formatting the resource string. + /// Third argument for formatting the resource string. + /// The formatted resource string. + internal static string FormatResourceStringStripCodeAndKeyword(string resourceName, object? arg1, object? arg2, object? arg3) + => FormatResourceStringStripCodeAndKeyword(out _, out _, resourceName, arg1, arg2, arg3); + + /// + /// Formats the resource string with the given arguments. + /// Ignores error codes and keywords. + /// + /// Resource string to load. + /// Optional arguments for formatting the resource string. + /// The formatted resource string. + /// the AssemblyResources.GetString() method is thread-safe. + internal static string FormatResourceStringIgnoreCodeAndKeyword(string resourceName, params object?[]? args) + => FormatString(GetResourceString(resourceName), args); + + // Overloads with 0-3 arguments to avoid array allocations. + + /// + /// Formats the resource string. + /// Ignores error codes and keywords. + /// + /// Resource string to load. + /// The formatted resource string. + internal static string FormatResourceStringIgnoreCodeAndKeyword(string resourceName) + => GetResourceString(resourceName); + + /// + /// Formats the resource string with the given argument. + /// Ignores error codes and keywords. + /// + /// Resource string to load. + /// Argument for formatting the resource string. + /// The formatted resource string. + internal static string FormatResourceStringIgnoreCodeAndKeyword(string resourceName, object? arg1) + => FormatString(GetResourceString(resourceName), arg1); + + /// + /// Formats the resource string with the given arguments. + /// Ignores error codes and keywords. + /// + /// Resource string to load. + /// First argument for formatting the resource string. + /// Second argument for formatting the resource string. + /// The formatted resource string. + internal static string FormatResourceStringIgnoreCodeAndKeyword(string resourceName, object? arg1, object? arg2) + => FormatString(GetResourceString(resourceName), arg1, arg2); + + /// + /// Formats the resource string with the given arguments. + /// Ignores error codes and keywords. + /// + /// Resource string to load. + /// First argument for formatting the resource string. + /// Second argument for formatting the resource string. + /// Third argument for formatting the resource string. + /// The formatted resource string. + internal static string FormatResourceStringIgnoreCodeAndKeyword(string resourceName, object? arg1, object? arg2, object? arg3) + => FormatString(GetResourceString(resourceName), arg1, arg2, arg3); + + /// + /// Formats the given string using the variable arguments passed in. + /// + /// PERF WARNING: calling a method that takes a variable number of arguments is expensive, because memory is allocated for + /// the array of arguments -- do not call this method repeatedly in performance-critical scenarios + /// + /// Thread safe. + /// + /// The string to format. + /// Optional arguments for formatting the given string. + /// The formatted string. + internal static string FormatString(string unformatted, params object?[]? args) + { + string formatted = unformatted; + + // NOTE: String.Format() does not allow a null arguments array + if (args?.Length > 0) + { + ValidateArgsIfDebug(args); + + // Format the string, using the variable arguments passed in. + // NOTE: all String methods are thread-safe + formatted = string.Format(CultureInfo.CurrentCulture, unformatted, args); + } + + return formatted; + } + + // Overloads with 1-3 arguments to avoid array allocations. + + /// + /// Formats the given string using the variable arguments passed in. + /// + /// The string to format. + /// Argument for formatting the given string. + /// The formatted string. + internal static string FormatString(string unformatted, object? arg1) + { + ValidateArgsIfDebug([arg1]); + return string.Format(CultureInfo.CurrentCulture, unformatted, arg1); + } + + /// + /// Formats the given string using the variable arguments passed in. + /// + /// The string to format. + /// First argument for formatting the given string. + /// Second argument for formatting the given string. + /// The formatted string. + internal static string FormatString(string unformatted, object? arg1, object? arg2) + { + ValidateArgsIfDebug([arg1, arg2]); + return string.Format(CultureInfo.CurrentCulture, unformatted, arg1, arg2); + } + + /// + /// Formats the given string using the variable arguments passed in. + /// + /// The string to format. + /// First argument for formatting the given string. + /// Second argument for formatting the given string. + /// Third argument for formatting the given string. + /// The formatted string. + internal static string FormatString(string unformatted, object? arg1, object? arg2, object? arg3) + { + ValidateArgsIfDebug([arg1, arg2, arg3]); + return string.Format(CultureInfo.CurrentCulture, unformatted, arg1, arg2, arg3); + } + + [Conditional("DEBUG")] + private static void ValidateArgsIfDebug(object?[] args) + { + // If you accidentally pass some random type in that can't be converted to a string, + // FormatResourceString calls ToString() which returns the full name of the type! + foreach (object? param in args) + { + // Check it has a real implementation of ToString() and the type is not actually System.String + if (param != null) + { + if (string.Equals(param.GetType().ToString(), param.ToString(), StringComparison.Ordinal) && + param.GetType() != typeof(string)) + { + ErrorUtilities.ThrowInternalError( + "Invalid resource parameter type, was {0}", + param.GetType().FullName); + } + } + } + } + + /// + /// Verifies that a particular resource string actually exists in the string table. This will only be called in debug + /// builds. It helps catch situations where a dev calls VerifyThrowXXX with a new resource string, but forgets to add the + /// resource string to the string table, or misspells it! + /// + /// This method is thread-safe. + /// Resource string to check. + [Conditional("DEBUG")] + internal static void VerifyResourceStringExists(string resourceName) + { + try + { + // Look up the resource string in the engine's string table. + // NOTE: the AssemblyResources.GetString() method is thread-safe + string unformattedMessage = AssemblyResources.GetString(resourceName); + + if (unformattedMessage == null) + { + ErrorUtilities.ThrowInternalError("The resource string \"" + resourceName + "\" was not found."); + } + } + catch (ArgumentException e) + { +#if FEATURE_DEBUG_LAUNCH + Debug.Fail("The resource string \"" + resourceName + "\" was not found."); +#endif + ErrorUtilities.ThrowInternalError(e.Message); + } + catch (InvalidOperationException e) + { +#if FEATURE_DEBUG_LAUNCH + Debug.Fail("The resource string \"" + resourceName + "\" was not found."); +#endif + ErrorUtilities.ThrowInternalError(e.Message); + } + catch (MissingManifestResourceException e) + { +#if FEATURE_DEBUG_LAUNCH + Debug.Fail("The resource string \"" + resourceName + "\" was not found."); +#endif + ErrorUtilities.ThrowInternalError(e.Message); + } +#endif + } + } +} diff --git a/src/MSBuildTaskHost/Resources/Strings.shared.resx b/src/MSBuildTaskHost/Resources/Strings.shared.resx new file mode 100644 index 00000000000..6e29c8e68af --- /dev/null +++ b/src/MSBuildTaskHost/Resources/Strings.shared.resx @@ -0,0 +1,379 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + MSB4188: Build was canceled. + {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + Build started. + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + + + Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs + + + {0} ({1},{2}) + A file location to be embedded in a string. + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + MSB4025: The project file could not be loaded. {0} + {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is + invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is + provided separately to loggers. + LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. + + + MSB4103: "{0}" is not a valid logger verbosity level. + {StrBegin="MSB4103: "} + + + MSB4233: There was an exception while reading the log file: {0} + {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. + + + MSBuild is expecting a valid "{0}" object. + + + MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. + {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. + + + MSB5016: The name "{0}" contains an invalid character "{1}". + {StrBegin="MSB5016: "} + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The string "{0}" cannot be converted to a boolean (true/false) value. + + + MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + {StrBegin="MSB5003: "} + + + MSB5018: Failed to delete the temporary file "{0}". {1} {2} + {StrBegin="MSB5018: "} + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + + + .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. + + + Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. + + + When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} + No Error code because this resource will be used in an exception. The error code is discarded if it is included + + + Could not find directory path: {0} + Directory must exist + + + You do not have access to: {0} + Directory must have access + + + Schema validation + + UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: + "MSBUILD : Schema validation error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + + MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. + {StrBegin="MSB5002: "} + + + Parameter "{0}" cannot be null. + + + Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. + + + Parameter "{0}" cannot have zero length. + + + Parameters "{0}" and "{1}" must have the same number of elements. + + + The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. + + + The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. + LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. + + + MSB5004: The solution file has two projects named "{0}". + {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. + + + MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. + {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. + + + MSB5006: Error parsing project section for project "{0}". The project file name is empty. + {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. + + + MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. + {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. + + + MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. + {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the nested project section in solution file. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. + {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. + + + MSB5010: No file format header found. + {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. + + + MSB5011: Parent project GUID not found in "{0}" project dependency section. + {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. + + + MSB5012: Unexpected end-of-file reached inside "{0}" project section. + {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. + + + MSB5013: Error parsing a project section. + {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. + + + MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. + {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. + + + MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. + {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. + + + Unrecognized solution version "{0}", attempting to continue. + + + Solution file + UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: + "MSBUILD : Solution file error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + MSB5019: The project file is malformed: "{0}". {1} + {StrBegin="MSB5019: "} + + + MSB5020: Could not load the project file: "{0}". {1} + {StrBegin="MSB5020: "} + + + MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. + {StrBegin="MSB5021: "} + + + MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. + {StrBegin="MSB5024: "} + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + + + An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} + + + MSB5025: Json in solution filter file "{0}" is incorrectly formatted. + {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. + + + MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. + {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. + + + MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". + {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. + + + MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. + {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its + attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. + + + MSB6005: Task attempted to log before it was initialized. Message was: {0} + {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + + This collection is read-only. + + + The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). + + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + + diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.cs.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.cs.xlf new file mode 100644 index 00000000000..efc33efebe0 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.cs.xlf @@ -0,0 +1,349 @@ + + + + + + MSB4188: Build was canceled. + MSB4188: Sestavování bylo zrušeno. + {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: Hostitel úlohy nástroje MSBuild nepodporuje spouštění úloh, které provádějí zpětná volání rozhraní IBuildEngine. Pokud chcete tyto operace provádět, spusťte svou úlohu v hlavním procesu nástroje MSBuild. Úloha bude automaticky provedena v hostiteli úlohy, pokud v deklaraci UsingTask byly použity atributy s hodnotami Runtime nebo Architecture nebo pokud pro volání úlohy byly použity atributy s hodnotami MSBuildRuntime nebo MSBuildArchitecture, které neodpovídají aktuálnímu modulu runtime nebo architektuře nástroje MSBuild. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + Build started. + Vytváření sestavení bylo zahájeno. + + + + The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). + Počet elementů v kolekci je větší než dostupné místo v cílovém poli (při spuštění na zadaném indexu). + + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: Bylo nalezeno konfliktní sestavení pro sestavení úlohy {0} v umístění {1}. + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs + Vlastní typ události '{0}' se nepodporuje, protože všechny vlastní typy událostí jsou zastaralé. Použijte prosím místo toho Extended*EventArgs. Další informace: https://aka.ms/msbuild/eventargs + + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + Očekávalo se, že typ události {0} bude možné serializovat pomocí serializátoru .NET. Událost nebylo možné serializovat a byla ignorována. + + + + {0} ({1},{2}) + {0} ({1},{2}) + A file location to be embedded in a string. + + + MSB6005: Task attempted to log before it was initialized. Message was: {0} + MSB6005: Úloha se pokusila přihlásit před tím, než byla inicializována. Zpráva: {0} + {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: {0} je neplatná hodnota parametru Importance. Platné hodnoty: High, Normal a Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Předtím, než bude pro hostitele úlohy použito prostředí přijaté z nadřazeného uzlu, budou provedeny jeho následující úpravy: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Nastavení proměnné {0} na hodnotu {1} místo hodnoty {2} proměnné prostředí nadřazeného uzlu + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + MSB4025: The project file could not be loaded. {0} + MSB4025: Soubor projektu nelze načíst. {0} + {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is + invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is + provided separately to loggers. + LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. + + + MSB4103: "{0}" is not a valid logger verbosity level. + MSB4103: {0} není platná úroveň podrobností protokolovacího nástroje. + {StrBegin="MSB4103: "} + + + MSBuild is expecting a valid "{0}" object. + Nástroj MSBuild očekává platný objekt {0}. + + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + Cesta: {0} překračuje maximální limit pro cestu k OS. Plně kvalifikovaný název souboru musí být kratší než {1} znaků. + + + + MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". + MSB5028: Soubor filtru řešení v {0} obsahuje projekt {1}, který není v souboru řešení v {2}. + {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. + + + MSB5025: Json in solution filter file "{0}" is incorrectly formatted. + MSB5025: JSON v souboru filtru řešení {0} má nesprávný formát. + {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. + + + MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. + MSB5026: Soubor filtru řešení v {0} určuje, že v {1} se bude nacházet soubor řešení, ale tento soubor neexistuje. + {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. + MSB5009: Při analýze oddílu {0} projektu s identifikátorem GUID: {1} došlo k chybě. Je vnořený pod {2}, ale tento projekt nebyl v řešení nalezen. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. + MSB4132: Verze nástrojů {0} je neznámá. Dostupné verze nástrojů jsou {1}. + {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. + + + MSB5016: The name "{0}" contains an invalid character "{1}". + MSB5016: Název {0} obsahuje neplatný znak {1}. + {StrBegin="MSB5016: "} + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" jsou vyhrazená metadata položky a nemohou být změněna nebo smazána. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The string "{0}" cannot be converted to a boolean (true/false) value. + Řetězec {0} nelze převést na logickou hodnotu (true/false). + + + + MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + MSB5003: Vytvoření dočasného souboru se nepovedlo. Složka dočasných souborů je plná nebo cesta k této složce není správná. {0} + {StrBegin="MSB5003: "} + + + MSB5018: Failed to delete the temporary file "{0}". {1} {2} + MSB5018: Nepodařilo se odstranit dočasný soubor {0}. {1} {2} + {StrBegin="MSB5018: "} + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + Metadata položky %({0}) nelze použít na cestu {1}. {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: Úloha {0} byla označena atributem LoadInSeparateAppDomain, není ale odvozena od třídy MarshalByRefObject. Zkontrolujte, jestli je úloha odvozena od třídy MarshalByRefObject nebo AppDomainIsolatedTask. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + Verze {0} rozhraní .NET Framework není podporovaná. Zadejte hodnotu z výčtu Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + + + + .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. + Verze {0} rozhraní .NET Framework není podporovaná, pokud je jako cíl explicitně určená sada Windows SDK, která je podporovaná jenom v rozhraní .NET 4.5 nebo novějším. Určete hodnotu Version45 nebo vyšší z výčtu Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + + + + Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. + Verze {0} sady Visual Studio není podporovaná. Zadejte hodnotu z výčtu Microsoft.Build.Utilities.VisualStudioVersion. + + + + When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} + Při pokusu o vygenerování cesty odkazovaného sestavení z cesty {0} a monikeru rozhraní {1} došlo k chybě. {2} + No Error code because this resource will be used in an exception. The error code is discarded if it is included + + + Could not find directory path: {0} + Nebyla nalezena cesta k adresáři: {0} + Directory must exist + + + You do not have access to: {0} + Nemáte přístup k objektu {0}. + Directory must have access + + + Schema validation + Ověřování schématu + + UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: + "MSBUILD : Schema validation error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + + MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. + MSB5002: Spustitelný soubor úlohy {0} se ukončuje, protože nebyl dokončen ve stanoveném limitu {1} milisekund. + {StrBegin="MSB5002: "} + + + Parameter "{0}" cannot be null. + Parametr {0} nesmí mít hodnotu Null. + + + + Parameter "{0}" cannot have zero length. + Parametr {0} nesmí mít nulovou délku. + + + + Parameters "{0}" and "{1}" must have the same number of elements. + Parametry {0} a {1} musí mít stejný počet prvků. + + + + The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. + Řetězec prostředku {0} pro úlohu {1} nebyl nalezen. Zkontrolujte, jestli je název prostředku {0} napsaný správně a jestli tento prostředek existuje v sestavení úlohy. + + + + The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. + Úloha {0} nezaregistrovala své prostředky. Aby bylo možné použít metodu TaskLoggingHelper.FormatResourceString(), musí tato úloha zaregistrovat své prostředky během konstrukce nebo prostřednictvím vlastnosti TaskResources. + LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. + + + MSB5004: The solution file has two projects named "{0}". + MSB5004: Soubor řešení obsahuje dva projekty s názvem {0}. + {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. + + + MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. + MSB5005: Při analýze oddílu projektu pro projekt {0} došlo k chybě. Název souboru projektu {1} obsahuje neplatné znaky. + {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. + + + MSB5006: Error parsing project section for project "{0}". The project file name is empty. + MSB5006: Při analýze oddílu projektu pro projekt {0} došlo k chybě. Název souboru projektu je prázdný. + {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. + + + MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. + MSB5007: Při analýze oddílu konfigurace projektu v souboru řešení došlo k chybě. Položka {0} je neplatná. + {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. + + + MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. + MSB5008: Při analýze oddílu konfigurace řešení v souboru řešení došlo k chybě. Položka {0} je neplatná. + {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the nested project section in solution file. + MSB5009: Při analýze vnořeného oddílu projektu v souboru řešení došlo k chybě. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. + MSB5023: Při analýze vnořeného oddílu projektu v souboru řešení došlo k chybě. Projekt s GUID {0} je uveden jako vnořený pod projektem {1}, ale v daném řešení neexistuje. + {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. + + + MSB5010: No file format header found. + MSB5010: Nebyla nalezena žádná hlavička formátu souboru. + {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. + + + MSB5011: Parent project GUID not found in "{0}" project dependency section. + MSB5011: V oddílu projektových závislostí {0} nebyl nalezen identifikátor GUID nadřazeného projektu. + {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. + + + MSB5012: Unexpected end-of-file reached inside "{0}" project section. + MSB5012: V oddílu projektu {0} bylo dosaženo neočekávaného konce souboru. + {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. + + + MSB5013: Error parsing a project section. + MSB5013: Při analýze oddílu projektu došlo k chybě. + {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. + + + MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. + MSB5014: Nebyla rozpoznána verze formátu souboru. Nástroj MSBuild může číst jenom soubory řešení verzí {0}.0 až {1}.0 včetně. + {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. + + + MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. + MSB5015: Nelze číst vlastnosti z oddílu WebsiteProperties projektu {0}. + {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. + + + Unrecognized solution version "{0}", attempting to continue. + Nerozpoznaná verze řešení {0}, pokus o pokračování. + + + + Solution file + Soubor řešení + UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: + "MSBUILD : Solution file error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + MSB5019: The project file is malformed: "{0}". {1} + MSB5019: Soubor projektu je nesprávně utvořen: {0}. {1} + {StrBegin="MSB5019: "} + + + MSB5020: Could not load the project file: "{0}". {1} + MSB5020: Nepodařilo se načíst soubor projektu: {0}. {1} + {StrBegin="MSB5020: "} + + + MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. + MSB5021: Spustitelný soubor úlohy {0} a její podřízené procesy se ukončují, protože sestavení bylo zrušeno. + {StrBegin="MSB5021: "} + + + This collection is read-only. + Tato kolekce je jen pro čtení. + + + + MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. + MSB5024: Nešlo určit platné umístění pro MSBuild. Zkuste tento proces spustit z nástroje Developer Command Prompt for Visual Studio. + {StrBegin="MSB5024: "} + + + MSB4233: There was an exception while reading the log file: {0} + MSB4233: Došlo k výjimce při čtení souboru protokolu: {0}. + {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. + + + Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. + Parametr {0} s přiřazenou hodnotou {1} nesmí obsahovat neplatnou cestu nebo neplatné znaky souboru. + + + + An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} + Při rozbalování fileSpec s globs: fileSpec došlo k výjimce: {0}, za předpokladu, že se jedná o název souboru. Výjimka: {1} + + + + MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. + MSB5029: Hodnota {0} atributu {1} v elementu <{2}> v souboru {3}je zástupný znak, jehož výsledkem je výčet všech souborů na jednotce, což pravděpodobně nebylo zamýšleno. Zkontrolujte, zda jsou odkazované vlastnosti vždy definovány a zda projekt a aktuální pracovní adresář nejsou v kořenovém adresáři jednotky. + {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its + attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.de.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.de.xlf new file mode 100644 index 00000000000..a63a5c4e29d --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.de.xlf @@ -0,0 +1,349 @@ + + + + + + MSB4188: Build was canceled. + MSB4188: Der Buildvorgang wurde abgebrochen. + {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: Der MSBuild-Aufgabenhost unterstützt das Ausführen von Aufgaben nicht, die IBuildEngine-Rückrufe tätigen. Führen Sie Ihre Aufgabe stattdessen im MSBuild-Kernprozess aus, wenn Sie diese Vorgänge durchführen möchten. Wenn UsingTask mit einem Runtime- oder Architecture-Wert oder der Aufgabenaufruf mit einem MSBuildRuntime- oder MSBuildArchitecture-Wert attribuiert wurde, der nicht mit der aktuellen Laufzeit oder Architektur von MSBuild übereinstimmt, wird eine Aufgabe automatisch im Aufgabenhost ausgeführt. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + Build started. + Der Buildvorgang wurde gestartet. + + + + The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). + Die Anzahl der Elemente in der Auflistung ist größer als der verfügbare Speicherplatz im Zielarray (wenn am angegebenen Index begonnen wird). + + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: Eine mit der Aufgabenassembly "{0}" in Konflikt stehende Assembly wurde in "{1}" gefunden. + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs + Der benutzerdefinierte Ereignistyp '{0}' wird nicht unterstützt, weil alle benutzerdefinierten Ereignistypen veraltet waren. Verwenden Sie stattdessen Extended*EventArgs. Weitere Informationen: https://aka.ms/msbuild/eventargs + + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + Es wurde erwartet, dass der Ereignistyp "{0}" mithilfe des .NET-Serialisierers serialisierbar ist. Das Ereignis war nicht serialisierbar und wurde ignoriert. + + + + {0} ({1},{2}) + {0} ({1},{2}) + A file location to be embedded in a string. + + + MSB6005: Task attempted to log before it was initialized. Message was: {0} + MSB6005: Die Aufgabe hat versucht, eine Protokollierung durchzuführen, bevor sie initialisiert wurde. Meldung: {0} + {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}" ist ein ungültiger Wert für den "Importance"-Parameter. Gültige Werte sind: High, Normal und Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Es werden folgende vom übergeordneten Knoten empfangene Änderungen an der Umgebung vorgenommen, bevor sie auf den Aufgabenhost angewendet wird: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Festlegen von "{0}" auf "{1}" statt auf den Wert "{2}" der übergeordneten Umgebung. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + MSB4025: The project file could not be loaded. {0} + MSB4025: Die Projektdatei konnte nicht geladen werden. {0} + {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is + invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is + provided separately to loggers. + LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. + + + MSB4103: "{0}" is not a valid logger verbosity level. + MSB4103: "{0}" ist kein gültiger Ausführlichkeitsgrad für die Protokollierung. + {StrBegin="MSB4103: "} + + + MSBuild is expecting a valid "{0}" object. + MSBuild erwartet ein gültiges {0}-Objekt. + + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + Der Pfad "{0}" überschreitet das maximale Pfadlimit des Betriebssystems. Der vollqualifizierte Dateiname muss weniger als {1} Zeichen umfassen. + + + + MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". + MSB5028: Die Projektmappenfilter-Datei unter "{0}" enthält das Projekt "{1}", das in der Projektmappendatei unter "{2}" nicht enthalten ist. + {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. + + + MSB5025: Json in solution filter file "{0}" is incorrectly formatted. + MSB5025: JSON in der Projektmappenfilter-Datei "{0}" ist falsch formatiert. + {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. + + + MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. + MSB5026: Die Projektmappenfilter-Datei unter "{0}" gibt an, dass eine Projektmappendatei unter "{1}" vorhanden ist. Diese Datei ist jedoch nicht vorhanden. + {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. + MSB5009: Fehler beim Analysieren des Projektabschnitts "{0}". GUID: {1}. Er ist unter "{2}" geschachtelt, aber dieses Projekt wurde in der Projektmappe nicht gefunden. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. + MSB4132: Die Toolsversion "{0}" ist unbekannt. Verfügbare Toolversionen sind {1}. + {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. + + + MSB5016: The name "{0}" contains an invalid character "{1}". + MSB5016: Der Name "{0}" enthält das ungültige Zeichen "{1}". + {StrBegin="MSB5016: "} + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" sind reservierte Elementmetadaten, die nicht geändert oder gelöscht werden können. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The string "{0}" cannot be converted to a boolean (true/false) value. + Die Zeichenfolge "{0}" kann nicht in einen booleschen Wert (true/false) konvertiert werden. + + + + MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + MSB5003: Fehler beim Erstellen einer temporären Datei. Der Ordner für temporäre Dateien ist voll, oder der Pfad ist falsch. {0} + {StrBegin="MSB5003: "} + + + MSB5018: Failed to delete the temporary file "{0}". {1} {2} + MSB5018: Fehler beim Löschen der temporären Datei „{0}“. {1} {2} + {StrBegin="MSB5018: "} + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + Die %({0})-Elementmetadaten können nicht auf den Pfad "{1}" angewendet werden. {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: Die Aufgabe "{0}" wurde mit dem LoadInSeparateAppDomain-Attribut markiert, ist jedoch nicht von MarshalByRefObject abgeleitet. Stellen Sie sicher, dass die Aufgabe von MarshalByRefObject oder AppDomainIsolatedTask abgeleitet wird. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + Die .NET Framework-Version "{0}" wird nicht unterstützt. Geben Sie einen Wert aus der Enumeration "Microsoft.Build.Utilities.TargetDotNetFrameworkVersion" an. + + + + .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. + .NET Framework, Version "{0}" wird nicht unterstützt, wenn das Windows SDK das explizite Ziel ist, da dies nur unter .NET 4.5 und höher unterstützt wird. Geben Sie einen Wert aus der Enumeration "Microsoft.Build.Utilities.TargetDotNetFrameworkVersion", Version45 oder höher an. + + + + Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. + Visual Studio-Version "{0}" wird nicht unterstützt. Geben Sie einen Wert aus der Enumeration "Microsoft.Build.Utilities.VisualStudioVersion" an. + + + + When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} + Fehler beim Versuch, einen Referenzassemblypfad aus dem Pfad "{0}" und dem Frameworkmoniker "{1}" zu generieren. {2} + No Error code because this resource will be used in an exception. The error code is discarded if it is included + + + Could not find directory path: {0} + Der Verzeichnispfad wurde nicht gefunden: {0} + Directory must exist + + + You do not have access to: {0} + Sie haben keinen Zugriff auf: {0} + Directory must have access + + + Schema validation + Schemavalidierung + + UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: + "MSBUILD : Schema validation error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + + MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. + MSB5002: Die ausführbare Datei der Aufgabe "{0}" wird beendet, weil sie nicht in der vorgegebenen Zeit von {1} Millisekunden abgeschlossen wurde. + {StrBegin="MSB5002: "} + + + Parameter "{0}" cannot be null. + Der Parameter "{0}" darf nicht NULL sein. + + + + Parameter "{0}" cannot have zero length. + Parameter "{0}" darf nicht die Länge NULL haben. + + + + Parameters "{0}" and "{1}" must have the same number of elements. + Die Parameter "{0}" und "{1}" müssen die gleiche Anzahl von Elementen enthalten. + + + + The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. + Die Ressourcenzeichenfolge "{0}" für die Aufgabe "{1}" konnte nicht gefunden werden. Vergewissern Sie sich, dass der Ressourcenname "{0}" richtig geschrieben wurde und die Ressource in der Assembly der Aufgabe enthalten ist. + + + + The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. + Die Aufgabe "{0}" hat ihre Ressourcen nicht registriert. Wenn die TaskLoggingHelper.FormatResourceString()-Methode verwendet werden soll, muss diese Aufgabe ihre Ressourcen während der Konstruktion oder über die Eigenschaft "TaskResources" registrieren. + LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. + + + MSB5004: The solution file has two projects named "{0}". + MSB5004: Die Projektmappendatei enthält zwei Projekte mit dem Namen "{0}". + {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. + + + MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. + MSB5005: Fehler beim Analysieren des Projektabschnitts für das Projekt "{0}". Der Projektdateiname "{1}" enthält ungültige Zeichen. + {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. + + + MSB5006: Error parsing project section for project "{0}". The project file name is empty. + MSB5006: Fehler beim Analysieren des Projektabschnitts für das Projekt "{0}". Der Projektdateiname ist leer. + {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. + + + MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. + MSB5007: Fehler beim Analysieren des Projektkonfigurationsabschnitts in der Projektmappendatei. Der Eintrag "{0}" ist ungültig. + {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. + + + MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. + MSB5008: Fehler beim Analysieren des Projektmappen-Konfigurationsabschnitts in der Projektmappendatei. Der Eintrag "{0}" ist ungültig. + {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the nested project section in solution file. + MSB5009: Fehler beim Analysieren des geschachtelten Projektabschnitts in der Projektmappendatei. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. + MSB5023: Fehler beim Analysieren des geschachtelten Projektabschnitts in der Projektmappendatei. Ein Projekt mit GUID "{0}" ist als unter Projekt "{1}" geschachtelt aufgeführt, in der Projektmappe jedoch nicht vorhanden. + {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. + + + MSB5010: No file format header found. + MSB5010: Es wurde kein Dateiformatheader gefunden. + {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. + + + MSB5011: Parent project GUID not found in "{0}" project dependency section. + MSB5011: Die GUID des übergeordneten Projekts wurde im Projektabhängigkeitsabschnitt "{0}" nicht gefunden. + {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. + + + MSB5012: Unexpected end-of-file reached inside "{0}" project section. + MSB5012: Unerwartetes Dateiende innerhalb des Projektabschnitts "{0}" erreicht. + {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. + + + MSB5013: Error parsing a project section. + MSB5013: Fehler beim Analysieren eines Projektabschnitts. + {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. + + + MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. + MSB5014: Die Dateiformatversion wird nicht erkannt. MSBuild kann nur Projektmappendateien der Versionen {0}.0 bis einschließlich {1}.0 lesen. + {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. + + + MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. + MSB5015: Die Eigenschaften konnten nicht aus dem Abschnitt "WebsiteProperties" des Projekts "{0}" gelesen werden. + {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. + + + Unrecognized solution version "{0}", attempting to continue. + Unbekannte Projektmappenversion {0}. Es wird versucht, den Vorgang fortzusetzen. + + + + Solution file + Projektmappendatei + UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: + "MSBUILD : Solution file error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + MSB5019: The project file is malformed: "{0}". {1} + MSB5019: Die Projektdatei ist falsch formatiert: "{0}". {1} + {StrBegin="MSB5019: "} + + + MSB5020: Could not load the project file: "{0}". {1} + MSB5020: Die Projektdatei konnte nicht geladen werden: "{0}". {1} + {StrBegin="MSB5020: "} + + + MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. + MSB5021: Die ausführbare Datei der Aufgabe "{0}" und zugehörige Prozesse werden beendet, weil die Builderstellung abgebrochen wurde. + {StrBegin="MSB5021: "} + + + This collection is read-only. + Diese Sammlung ist schreibgeschützt. + + + + MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. + MSB5024: Es wurde kein gültiger Speicherort für MSBuild ermittelt. Versuchen Sie, den Prozess über die Entwicklereingabeaufforderung für Visual Studio auszuführen. + {StrBegin="MSB5024: "} + + + MSB4233: There was an exception while reading the log file: {0} + MSB4233: Beim Lesen der Protokolldatei ist eine Ausnahme aufgetreten: {0}. + {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. + + + Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. + Der Parameter "{0}" mit dem zugewiesenen Wert "{1}" darf keinen ungültigen Pfad und keine ungültigen Dateizeichen haben. + + + + An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} + Eine Ausnahme ist beim Erweitern einer fileSpec mit Globs aufgetreten: fileSpec: „{0}“, angenommen, es handelt sich um einen Dateinamen. Ausnahme: {1} + + + + MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. + MSB5029: Der Wert „{0}“ des Attributs „{1}“ in Element <{2}> in der Datei „{3}“ ist ein Platzhalter, der dazu führt, dass alle Dateien auf dem Laufwerk aufgelistet werden, was wahrscheinlich nicht beabsichtigt war. Überprüfen Sie, ob die referenzierten Eigenschaften immer definiert sind und dass sich das Projekt und das aktuelle Arbeitsverzeichnis nicht im Laufwerkstamm befinden. + {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its + attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.es.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.es.xlf new file mode 100644 index 00000000000..b7936f94976 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.es.xlf @@ -0,0 +1,349 @@ + + + + + + MSB4188: Build was canceled. + MSB4188: Se canceló la compilación. + {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: El host de tareas de MSBuild no admite la ejecución de tareas que realizan devoluciones de llamadas de IBuildEngine. Si desea realizar estas operaciones, ejecute la tarea en el proceso de MSBuild principal en su lugar. Se ejecutará una tarea automáticamente en el host de tareas si a UsingTask se le ha agregado un atributo con un valor "Runtime" o "Arquitectura", o bien a la invocación de tareas se le ha agregado un atributo con un valor "MSBuildRuntime" o "MSBuildArchitecture", que no coincide con el runtime o la arquitectura actual de MSBuild. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + Build started. + Compilación iniciada. + + + + The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). + El número de elementos de la colección es mayor que el espacio disponible en la matriz de destino (a partir del índice especificado). + + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: Se detectó un ensamblado conflictivo para el ensamblado de tarea "{0}" en "{1}". + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs + No se admite el tipo de evento personalizado '{0}' porque todos los tipos de eventos personalizados estaban en desuso. Use Extended*EventArgs en su lugar. Más información: https://aka.ms/msbuild/eventargs + + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + Se esperaba que el tipo de evento "{0}" fuera serializable con el serializador .NET. El evento no era serializable y se ha omitido. + + + + {0} ({1},{2}) + {0} ({1},{2}) + A file location to be embedded in a string. + + + MSB6005: Task attempted to log before it was initialized. Message was: {0} + MSB6005: La tarea intentó registrarse antes de inicializarse. El mensaje era: {0} + {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}" no es un valor válido para el parámetro "Importance". Los valores válidos son: High, Normal y Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Se están realizando las siguientes modificaciones en el entorno recibido del nodo primario antes de aplicarlo al host de tareas: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Estableciendo '{0}' en '{1}' en lugar del valor del entorno primario, '{2}'. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + MSB4025: The project file could not be loaded. {0} + MSB4025: No se pudo cargar el archivo del proyecto. {0} + {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is + invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is + provided separately to loggers. + LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. + + + MSB4103: "{0}" is not a valid logger verbosity level. + MSB4103: "{0}" no es un nivel de detalle del registrador. + {StrBegin="MSB4103: "} + + + MSBuild is expecting a valid "{0}" object. + MSBuild espera un objeto "{0}" válido. + + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + La ruta de acceso {0} supera el límite máximo para la ruta de acceso del sistema operativo. El nombre de archivo completo debe ser inferior a {1} caracteres. + + + + MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". + MSB5028: El archivo de filtro de soluciones en "{0}" incluye el proyecto "{1}", que no está en el archivo de solución en "{2}". + {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. + + + MSB5025: Json in solution filter file "{0}" is incorrectly formatted. + MSB5025: El formato JSON del archivo de filtro de soluciones "{0}" no es correcto. + {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. + + + MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. + MSB5026: El archivo de filtro de soluciones en "{0}" especifica que habrá un archivo de solución en "{1}", pero ese archivo no existe. + {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. + MSB5009: Error al analizar la sección "{0}" del proyecto con el GUID "{1}". Está anidado en "{2}", pero ese proyecto no se encuentra en la solución. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. + MSB4132: No se reconoce la versión de herramientas "{0}". Las versiones de herramientas disponibles son {1}. + {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. + + + MSB5016: The name "{0}" contains an invalid character "{1}". + MSB5016: El nombre "{0}" contiene un carácter no válido "{1}". + {StrBegin="MSB5016: "} + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" es un metadato de elemento reservado y no se puede modificar ni eliminar. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The string "{0}" cannot be converted to a boolean (true/false) value. + La cadena "{0}" no puede convertirse en un valor booleano (true/false). + + + + MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + MSB5003: Error al crear un archivo temporal. La carpeta de archivos temporales está llena o la ruta de acceso no es correcta. {0} + {StrBegin="MSB5003: "} + + + MSB5018: Failed to delete the temporary file "{0}". {1} {2} + MSB5018: No se pudo eliminar el archivo temporal "{0}". {1} {2} + {StrBegin="MSB5018: "} + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + Los metadatos "%({0})" del elemento no pueden aplicarse a la ruta de acceso "{1}". {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: La tarea "{0}" se marcó con el atributo LoadInSeparateAppDomain, pero esta no deriva de MarshalByRefObject. Asegúrese de que la tarea deriva de MarshalByRefObject o de AppDomainIsolatedTask. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + La versión "{0}" no es compatible. Especifique un valor de la enumeración Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + + + + .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. + La versión "{0}" de .NET Framework no es compatible cuando se establece de forma explícita Windows SDK como destino, que solo se admite en .NET 4.5 y posterior. Especifique un valor de la enumeración Microsoft.Build.Utilities.TargetDotNetFrameworkVersion que sea Version45 o posterior. + + + + Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. + La versión "{0}" de Visual Studio no es compatible. Especifique un valor de la enumeración Microsoft.Build.Utilities.VisualStudioVersion. + + + + When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} + Error al intentar generar una ruta de acceso de ensamblado de referencia a partir de la ruta de acceso "{0}" y del moniker "{1}" de la versión de .NET Framework. {2} + No Error code because this resource will be used in an exception. The error code is discarded if it is included + + + Could not find directory path: {0} + No se encuentra la ruta de acceso del directorio: {0} + Directory must exist + + + You do not have access to: {0} + No tiene acceso a: {0} + Directory must have access + + + Schema validation + Validación de esquemas + + UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: + "MSBUILD : Schema validation error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + + MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. + MSB5002: Finalizando el ejecutable de la tarea "{0}" porque no se completó dentro del límite especificado de {1} milisegundos. + {StrBegin="MSB5002: "} + + + Parameter "{0}" cannot be null. + El parámetro "{0}" no puede ser NULL. + + + + Parameter "{0}" cannot have zero length. + La longitud del parámetro "{0}" no puede ser cero. + + + + Parameters "{0}" and "{1}" must have the same number of elements. + Los parámetros "{0}" y "{1}" deben tener el mismo número de elementos. + + + + The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. + No se encuentra la cadena de recursos "{0}" para la tarea "{1}". Asegúrese de que el nombre del recurso "{0}" está escrito correctamente y de que el recurso existe en el ensamblado de la tarea. + + + + The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. + La tarea "{0}" no registró sus recursos. Para usar el método "TaskLoggingHelper.FormatResourceString()", esta tarea tiene que registrar sus recursos durante la construcción o mediante la propiedad "TaskResources". + LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. + + + MSB5004: The solution file has two projects named "{0}". + MSB5004: El archivo de solución tiene dos proyectos denominados "{0}". + {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. + + + MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. + MSB5005: Error al analizar la sección del proyecto "{0}". El nombre del archivo del proyecto "{1}" contiene caracteres no válidos. + {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. + + + MSB5006: Error parsing project section for project "{0}". The project file name is empty. + MSB5006: Error al analizar la sección del proyecto "{0}". El nombre del archivo del proyecto está vacío. + {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. + + + MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. + MSB5007: Error al analizar la sección de configuración del proyecto en el archivo de la solución. La entrada "{0}" no es válida. + {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. + + + MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. + MSB5008: Error al analizar la sección de configuración de la solución en el archivo de la solución. La entrada "{0}" no es válida. + {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the nested project section in solution file. + MSB5009: Error al analizar la sección del proyecto anidado en el archivo de solución. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. + MSB5023: Error al analizar la sección del proyecto anidado en el archivo de solución. El proyecto con el GUID "{0}" figura como anidado en el proyecto "{1}", pero no existe en la solución. + {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. + + + MSB5010: No file format header found. + MSB5010: No se encuentra ningún encabezado de formato de archivo. + {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. + + + MSB5011: Parent project GUID not found in "{0}" project dependency section. + MSB5011: No se encuentra el GUID del proyecto primario en la sección de dependencia del proyecto "{0}". + {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. + + + MSB5012: Unexpected end-of-file reached inside "{0}" project section. + MSB5012: Fin del archivo inesperado dentro de la sección del proyecto "{0}". + {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. + + + MSB5013: Error parsing a project section. + MSB5013: Error al analizar una sección del proyecto. + {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. + + + MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. + MSB5014: No se reconoce la versión del formato de archivo. MSBuild solo puede leer archivos de solución en las versiones de la {0}.0 y {1}.0, ambas incluidas. + {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. + + + MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. + MSB5015: No se pudieron leer las propiedades de la sección WebsiteProperties del proyecto "{0}". + {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. + + + Unrecognized solution version "{0}", attempting to continue. + Versión de solución "{0}" no reconocida. Se intentará continuar. + + + + Solution file + Archivo de la solución + UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: + "MSBUILD : Solution file error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + MSB5019: The project file is malformed: "{0}". {1} + MSB5019: El archivo del proyecto tiene un formato incorrecto: "{0}". {1} + {StrBegin="MSB5019: "} + + + MSB5020: Could not load the project file: "{0}". {1} + MSB5020: No se pudo cargar el archivo del proyecto: "{0}". {1} + {StrBegin="MSB5020: "} + + + MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. + MSB5021: Finalizando el ejecutable de la tarea "{0}" y sus procesos secundarios porque se canceló la compilación. + {StrBegin="MSB5021: "} + + + This collection is read-only. + Esta colección es de solo lectura. + + + + MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. + MSB5024: No se pudo determinar una ubicación válida para MSBuild. Intente ejecutar este proceso desde el Símbolo del sistema para desarrolladores de Visual Studio. + {StrBegin="MSB5024: "} + + + MSB4233: There was an exception while reading the log file: {0} + MSB4233: Excepción al leer el archivo de registro: {0} + {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. + + + Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. + El parámetro "{0}" con el valor asignado "{1}" no puede tener una ruta de acceso no válida o caracteres de archivo no válidos. + + + + An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} + Se produjo una excepción al expandir un fileSpec con globs: fileSpec: "{0}"; suponiendo que es un nombre de archivo. Excepción: {1} + + + + MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. + MSB5029: el valor "{0}" del atributo "{1}" en el elemento <{2}> en el archivo "{3}" es un carácter comodín que da como resultado enumerar todos los archivos de la unidad, lo que probablemente no estaba previsto. Compruebe que las propiedades a las que se hace referencia siempre están definidas y que el proyecto y el directorio de trabajo actual no están en la raíz de la unidad. + {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its + attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.fr.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.fr.xlf new file mode 100644 index 00000000000..abbc14e5158 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.fr.xlf @@ -0,0 +1,349 @@ + + + + + + MSB4188: Build was canceled. + MSB4188: La génération a été annulée. + {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: L'hôte de tâche MSBuild ne prend pas en charge l'exécution de tâches qui effectuent des rappels IBuildEngine. Pour effectuer ces opérations, exécutez plutôt votre tâche dans le processus MSBuild de base. Une tâche s'exécute automatiquement dans l'hôte de tâche si UsingTask a été défini sur la valeur "Runtime" ou "Architecture", ou si l'appel de la tâche a été défini sur la valeur "MSBuildRuntime" ou "MSBuildArchitecture", qui ne correspond pas à l'architecture ou au runtime actuel de MSBuild. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + Build started. + La génération a démarré. + + + + The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). + Le nombre d’éléments de la collection est supérieur à l’espace disponible dans le tableau de destination (lors du démarrage à l’index spécifié). + + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: un assembly en conflit avec l'assembly de tâche "{0}" a été trouvé sur "{1}". + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs + Le type d’événement personnalisé '{0}' n’est pas pris en charge, car tous les types d’événement personnalisés ont été dépréciés. Utilisez Extended*EventArgs à la place. Plus d’informations : https://aka.ms/msbuild/eventargs + + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + Le type d'événement "{0}" devait être sérialisable à l'aide du sérialiseur .NET. L'événement n'était pas sérialisable et a été ignoré. + + + + {0} ({1},{2}) + {0} ({1},{2}) + A file location to be embedded in a string. + + + MSB6005: Task attempted to log before it was initialized. Message was: {0} + MSB6005: La tâche a tenté d'ouvrir une session avant d'être initialisée. Le message était le suivant : {0} + {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}" n'est pas une valeur valide pour le paramètre "Importance". Les valeurs valides sont : High, Normal et Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Modifications suivantes en cours sur l'environnement reçu du nœud parent avant son application à l'hôte de tâche : + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Définition de '{0}' sur '{1}' plutôt que sur la valeur de l'environnement parent, '{2}'. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + MSB4025: The project file could not be loaded. {0} + MSB4025: Impossible de charger le fichier projet. {0} + {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is + invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is + provided separately to loggers. + LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. + + + MSB4103: "{0}" is not a valid logger verbosity level. + MSB4103: "{0}" n'est pas un niveau de verbosité de journaliseur valide. + {StrBegin="MSB4103: "} + + + MSBuild is expecting a valid "{0}" object. + MSBuild attend un objet "{0}" valide. + + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + Le chemin {0} dépasse la limite maximale de chemin du système d'exploitation. Le nom du fichier qualifié complet doit contenir moins de {1} caractères. + + + + MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". + MSB5028: le fichier de filtre de solution sur "{0}" inclut le projet "{1}", qui ne figure pas dans le fichier solution sur "{2}". + {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. + + + MSB5025: Json in solution filter file "{0}" is incorrectly formatted. + MSB5025: les données JSON du fichier de filtre de solution "{0}" sont dans un format incorrect. + {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. + + + MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. + MSB5026: le fichier de filtre de solution sur "{0}" spécifie l'existence d'un fichier solution sur "{1}", mais ce fichier n'existe pas. + {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. + MSB5009: erreur durant l'analyse de la section de projet "{0}" ayant le GUID "{1}". Elle est située sous "{2}" mais ce projet est introuvable dans la solution. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. + MSB4132: La version des outils "{0}" n'est pas reconnue. Les versions disponibles sont {1}. + {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. + + + MSB5016: The name "{0}" contains an invalid character "{1}". + MSB5016: Le nom "{0}" contient un caractère non valide "{1}". + {StrBegin="MSB5016: "} + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" est une métadonnée d'élément réservée qui ne peut être ni modifiée ni supprimée. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The string "{0}" cannot be converted to a boolean (true/false) value. + Impossible de convertir la chaîne "{0}" en valeur booléenne (true/false). + + + + MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + MSB5003: Échec de la création d'un fichier temporaire. Le dossier de fichiers temporaires est plein ou son chemin est incorrect. {0} + {StrBegin="MSB5003: "} + + + MSB5018: Failed to delete the temporary file "{0}". {1} {2} + MSB5018: Impossible de supprimer le fichier temporaire « {0} ». {1} {2} + {StrBegin="MSB5018: "} + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + Impossible d'appliquer la métadonnée d'élément "%({0})" au chemin d'accès "{1}". {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: la tâche "{0}" a été marquée avec l'attribut LoadInSeparateAppDomain, mais ne dérive pas de MarshalByRefObject. Vérifiez que la tâche dérive de MarshalByRefObject ou de AppDomainIsolatedTask. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + La version "{0}" de .NET Framework n'est pas prise en charge. Spécifiez une valeur de l'énumération Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + + + + .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. + La version "{0}" de .NET Framework n'est pas prise en charge lors du ciblage explicite du SDK Windows, qui est pris en charge uniquement par .NET 4.5 et ultérieur. Spécifiez une valeur de l'énumération Microsoft.Build.Utilities.TargetDotNetFrameworkVersion correspondant à la version 4.5 (Version45) ou ultérieure. + + + + Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. + La version "{0}" de Visual Studio n'est pas prise en charge. Spécifiez une valeur de l'énumération Microsoft.Build.Utilities.VisualStudioVersion. + + + + When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} + Une erreur s'est produite lors de la tentative de génération d'un chemin d'assembly de référence à partir du chemin d'accès "{0}" et du moniker du Framework "{1}". {2} + No Error code because this resource will be used in an exception. The error code is discarded if it is included + + + Could not find directory path: {0} + Chemin d'accès au répertoire introuvable : {0} + Directory must exist + + + You do not have access to: {0} + Vous n'avez pas accès à : {0} + Directory must have access + + + Schema validation + Validation de schéma + + UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: + "MSBUILD : Schema validation error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + + MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. + MSB5002: Arrêt de la tâche exécutable "{0}", car elle ne s'est pas terminée dans le délai spécifié de {1} millisecondes. + {StrBegin="MSB5002: "} + + + Parameter "{0}" cannot be null. + Le paramètre "{0}" ne peut pas être null. + + + + Parameter "{0}" cannot have zero length. + La longueur du paramètre "{0}" ne peut pas être égale à zéro. + + + + Parameters "{0}" and "{1}" must have the same number of elements. + Les paramètres "{0}" et "{1}" doivent avoir le même nombre d'éléments. + + + + The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. + La chaîne de ressource "{0}" pour la tâche "{1}" est introuvable. Vérifiez que le nom de ressource "{0}" est bien orthographié et que la ressource existe dans l'assembly de la tâche. + + + + The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. + La tâche "{0}" n'a pas inscrit ses ressources. Pour pouvoir utiliser la méthode "TaskLoggingHelper.FormatResourceString()", cette tâche doit inscrire ses ressources durant la construction ou par le biais de la propriété "TaskResources". + LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. + + + MSB5004: The solution file has two projects named "{0}". + MSB5004: Le fichier solution contient deux projets nommés "{0}". + {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. + + + MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. + MSB5005: Erreur pendant l'analyse de la section du projet "{0}". Le nom du fichier projet "{1}" contient des caractères non valides. + {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. + + + MSB5006: Error parsing project section for project "{0}". The project file name is empty. + MSB5006: Erreur lors de l'analyse de la section du projet "{0}". Le nom du fichier projet est vide. + {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. + + + MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. + MSB5007: Erreur pendant l'analyse de la section de configuration de projet dans le fichier solution. L'entrée "{0}" n'est pas valide. + {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. + + + MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. + MSB5008: Erreur pendant l'analyse de la section de configuration de solution dans le fichier solution. L'entrée "{0}" n'est pas valide. + {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the nested project section in solution file. + MSB5009: Erreur pendant l'analyse de la section du projet imbriqué dans le fichier solution. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. + MSB5023: Erreur pendant l'analyse de la section du projet imbriqué dans le fichier solution. Un projet avec le GUID "{0}" est répertorié comme étant imbriqué sous le projet "{1}", mais il n'existe pas dans la solution. + {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. + + + MSB5010: No file format header found. + MSB5010: Aucun en-tête de format de fichier trouvé. + {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. + + + MSB5011: Parent project GUID not found in "{0}" project dependency section. + MSB5011: GUID du projet parent introuvable dans la section de dépendance du projet "{0}". + {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. + + + MSB5012: Unexpected end-of-file reached inside "{0}" project section. + MSB5012: Fin du fichier inattendue atteinte à l'intérieur de la section de projet "{0}". + {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. + + + MSB5013: Error parsing a project section. + MSB5013: Erreur lors de l'analyse d'une section de projet. + {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. + + + MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. + MSB5014: La version du format du fichier n'est pas reconnue. MSBuild peut lire uniquement les fichiers solution entre les versions {0}.0 et {1}.0 (inclus). + {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. + + + MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. + MSB5015: Impossible de lire les propriétés dans la section WebsiteProperties du projet "{0}". + {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. + + + Unrecognized solution version "{0}", attempting to continue. + Version de solution non reconnue "{0}", tentative de poursuite. + + + + Solution file + Fichier solution + UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: + "MSBUILD : Solution file error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + MSB5019: The project file is malformed: "{0}". {1} + MSB5019: Le fichier projet est incorrect : "{0}". {1} + {StrBegin="MSB5019: "} + + + MSB5020: Could not load the project file: "{0}". {1} + MSB5020: Impossible de charger le fichier projet : "{0}". {1} + {StrBegin="MSB5020: "} + + + MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. + MSB5021: Arrêt de la tâche exécutable "{0}" et de ses processus enfants, car la génération a été annulée. + {StrBegin="MSB5021: "} + + + This collection is read-only. + Cette collection est en lecture seule. + + + + MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. + MSB5024: Impossible de déterminer un emplacement valide vers MSBuild. Essayez d’exécuter ce processus à partir de l’invite de commandes Developer pour Visual Studio. + {StrBegin="MSB5024: "} + + + MSB4233: There was an exception while reading the log file: {0} + MSB4233: Une exception s'est produite durant la lecture du fichier journal : {0} + {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. + + + Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. + Le paramètre "{0}" avec la valeur assignée "{1}" ne peut pas avoir un chemin non valide ou des caractères de fichier non valides. + + + + An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} + Une exception s’est produite lors du développement d’un fileSpec avec globs: fileSpec: '{0}', en supposant qu’il s’agit d’un nom de fichier. Exception : {1} + + + + MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. + MSB5029: La valeur «{0}» de l’attribut «{1}» dans l’élément <{2}> dans le fichier «{3}» est un caractère générique qui entraîne l’énumération de tous les fichiers sur le lecteur, ce qui n’était probablement pas prévu. Vérifiez que les propriétés référencées sont toujours définies et que le projet et le répertoire de travail actuel ne se trouvent pas à la racine du lecteur. + {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its + attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.it.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.it.xlf new file mode 100644 index 00000000000..082c3faab51 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.it.xlf @@ -0,0 +1,349 @@ + + + + + + MSB4188: Build was canceled. + MSB4188: compilazione annullata. + {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: l'host attività MSBuild non supporta l'esecuzione di attività che eseguono callback IBuildEngine. Per eseguire tali operazioni, eseguire l'attività nel processo MSBuild di base. Un'attività verrà automaticamente eseguita nell'host attività se per UsingTask è stato definito un attributo con valore "Runtime" o "Architecture" o se per la chiamata all'attività è stato definito un attributo con valore "MSBuildRuntime" o "MSBuildArchitecture" che non corrisponde al runtime corrente o all'architettura di MSBuild. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + Build started. + Compilazione avviata. + + + + The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). + Il numero di elementi nella raccolta è maggiore dello spazio disponibile nella matrice di destinazione (a partire dall'indice specificato). + + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: rilevato un assembly in conflitto per l'assembly dell'attività "{0}" in "{1}". + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs + Il tipo di evento personalizzato '{0}' non è supportato perché tutti i tipi di evento personalizzati sono deprecati. Usare Invece Extended*EventArgs. Altre informazioni: https://aka.ms/msbuild/eventargs + + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + È previsto un tipo di evento "{0}" serializzabile con il serializzatore .NET. L'evento non era serializzabile ed è stato ignorato. + + + + {0} ({1},{2}) + {0} ({1},{2}) + A file location to be embedded in a string. + + + MSB6005: Task attempted to log before it was initialized. Message was: {0} + MSB6005: tentativo di registrazione prima dell'inizializzazione dell'attività. Messaggio: {0} + {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}" non è un valore valido per il parametro "Importance". I valori validi sono: High, Normal e Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Le modifiche seguenti verranno apportate all'ambiente ricevuto dal nodo padre prima dell'applicazione all'host attività: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Impostazione di '{0}' su '{1}' anziché sul valore dell'ambiente padre, '{2}'. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + MSB4025: The project file could not be loaded. {0} + MSB4025: non è stato possibile caricare il file di progetto. {0} + {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is + invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is + provided separately to loggers. + LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. + + + MSB4103: "{0}" is not a valid logger verbosity level. + MSB4103: "{0}" non è un livello di dettaglio valido del logger. + {StrBegin="MSB4103: "} + + + MSBuild is expecting a valid "{0}" object. + MSBuild prevede un oggetto "{0}" valido. + + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + Il percorso {0} supera il limite massimo dei percorsi del sistema operativo. Il nome completo del file deve essere composto da meno di {1}. + + + + MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". + MSB5028: il file di filtro della soluzione in "{0}" include il progetto "{1}" che non è presente nel file di soluzione in "{2}". + {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. + + + MSB5025: Json in solution filter file "{0}" is incorrectly formatted. + MSB5025: il codice JSON nel file di filtro della soluzione "{0}" non è formattato correttamente. + {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. + + + MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. + MSB5026: nel file di filtro della soluzione in "{0}" è indicata la presenza di un file di soluzione in "{1}", ma tale file non esiste. + {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. + MSB5009: si è verificato un errore durante l'analisi della sezione "{0}" del progetto con GUID: "{1}". È annidata in "{2}", ma tale progetto non è stato trovato nella soluzione. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. + MSB4132: versione degli strumenti "{0}" non riconosciuta. Le versioni disponibili sono {1}. + {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. + + + MSB5016: The name "{0}" contains an invalid character "{1}". + MSB5016: il nome "{0}" contiene il carattere "{1}" non valido. + {StrBegin="MSB5016: "} + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" è un metadato di un elemento riservato e pertanto non può essere modificato o eliminato. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The string "{0}" cannot be converted to a boolean (true/false) value. + Non è possibile convertire la stringa "{0}" in un valore booleano (true/false). + + + + MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + MSB5003: non è stato possibile creare un file temporaneo. La cartella dei file temporanei è piena oppure il percorso è errato. {0} + {StrBegin="MSB5003: "} + + + MSB5018: Failed to delete the temporary file "{0}". {1} {2} + MSB5018: non è stato possibile eliminare il file temporaneo "{0}". {1} {2} + {StrBegin="MSB5018: "} + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + Non è possibile applicare i metadati dell'elemento "%({0})" al percorso "{1}". {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: l'attività "{0}" è stata contrassegnata con l'attributo LoadInSeparateAppDomain, ma non deriva da MarshalByRefObject. Verificare che l'attività derivi da MarshalByRefObject o AppDomainIsolatedTask. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + La versione "{0}" di .NET Framework non è supportata. Specificare un valore dall'enumerazione Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + + + + .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. + La versione "{0}" di .NET Framework non è supportata se è destinata in modo esplicito a Windows SDK, che è supportato solo da .NET 4.5 e versioni successive. Specificare un valore dell'enumerazione Microsoft.Build.Utilities.TargetDotNetFrameworkVersion che sia Version45 o successivo. + + + + Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. + La versione "{0}" di Visual Studio non è supportata. Specificare un valore dall'enumerazione Microsoft.Build.Utilities.VisualStudioVersion. + + + + When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} + Si è verificato un errore durante il tentativo di generare un percorso dell'assembly di riferimento dal "{0}" e dal moniker del framework "{1}". {2} + No Error code because this resource will be used in an exception. The error code is discarded if it is included + + + Could not find directory path: {0} + Il percorso di directory {0} non è stato trovato + Directory must exist + + + You do not have access to: {0} + Non si dispone dell'accesso a {0} + Directory must have access + + + Schema validation + Convalida schema + + UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: + "MSBUILD : Schema validation error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + + MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. + MSB5002: l'eseguibile "{0}" dell'attività verrà terminato perché non è finito entro il limite specificato di {1} millisecondi. + {StrBegin="MSB5002: "} + + + Parameter "{0}" cannot be null. + Il parametro "{0}" non può essere Null. + + + + Parameter "{0}" cannot have zero length. + Il parametro "{0}" non può avere lunghezza zero. + + + + Parameters "{0}" and "{1}" must have the same number of elements. + I parametri "{0}" e "{1}" devono avere lo stesso numero di elementi. + + + + The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. + La stringa di risorsa "{0}" per l'attività "{1}" non è stata trovata. Verificare che il nome della risorsa "{0}" sia stato digitato correttamente e che la risorsa sia presente nell'assembly dell'attività. + + + + The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. + L'attività "{0}" non ha registrato le proprie risorse. Per usare il metodo "TaskLoggingHelper.FormatResourceString()", è necessario che l'attività registri le risorse durante la costruzione o con la proprietà "TaskResources". + LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. + + + MSB5004: The solution file has two projects named "{0}". + MSB5004: al file di soluzione sono associati due progetti denominati "{0}". + {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. + + + MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. + MSB5005: errore durante l'analisi di una sezione di progetto per il progetto "{0}". Il nome del file di progetto "{1}" contiene caratteri non validi. + {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. + + + MSB5006: Error parsing project section for project "{0}". The project file name is empty. + MSB5006: errore durante l'analisi di una sezione di progetto per il progetto "{0}". Il nome del file di progetto è vuoto. + {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. + + + MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. + MSB5007: errore durante l'analisi della sezione di configurazione del progetto nel file di soluzione. La voce "{0}" non è valida. + {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. + + + MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. + MSB5008: errore durante l'analisi della sezione di configurazione della soluzione nel file di soluzione. La voce "{0}" non è valida. + {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the nested project section in solution file. + MSB5009: errore durante l'analisi della sezione del progetto annidato nel file di soluzione. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. + MSB5023: errore durante l'analisi della sezione del progetto annidato nel file di soluzione. Un progetto con GUID "{0}" è elencato come annidato nel progetto "{1}", ma non esiste nella soluzione. + {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. + + + MSB5010: No file format header found. + MSB5010: nessuna intestazione di formato di file trovata. + {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. + + + MSB5011: Parent project GUID not found in "{0}" project dependency section. + MSB5011: il GUID del progetto padre non è stato trovato nella sezione delle dipendenze del progetto "{0}". + {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. + + + MSB5012: Unexpected end-of-file reached inside "{0}" project section. + MSB5012: fine del file imprevista all'interno della sezione del progetto "{0}". + {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. + + + MSB5013: Error parsing a project section. + MSB5013: errore durante l'analisi di una sezione di progetto. + {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. + + + MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. + MSB5014: la versione del formato di file non è riconosciuta. Con MSBuild è possibile leggere solo file di soluzione dalla versione {0}.0 alla versione {1}.0. + {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. + + + MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. + MSB5015: non è stato possibile leggere le proprietà dalla sezione WebsiteProperties del progetto "{0}". + {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. + + + Unrecognized solution version "{0}", attempting to continue. + Versione di soluzione "{0}" non riconosciuta. Tentativo di continuare in corso. + + + + Solution file + File di soluzione + UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: + "MSBUILD : Solution file error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + MSB5019: The project file is malformed: "{0}". {1} + MSB5019: il formato del file di progetto non valido: "{0}". {1} + {StrBegin="MSB5019: "} + + + MSB5020: Could not load the project file: "{0}". {1} + MSB5020: non è stato possibile caricare il file di progetto: "{0}". {1} + {StrBegin="MSB5020: "} + + + MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. + MSB5021: l'eseguibile "{0}" dell'attività e i relativi processi figlio verranno terminati perché la compilazione è stata annullata. + {StrBegin="MSB5021: "} + + + This collection is read-only. + Raccolta di sola lettura. + + + + MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. + MSB5024: Non è stato possibile determinare un percorso valido di MSBuild. Provare a eseguire il processo dal prompt dei comandi per gli sviluppatori di Visual Studio. + {StrBegin="MSB5024: "} + + + MSB4233: There was an exception while reading the log file: {0} + MSB4233: si è verificata un'eccezione durante la lettura del file di log: {0} + {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. + + + Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. + Il parametro "{0}" con valore assegnato "{1}" non può contenere un percorso non valido o caratteri di file non validi. + + + + An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} + Si è verificata un'eccezione durante l'espansione di un fileSpec con GLOBS: fileSpec: "{0}", presupponendo che si tratti di un nome di file. Eccezione: {1} + + + + MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. + MSB5029: il valore "{0}" dell'attributo "{1}" nell'elemento <{2}> nel file "{3}" è un carattere jolly che determina l'enumerazione di tutti i file nell'unità, che probabilmente non era previsto. Controllare che le proprietà a cui si fa riferimento siano sempre definite e che il progetto e la directory di lavoro corrente non siano nella radice dell'unità. + {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its + attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ja.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ja.xlf new file mode 100644 index 00000000000..8e33727f2fb --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ja.xlf @@ -0,0 +1,349 @@ + + + + + + MSB4188: Build was canceled. + MSB4188: ビルドが取り消されました。 + {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: MSBuild タスク ホストは、IBuildEngine コールバックを実行するタスクの実行をサポートしていません。これらの操作を実行する場合は、タスクをコア MSBuild プロセスで実行してください。UsingTask の属性として設定されている "Runtime" または "Architecture" の値、あるいはタスク呼び出しの属性として設定されている "MSBuildRuntime" または "MSBuildArchitecture" の値が MSBuild の現在のランタイムまたはアーキテクチャと一致しない場合、タスクは自動的にタスク ホストで実行されます。 + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + Build started. + ビルドを開始しました。 + + + + The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). + コレクション内の要素の数が、同期先配列内の使用可能な領域を超えています (指定されたインデックスで開始する場合)。 + + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: タスク アセンブリ "{0}" に対して競合しているアセンブリが "{1}" で見つかりました。 + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs + カスタム イベントの種類 '{0}' は、すべてのカスタム イベントの種類が非推奨になったため、サポートされていません。代わりに Extended*EventArgs を使用してください。詳細情報: https://aka.ms/msbuild/eventargs + + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + イベントの種類 "{0}" は .NET シリアライザーを使用してシリアル化可能であることが想定されていましたが、シリアル化可能でなかったため無視されました。 + + + + {0} ({1},{2}) + {0} ({1},{2}) + A file location to be embedded in a string. + + + MSB6005: Task attempted to log before it was initialized. Message was: {0} + MSB6005: タスクは、初期化される前にログを記録しようとしました。メッセージ: {0} + {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}" は、"Importance" パラメーターに対して無効な値です。有効な値は High、Normal および Low です。 + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + 親ノードから受け取った環境をタスク ホストに適用する前に、次の変更を行っています: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + '{0}' を親環境の値 '{2}' ではなく '{1}' に設定しています。 + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + MSB4025: The project file could not be loaded. {0} + MSB4025: プロジェクト ファイルを読み込めませんでした。{0} + {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is + invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is + provided separately to loggers. + LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. + + + MSB4103: "{0}" is not a valid logger verbosity level. + MSB4103: "{0}" は有効なロガー詳細レベルではありません。 + {StrBegin="MSB4103: "} + + + MSBuild is expecting a valid "{0}" object. + MSBuild は有効な "{0}" オブジェクトを必要としています。 + + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + パス: {0} は OS のパスの上限を越えています。完全修飾のファイル名は {1} 文字以下にする必要があります。 + + + + MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". + MSB5028: "{0}" のソリューション フィルター ファイルには、"{2}" のソリューション ファイルにないプロジェクト "{1}" が含まれています。 + {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. + + + MSB5025: Json in solution filter file "{0}" is incorrectly formatted. + MSB5025: ソリューション フィルター ファイル "{0}" の JSON の形式が正しくありません。 + {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. + + + MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. + MSB5026: "{0}" のソリューション フィルター ファイルでは、"{1}" にソリューション ファイルを配置するように指定されていますが、そのファイルは存在しません。 + {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. + MSB5009: GUID "{1}" のプロジェクト "{0}" セクションの解析でエラーが発生しました。これは "{2}" の下に入れ子にされていますが、このプロジェクトはソリューション内で見つかりません。 + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. + MSB4132: ツール バージョン "{0}" が認識されません。使用可能なツール バージョンは {1} です。 + {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. + + + MSB5016: The name "{0}" contains an invalid character "{1}". + MSB5016: 名前 "{0}" は無効な文字 "{1}" を含んでいます。 + {StrBegin="MSB5016: "} + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" は予約された項目メタデータです。変更または削除することはできません。 + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The string "{0}" cannot be converted to a boolean (true/false) value. + 文字列 "{0}" をブール値 (true/false) に変換することはできません。 + + + + MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + MSB5003: 一時ファイルを作成できませんでした。一時ファイル フォルダーがいっぱいであるか、またはそのパスが正しくありません。{0} + {StrBegin="MSB5003: "} + + + MSB5018: Failed to delete the temporary file "{0}". {1} {2} + MSB5018: 一時ファイル "{0}" を削除できませんでした。{1} {2} + {StrBegin="MSB5018: "} + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + 項目メタデータ "%({0})" をパス "{1}" に適用できません。{2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: "{0}" タスクに属性 LoadInSeparateAppDomain が設定されていますが、MarshalByRefObject から派生していません。そのタスクが MarshalByRefObject または AppDomainIsolatedTask から派生していることを確認してください。 + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + .NET Framework のバージョン "{0}" はサポートされていません。列挙 Microsoft.Build.Utilities.TargetDotNetFrameworkVersion から値を指定してください。 + + + + .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. + Windows SDK を明示的にターゲットとする場合、.NET Framework のバージョン "{0}" はサポートされません。Windows SDK は、.NET 4.5 以降でのみサポートされています。列挙 Microsoft.Build.Utilities.TargetDotNetFrameworkVersion から Version45 以上の値を指定してください。 + + + + Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. + Visual Studio のバージョン "{0}" はサポートされていません。列挙 Microsoft.Build.Utilities.VisualStudioVersion から値を指定してください。 + + + + When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} + 参照アセンブリ パスをパス "{0}" とフレームワーク モニカー "{1}" から生成しようとしたときに、エラーが発生しました。{2} + No Error code because this resource will be used in an exception. The error code is discarded if it is included + + + Could not find directory path: {0} + ディレクトリ パスが見つかりませんでした: {0} + Directory must exist + + + You do not have access to: {0} + {0} へのアクセス権がありません + Directory must have access + + + Schema validation + スキーマの検証 + + UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: + "MSBUILD : Schema validation error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + + MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. + MSB5002: 実行可能なタスク "{0}" は、指定された制限 ({1} ミリ秒) 内で完了しなかったため、終了しています。 + {StrBegin="MSB5002: "} + + + Parameter "{0}" cannot be null. + パラメーター "{0}" を null にすることはできません。 + + + + Parameter "{0}" cannot have zero length. + パラメーター "{0}" の長さを 0 にすることはできません。 + + + + Parameters "{0}" and "{1}" must have the same number of elements. + パラメーター "{0}" と "{1}" の要素数は同じである必要があります。 + + + + The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. + "{1}" タスクのリソース文字列 "{0}" が見つかりません。リソース名 "{0}" のスペルが正しいこと、およびリソースがタスクのアセンブリ内に存在することを確認してください。 + + + + The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. + "{0}" タスクのリソースが登録されていません。"TaskLoggingHelper.FormatResourceString()" メソッドを使用するためには、構築時に、または "TaskResources" プロパティを通じて、このタスクのリソースを登録する必要があります。 + LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. + + + MSB5004: The solution file has two projects named "{0}". + MSB5004: ソリューション ファイルには "{0}" という名前のプロジェクトが 2 つあります。 + {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. + + + MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. + MSB5005: プロジェクト "{0}" のプロジェクト セクションを解析中にエラーが発生しました。プロジェクトのファイル名 "{1}" に無効な文字が使用されています。 + {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. + + + MSB5006: Error parsing project section for project "{0}". The project file name is empty. + MSB5006: プロジェクト "{0}" のプロジェクト セクションを解析中にエラーが発生しました。プロジェクトのファイル名が空です。 + {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. + + + MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. + MSB5007: ソリューション ファイル内のプロジェクト構成セクションを解析中にエラーが発生しました。エントリ "{0}" は無効です。 + {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. + + + MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. + MSB5008: ソリューション ファイル内のソリューション構成セクションを解析中にエラーが発生しました。エントリ "{0}" は無効です。 + {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the nested project section in solution file. + MSB5009: ソリューション ファイル内の入れ子にされたプロジェクト セクションを解析中にエラーが発生しました。 + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. + MSB5023: ソリューション ファイルの入れ子になったプロジェクト セクションを解析中にエラーが発生しました。GUID "{0}" のプロジェクトは、プロジェクト "{1}" 下に入れ子として表示されていますが、ソリューション内に存在しません。 + {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. + + + MSB5010: No file format header found. + MSB5010: ファイル形式のヘッダーが見つかりませんでした。 + {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. + + + MSB5011: Parent project GUID not found in "{0}" project dependency section. + MSB5011: 親プロジェクト GUID が "{0}" プロジェクト依存セクションで見つかりませんでした。 + {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. + + + MSB5012: Unexpected end-of-file reached inside "{0}" project section. + MSB5012: "{0}" プロジェクト セクション内で、予期しない EOF に到達しました。 + {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. + + + MSB5013: Error parsing a project section. + MSB5013: プロジェクト セクションを解析中にエラーが発生しました。 + {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. + + + MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. + MSB5014: ファイル形式のバージョンを認識できません。MSBuild で読み取ることができるのは、バージョン {0}.0 ~ {1}.0 のソリューション ファイルだけです。 + {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. + + + MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. + MSB5015: "{0}" プロジェクトの WebsiteProperties セクションからプロパティを読み取れませんでした。 + {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. + + + Unrecognized solution version "{0}", attempting to continue. + "{0}" は認識できないソリューション バージョンです。続行を試みます。 + + + + Solution file + ソリューション ファイル + UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: + "MSBUILD : Solution file error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + MSB5019: The project file is malformed: "{0}". {1} + MSB5019: プロジェクト ファイルの形式が正しくありません: "{0}"。{1} + {StrBegin="MSB5019: "} + + + MSB5020: Could not load the project file: "{0}". {1} + MSB5020: プロジェクト ファイル "{0}" を読み込めませんでした。{1} + {StrBegin="MSB5020: "} + + + MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. + MSB5021: ビルドが取り消されたため、実行可能なタスク "{0}" とその子プロセスを終了しています。 + {StrBegin="MSB5021: "} + + + This collection is read-only. + このコレクションは読み取り専用です。 + + + + MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. + MSB5024: MSBuild への有効な場所が決定できませんでした。Visual Studio の開発者コマンド プロンプトからこのプロセスを実行してください。 + {StrBegin="MSB5024: "} + + + MSB4233: There was an exception while reading the log file: {0} + MSB4233: ログ ファイルの読み取り中に例外が発生しました: {0} + {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. + + + Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. + 値 "{1}" が割り当てられたパラメーター "{0}" には、無効なパスまたは無効なファイル内の文字を指定することはできません。 + + + + An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} + glob を使用して fileSpec を展開中に例外が発生しました: fileSpec: "{0}"、これはファイル名であると仮定します。例外: {1} + + + + MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. + MSB5029: ファイル "{3}" の要素 <{2}> の "{1}" 属性の値 "{0}" は、ドライブ上のすべてのファイルを列挙するワイルドカードであり、意図されていない可能性があります。参照されるプロパティが常に定義されていること、およびプロジェクトと現在の作業ディレクトリがドライブ ルートにないことを確認します。 + {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its + attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ko.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ko.xlf new file mode 100644 index 00000000000..102c6ecfb97 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ko.xlf @@ -0,0 +1,349 @@ + + + + + + MSB4188: Build was canceled. + MSB4188: 빌드가 취소되었습니다. + {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: MSBuild 작업 호스트는 IBuildEngine 콜백을 수행하는 작업의 실행을 지원하지 않습니다. 이러한 작업을 수행하려면 대신 코어 MSBuild 프로세스에서 작업을 실행하세요. UsingTask에 "Runtime" 또는 "Architecture" 값이 사용되었거나 작업 호출에 MSBuild의 현재 런타임 또는 아키텍처와 일치하지 않는 "MSBuildRuntime" 또는 "MSBuildArchitecture" 값이 사용된 경우 작업 호스트에서 작업이 자동으로 실행됩니다. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + Build started. + 빌드를 시작했습니다. + + + + The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). + 컬렉션의 요소 수가 대상 배열에서 사용 가능한 공간보다 큽니다(지정된 인덱스에서 시작할 때). + + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: 작업 어셈블리 "{0}"과(와) 충돌하는 어셈블리가 "{1}"에 있습니다. + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs + 모든 사용자 지정 이벤트 유형이 사용되지 않으므로 사용자 지정 이벤트 유형 '{0}' 지원되지 않습니다. 대신 Extended*EventArgs를 사용하세요. 추가 정보: https://aka.ms/msbuild/eventargs + + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + 이벤트 유형 "{0}"은(는) .NET serializer를 사용하여 serialize할 수 있어야 합니다. 이 이벤트는 serialize할 수 없으므로 무시되었습니다. + + + + {0} ({1},{2}) + {0}({1},{2}) + A file location to be embedded in a string. + + + MSB6005: Task attempted to log before it was initialized. Message was: {0} + MSB6005: 작업을 초기화하기 전에 로깅하려고 했습니다. 메시지: {0} + {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}"은(는) "Importance" 매개 변수에 사용할 수 없는 값입니다. 유효한 값은 High, Normal 및 Low입니다. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + 작업 호스트에 적용하기 전에 부모 노드로부터 받은 환경을 다음과 같이 수정하고 있습니다. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + '{0}'을(를) 부모 환경 값인 '{2}' 대신 '{1}'(으)로 설정하고 있습니다. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + MSB4025: The project file could not be loaded. {0} + MSB4025: 프로젝트 파일을 로드할 수 없습니다. {0} + {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is + invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is + provided separately to loggers. + LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. + + + MSB4103: "{0}" is not a valid logger verbosity level. + MSB4103: "{0}"은(는) 로거의 세부 정보 표시로 알맞지 않습니다. + {StrBegin="MSB4103: "} + + + MSBuild is expecting a valid "{0}" object. + MSBuild에 올바른 "{0}" 개체가 필요합니다. + + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + 경로: {0}은(는) OS 최대 경로 제한을 초과합니다. 정규화된 파일 이름은 {1}자 이하여야 합니다. + + + + MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". + MSB5028: "{0}"의 솔루션 필터 파일에 "{2}"의 솔루션 파일에 없는 "{1}" 프로젝트가 포함되어 있습니다. + {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. + + + MSB5025: Json in solution filter file "{0}" is incorrectly formatted. + MSB5025: 솔루션 필터 파일 "{0}"의 Json 형식이 잘못되었습니다. + {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. + + + MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. + MSB5026: "{0}"의 솔루션 필터 파일이 "{1}"에 솔루션 파일이 있도록 지정하지만, 해당 파일이 없습니다. + {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. + MSB5009: GUID가 "{1}"인 프로젝트 "{0}" 섹션을 구문 분석하는 동안 오류가 발생했습니다. "{2}" 아래에 중첩되지만 해당 프로젝트를 솔루션에서 찾을 수 없습니다. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. + MSB4132: 도구 버전 "{0}"을(를) 인식할 수 없습니다. 사용할 수 있는 도구 버전은 {1}입니다. + {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. + + + MSB5016: The name "{0}" contains an invalid character "{1}". + MSB5016: 이름 "{0}"에 잘못된 문자 "{1}"이(가) 사용되었습니다. + {StrBegin="MSB5016: "} + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}"은(는) 예약된 항목 메타데이터이므로 수정 또는 삭제할 수 없습니다. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The string "{0}" cannot be converted to a boolean (true/false) value. + "{0}" 문자열을 부울 값(true/false)으로 변환할 수 없습니다. + + + + MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + MSB5003: 임시 파일을 만들지 못했습니다. 임시 파일 폴더가 꽉 찼거나 경로가 올바르지 않습니다. {0} + {StrBegin="MSB5003: "} + + + MSB5018: Failed to delete the temporary file "{0}". {1} {2} + MSB5018: 임시 파일 "{0}"을(를) 삭제하지 못했습니다. {1} {2} + {StrBegin="MSB5018: "} + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + 항목 메타데이터 "%({0})"을(를) "{1}" 경로에 적용할 수 없습니다. {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: "{0}" 작업이 LoadInSeparateAppDomain 특성으로 표시되었지만 MarshalByRefObject에서 파생되지 않습니다. 해당 작업이 MarshalByRefObject 또는 AppDomainIsolatedTask에서 파생되는지 확인하십시오. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + .NET Framework 버전 "{0}"이(가) 지원되지 않습니다. Microsoft.Build.Utilities.TargetDotNetFrameworkVersion 열거형에서 값을 지정하세요. + + + + .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. + .NET 4.5 이상에서만 지원되는 Windows SDK를 명시적으로 대상으로 지정하는 경우 .NET Framework 버전 "{0}"이(가) 지원되지 않습니다. Microsoft.Build.Utilities.TargetDotNetFrameworkVersion 열거형에서 Version45 이상인 값을 지정하세요. + + + + Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. + Visual Studio 버전 "{0}"이(가) 지원되지 않습니다. Microsoft.Build.Utilities.VisualStudioVersion 열거형에서 값을 지정하세요. + + + + When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} + "{0}" 경로 및 프레임워크 모니커 "{1}"에서 참조 어셈블리 경로를 생성하는 동안 오류가 발생했습니다. {2} + No Error code because this resource will be used in an exception. The error code is discarded if it is included + + + Could not find directory path: {0} + 디렉터리 경로를 찾을 수 없습니다. {0} + Directory must exist + + + You do not have access to: {0} + {0}에 액세스할 수 있는 권한이 없습니다. + Directory must have access + + + Schema validation + 스키마 유효성 검사 + + UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: + "MSBUILD : Schema validation error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + + MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. + MSB5002: 작업 실행 파일 "{0}"이(가) 지정한 제한 시간인 {1}밀리초 안에 완료되지 않았으므로 종료합니다. + {StrBegin="MSB5002: "} + + + Parameter "{0}" cannot be null. + "{0}" 매개 변수는 null일 수 없습니다. + + + + Parameter "{0}" cannot have zero length. + "{0}" 매개 변수의 길이는 0일 수 없습니다. + + + + Parameters "{0}" and "{1}" must have the same number of elements. + "{0}" 및 "{1}" 매개 변수에는 동일한 수의 요소를 사용해야 합니다. + + + + The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. + "{1}" 작업의 리소스 문자열 "{0}"을(를) 찾을 수 없습니다. 리소스 이름 "{0}"의 맞춤법이 올바른지 그리고 리소스가 작업 어셈블리에 있는지 확인하세요. + + + + The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. + {0} 작업의 리소스가 등록되지 않았습니다. "TaskLoggingHelper.FormatResourceString()" 메서드를 사용하려면 생성 시 또는 "TaskResources" 속성을 통해 이 작업의 리소스를 등록해야 합니다. + LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. + + + MSB5004: The solution file has two projects named "{0}". + MSB5004: 솔루션 파일에 이름이 "{0}"인 프로젝트가 두 개 있습니다. + {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. + + + MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. + MSB5005: "{0}" 프로젝트의 프로젝트 섹션을 구문 분석하는 동안 오류가 발생했습니다. 프로젝트 파일 이름 "{1}"에 잘못된 문자가 있습니다. + {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. + + + MSB5006: Error parsing project section for project "{0}". The project file name is empty. + MSB5006: "{0}" 프로젝트의 프로젝트 섹션을 구문 분석하는 동안 오류가 발생했습니다. 프로젝트 파일 이름이 비어 있습니다. + {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. + + + MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. + MSB5007: 솔루션 파일의 프로젝트 구성 섹션을 구문 분석하는 동안 오류가 발생했습니다. "{0}" 항목이 잘못되었습니다. + {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. + + + MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. + MSB5008: 솔루션 파일의 솔루션 구성 섹션을 구문 분석하는 동안 오류가 발생했습니다. "{0}" 항목이 잘못되었습니다. + {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the nested project section in solution file. + MSB5009: 솔루션 파일의 중첩된 프로젝트 섹션을 구문 분석하는 동안 오류가 발생했습니다. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. + MSB5023: 솔루션 파일의 중첩된 프로젝트 섹션을 구문 분석하는 동안 오류가 발생했습니다. GUID가 "{0}"인 프로젝트는 "{1}" 프로젝트 아래에 중첩되고 있는 것으로 나열되지만 솔루션에 없습니다. + {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. + + + MSB5010: No file format header found. + MSB5010: 파일 형식 헤더가 없습니다. + {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. + + + MSB5011: Parent project GUID not found in "{0}" project dependency section. + MSB5011: "{0}" 프로젝트 종속성 섹션에 부모 프로젝트 GUID가 없습니다. + {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. + + + MSB5012: Unexpected end-of-file reached inside "{0}" project section. + MSB5012: "{0}" 프로젝트 섹션에서 예기치 않은 파일 끝에 도달했습니다. + {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. + + + MSB5013: Error parsing a project section. + MSB5013: 프로젝트 섹션을 구문 분석하는 동안 오류가 발생했습니다. + {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. + + + MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. + MSB5014: 파일 형식 버전을 인식할 수 없습니다. MSBuild는 {0}.0 버전에서 {1}.0 버전까지의 솔루션 파일만 읽을 수 있습니다. + {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. + + + MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. + MSB5015: "{0}" 프로젝트의 WebsiteProperties 섹션에서 속성을 읽을 수 없습니다. + {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. + + + Unrecognized solution version "{0}", attempting to continue. + 인식할 수 없는 솔루션 버전 "{0}"입니다. 작업을 계속하려고 합니다. + + + + Solution file + 솔루션 파일 + UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: + "MSBUILD : Solution file error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + MSB5019: The project file is malformed: "{0}". {1} + MSB5019: 프로젝트 파일의 형식이 잘못되었습니다. "{0}". {1} + {StrBegin="MSB5019: "} + + + MSB5020: Could not load the project file: "{0}". {1} + MSB5020: 프로젝트 파일을 로드할 수 없습니다. "{0}". {1} + {StrBegin="MSB5020: "} + + + MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. + MSB5021: 빌드가 취소되었으므로 작업 실행 파일 "{0}" 및 자식 프로세스를 종료합니다. + {StrBegin="MSB5021: "} + + + This collection is read-only. + 이 컬렉션은 읽기 전용입니다. + + + + MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. + MSB5024: 유효한 MSBuild 위치를 확인할 수 없습니다. Visual Studio용 개발자 명령 프롬프트에서 프로세스를 실행해 보세요. + {StrBegin="MSB5024: "} + + + MSB4233: There was an exception while reading the log file: {0} + MSB4233: 로그 파일을 읽는 동안 예외가 발생했습니다. {0} + {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. + + + Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. + "{1}" 값이 할당된 "{0}" 매개 변수는 유효하지 않은 경로 또는 파일 문자를 포함할 수 없습니다. + + + + An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} + glob를 사용하여 fileSpec을 확장하는 동안 예외가 발생했습니다. fileSpec: "{0}", 파일 이름이라고 가정합니다. 예외: {1} + + + + MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. + MSB5029: 파일 "{0}"에 있는 요소 <{1}> 요소의 "{2}" 특성의 값 "{3}"은(는) 의도하지 않은 드라이브의 모든 파일을 열거하는 와일드카드입니다. 참조된 속성이 항상 정의되어 있고 프로젝트 및 현재 작업 디렉터리가 드라이브 루트에 없는지 확인합니다. + {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its + attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.pl.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.pl.xlf new file mode 100644 index 00000000000..f5c531296d1 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.pl.xlf @@ -0,0 +1,349 @@ + + + + + + MSB4188: Build was canceled. + MSB4188: Kompilacja została anulowana. + {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: Host zadań programu MSBuild nie obsługuje zadań wykonujących wywołania zwrotne do aparatu IBuildEngine. Jeśli chcesz wykonywać te operacje, uruchom zadanie w podstawowym procesie programu MSBuild. Zadanie będzie automatycznie wykonywane na hoście zadań, jeśli w deklaracji UsingTask ustawiono wartość „Runtime” lub „Architecture” albo w wywołaniu zadania ustawiono wartość „MSBuildRuntime” lub „MSBuildArchitecture”, która nie odpowiada bieżącemu środowisku uruchomieniowemu lub architekturze programu MSBuild. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + Build started. + Kompilacja rozpoczęła się. + + + + The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). + Liczba elementów w kolekcji jest większa niż dostępne miejsce w tablicy docelowej (zaczynając od określonego indeksu). + + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: Zestaw, który wywołuje konflikt z zestawem zadania „{0}”, został znaleziony w „{1}”. + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs + Niestandardowy typ zdarzenia '{0}' nie jest obsługiwany, ponieważ wszystkie typy zdarzeń niestandardowych były przestarzałe. Zamiast tego użyj elementu Extended*EventArgs. Więcej informacji: https://aka.ms/msbuild/eventargs + + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + Oczekiwano, że zdarzenie typu „{0}” będzie uszeregowane przy użyciu serializatora platformy .NET. Zdarzenie nie może podlegać szeregowaniu, dlatego zostało zignorowane. + + + + {0} ({1},{2}) + {0} ({1},{2}) + A file location to be embedded in a string. + + + MSB6005: Task attempted to log before it was initialized. Message was: {0} + MSB6005: Zadanie podjęło próbę zarejestrowania przed zainicjowaniem. Pojawił się komunikat: {0} + {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: „{0}” jest nieprawidłową wartością parametru „Importance”. Prawidłowe wartości: High, Normal i Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Wymienione zmiany otrzymane z węzła nadrzędnego zostaną wprowadzone w środowisku, a po sprawdzeniu działania zastosowane do hosta zadań: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Ustawienie dla elementu „{0}” wartości „{1}” zamiast wartości „{2}” obowiązującej w środowisku nadrzędnym. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + MSB4025: The project file could not be loaded. {0} + MSB4025: Nie można załadować pliku projektu. {0} + {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is + invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is + provided separately to loggers. + LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. + + + MSB4103: "{0}" is not a valid logger verbosity level. + MSB4103: „{0}” nie jest prawidłowym poziomem szczegółowości rejestratora. + {StrBegin="MSB4103: "} + + + MSBuild is expecting a valid "{0}" object. + Program MSBuild oczekuje prawidłowego obiektu „{0}”. + + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + Ścieżka: {0} przekracza limit maksymalnej długości ścieżki w systemie operacyjnym. W pełni kwalifikowana nazwa pliku musi się składać z mniej niż {1} znaków. + + + + MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". + MSB5028: Plik filtru rozwiązania w lokalizacji „{0}” obejmuje projekt „{1}”, który nie znajduje się w pliku rozwiązania w lokalizacji „{2}”. + {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. + + + MSB5025: Json in solution filter file "{0}" is incorrectly formatted. + MSB5025: Kod JSON w pliku filtru rozwiązania „{0}” jest niepoprawnie sformatowany. + {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. + + + MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. + MSB5026: Plik filtru rozwiązania w lokalizacji „{0}” określa, że plik rozwiązania będzie się znajdował w lokalizacji „{1}”, ale ten plik nie istnieje. + {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. + MSB5009: Błąd analizowania sekcji projektu „{0}” o identyfikatorze GUID: „{1}”. Jest ona zagnieżdżona w obszarze „{2}”, ale nie znaleziono tego projektu w rozwiązaniu. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. + MSB4132: Wersja narzędzi „{0}” nie została rozpoznana. Dostępne wersje narzędzi to {1}. + {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. + + + MSB5016: The name "{0}" contains an invalid character "{1}". + MSB5016: Nazwa „{0}” zawiera nieprawidłowy znak „{1}”. + {StrBegin="MSB5016: "} + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + „{0}” jest zastrzeżonym elementem metadanych i nie może zostać zmodyfikowany ani usunięty. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The string "{0}" cannot be converted to a boolean (true/false) value. + Ciągu „{0}” nie można przekonwertować na wartość logiczną (prawda/fałsz). + + + + MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + MSB5003: Nie można utworzyć pliku tymczasowego. Folder plików tymczasowych jest zapełniony lub jego ścieżka jest niepoprawna. {0} + {StrBegin="MSB5003: "} + + + MSB5018: Failed to delete the temporary file "{0}". {1} {2} + MSB5018: nie można usunąć pliku tymczasowego „{0}”. {1} {2} + {StrBegin="MSB5018: "} + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + Elementu metadanych „%({0})” nie można zastosować do ścieżki „{1}”. {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: Zadanie „{0}” zostało oznaczone atrybutem LoadInSeparateAppDomain, ale nie pochodzi od obiektu MarshalByRefObject. Sprawdź, czy zadanie pochodzi od obiektu MarshalByRefObject lub zadania AppDomainIsolatedTask. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + Program .NET Framework w wersji „{0}” nie jest obsługiwany. Podaj wartość z wyliczenia Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + + + + .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. + Program .NET Framework w wersji „{0}” nie jest obsługiwany, jeśli jawnym obiektem docelowym jest zestaw SDK systemu Windows, ponieważ taki zestaw jest obsługiwany tylko na platformie .NET w wersji 4.5 lub nowszej. Podaj wartość z wyliczenia Microsoft.Build.Utilities.TargetDotNetFrameworkVersion, która wynosi co najmniej Version45. + + + + Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. + Program Visual Studio w wersji „{0}” nie jest obsługiwany. Podaj wartość z wyliczenia Microsoft.Build.Utilities.VisualStudioVersion. + + + + When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} + Wystąpił błąd podczas próby wygenerowania ścieżki zestawu odwołania ze ścieżki „{0}” i monikera struktury „{1}”. {2} + No Error code because this resource will be used in an exception. The error code is discarded if it is included + + + Could not find directory path: {0} + Nie można odnaleźć ścieżki katalogu: {0} + Directory must exist + + + You do not have access to: {0} + Nie masz dostępu do następującego elementu: {0} + Directory must have access + + + Schema validation + Walidacja schematu + + UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: + "MSBUILD : Schema validation error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + + MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. + MSB5002: plik wykonywalny zadania „{0}” zostanie zakończony, ponieważ nie ukończył działania przed określonym limitem czasu ({1} ms). + {StrBegin="MSB5002: "} + + + Parameter "{0}" cannot be null. + Parametr „{0}” nie może być zerowy. + + + + Parameter "{0}" cannot have zero length. + Parametr „{0}” nie może mieć zerowej długości. + + + + Parameters "{0}" and "{1}" must have the same number of elements. + Parametry „{0}” i „{1}” muszą mieć tę samą liczbę elementów. + + + + The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. + Nie można odnaleźć ciągu zasobu „{0}” dla zadania „{1}”. Upewnij się, że nazwa zasobu „{0}” ma poprawną pisownię i że zasób istnieje w zestawie zadania. + + + + The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. + Zadanie „{0}” nie zarejestrowało swoich zasobów. Aby było możliwe użycie metody „TaskLoggingHelper.FormatResourceString()”, to zadanie musi zarejestrować swoje zasoby podczas konstruowania lub przy użyciu właściwości „TaskResources”. + LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. + + + MSB5004: The solution file has two projects named "{0}". + MSB5004: Plik rozwiązania zawiera dwa projekty o nazwie „{0}”. + {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. + + + MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. + MSB5005: Błąd podczas analizowania sekcji projektu „{0}”. Nazwa pliku projektu „{1}” zawiera nieprawidłowe znaki. + {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. + + + MSB5006: Error parsing project section for project "{0}". The project file name is empty. + MSB5006: Błąd podczas analizowania sekcji projektu „{0}”. Nazwa pliku projektu jest pusta. + {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. + + + MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. + MSB5007: Błąd podczas analizowania sekcji konfiguracji projektu w pliku rozwiązania. Wpis „{0}” jest nieprawidłowy. + {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. + + + MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. + MSB5008: Błąd podczas analizowania sekcji konfiguracji rozwiązania w pliku rozwiązania. Wpis „{0}” jest nieprawidłowy. + {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the nested project section in solution file. + MSB5009: Błąd podczas analizowania zagnieżdżonej sekcji projektu w pliku rozwiązania. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. + MSB5023: Błąd podczas analizowania zagnieżdżonej sekcji projektu w pliku rozwiązania. Projekt z identyfikatorem GUID „{0}” figuruje jako zagnieżdżony w projekcie „{1}”, ale nie istnieje w rozwiązaniu. + {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. + + + MSB5010: No file format header found. + MSB5010: Nie odnaleziono nagłówka formatu pliku. + {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. + + + MSB5011: Parent project GUID not found in "{0}" project dependency section. + MSB5011: Nie odnaleziono identyfikatora GUID projektu nadrzędnego w sekcji zależności projektu „{0}”. + {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. + + + MSB5012: Unexpected end-of-file reached inside "{0}" project section. + MSB5012: Osiągnięto nieoczekiwany koniec pliku wewnątrz sekcji projektu „{0}”. + {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. + + + MSB5013: Error parsing a project section. + MSB5013: Błąd podczas analizowania sekcji projektu. + {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. + + + MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. + MSB5014: Nie rozpoznano wersji formatu pliku. Program MSBuild może odczytywać tylko pliki rozwiązań w wersjach od {0}.0 do {1}.0 włącznie. + {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. + + + MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. + MSB5015: Nie można odczytać właściwości z sekcji WebsiteProperties projektu „{0}”. + {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. + + + Unrecognized solution version "{0}", attempting to continue. + Nierozpoznana wersja rozwiązania „{0}”, nastąpi próba kontynuacji. + + + + Solution file + Plik rozwiązania + UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: + "MSBUILD : Solution file error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + MSB5019: The project file is malformed: "{0}". {1} + MSB5019: Plik projektu ma nieprawidłową postać: „{0}”. {1} + {StrBegin="MSB5019: "} + + + MSB5020: Could not load the project file: "{0}". {1} + MSB5020: Nie można załadować pliku projektu: „{0}”. {1} + {StrBegin="MSB5020: "} + + + MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. + MSB5021: plik wykonywalny zadania „{0}” i jego procesy podrzędne zostaną zakończone, ponieważ anulowano kompilację. + {StrBegin="MSB5021: "} + + + This collection is read-only. + Ta kolekcja jest tylko do odczytu. + + + + MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. + MSB5024: Nie można określić prawidłowej lokalizacji programu MSBuild. Spróbuj uruchomić ten proces z wiersza polecenia dewelopera dla programu Visual Studio. + {StrBegin="MSB5024: "} + + + MSB4233: There was an exception while reading the log file: {0} + MSB4233: Wystąpił wyjątek podczas odczytywania pliku dziennika: {0} + {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. + + + Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. + Parametr „{0}” z przypisaną wartością „{1}” nie może mieć nieprawidłowej ścieżki ani zawierać nieprawidłowych znaków w pliku. + + + + An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} + Wystąpił wyjątek podczas rozwijania elementu fileSpec za pomocą globów: fileSpec: „{0}”, przy założeniu, że jest to nazwa pliku. Wyjątek: {1} + + + + MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. + MSB5029: Wartość „{0}” atrybutu „{1}” w elemencie <{2}> w pliku „{3}” jest symbolem wieloznacznym, który powoduje wyliczenie wszystkich plików na dysku, co prawdopodobnie nie było zamierzone. Sprawdź, czy przywoływane właściwości są zawsze zdefiniowane oraz czy projekt i bieżący katalog roboczy nie znajdują się w katalogu głównym dysku. + {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its + attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.pt-BR.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.pt-BR.xlf new file mode 100644 index 00000000000..cf78ee34200 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.pt-BR.xlf @@ -0,0 +1,349 @@ + + + + + + MSB4188: Build was canceled. + MSB4188: Compilação cancelada. + {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: O host de tarefas MSBuild não dá suporte a tarefas em execução que executam chamadas de retorno de IBuildEngine. Se desejar executar essas operações, execute sua tarefa no processo MSBuild principal. Uma tarefa será automaticamente executada no host de tarefas se UsingTask tiver como atributo um valor "Runtime" ou "Architecture" ou se a invocação da tarefa tiver como atributo um valor "MSBuildRuntime" ou "MSBuildArchitecture", que não coincida com o tempo de execução ou arquitetura atual do MSBuild. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + Build started. + Compilação iniciada. + + + + The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). + O número de elementos na coleção é maior que o espaço disponível na matriz de destino (ao iniciar no índice especificado). + + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: Foi encontrado um assembly conflitante no assembly da tarefa "{0}" em "{1}". + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs + Não há suporte '{0}' tipo de evento personalizado porque todos os tipos de eventos personalizados foram preteridos. Use Extended*EventArgs em vez disso. Mais informações: https://aka.ms/msbuild/eventargs + + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + Era esperado que o tipo de evento "{0}" fosse serializável usando o serializador .NET. O evento não era serializável e foi ignorado. + + + + {0} ({1},{2}) + {0} ({1},{2}) + A file location to be embedded in a string. + + + MSB6005: Task attempted to log before it was initialized. Message was: {0} + MSB6005: A tarefa tentou fazer o registro antes de ser inicializada. A mensagem era: {0} + {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}" é um valor inválido para o parâmetro "Importance". Os valores válidos são: High, Normal e Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Fazendo as seguintes modificações no ambiente recebido do nó pai antes de aplicá-lo ao host de tarefas: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Definindo "{0}" como "{1}" em vez do valor do ambiente pai, "{2}". + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + MSB4025: The project file could not be loaded. {0} + MSB4025: Não foi possível carregar o arquivo de projeto. {0} + {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is + invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is + provided separately to loggers. + LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. + + + MSB4103: "{0}" is not a valid logger verbosity level. + MSB4103: "{0}" não é um nível de detalhamento de agente de log válido. + {StrBegin="MSB4103: "} + + + MSBuild is expecting a valid "{0}" object. + O MSBuild está esperando um objeto "{0}" válido. + + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + Caminho: {0} excede o limite máximo do caminho do SO. O nome do arquivo totalmente qualificado deve ter menos de {1} caracteres. + + + + MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". + MSB5028: o arquivo de filtro da solução em "{0}" inclui o projeto "{1}" que não está no arquivo da solução em "{2}". + {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. + + + MSB5025: Json in solution filter file "{0}" is incorrectly formatted. + MSB5025: o JSON no arquivo de filtro da solução "{0}" está formatado incorretamente. + {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. + + + MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. + MSB5026: o arquivo de filtro da solução em "{0}" especifica que haverá um arquivo de solução em "{1}", mas esse arquivo não existe. + {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. + MSB5009: erro ao analisar a seção do projeto "{0}" com o GUID: "{1}". Ela está aninhada em "{2}", mas o projeto não foi encontrado na solução. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. + MSB4132: A versão das ferramentas "{0}" não é reconhecida. As versões das ferramentas disponíveis são {1}. + {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. + + + MSB5016: The name "{0}" contains an invalid character "{1}". + MSB5016: O nome "{0}" contém um caractere inválido "{1}". + {StrBegin="MSB5016: "} + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" são metadados de item reservado e não podem ser modificados ou excluídos. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The string "{0}" cannot be converted to a boolean (true/false) value. + A cadeia de caracteres "{0}" não pode ser convertida em um valor booliano (true/false). + + + + MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + MSB5003: Falha ao criar arquivo temporário. A pasta de arquivos temporários está cheia ou o caminho está incorreto. {0} + {StrBegin="MSB5003: "} + + + MSB5018: Failed to delete the temporary file "{0}". {1} {2} + MSB5018: falha ao excluir o arquivo temporário "{0}". {1} {2} + {StrBegin="MSB5018: "} + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + Os metadados do item "%({0})" não podem ser aplicados ao caminho "{1}". {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: A tarefa "{0}" foi marcada com o atributo LoadInSeparateAppDomain, mas não é derivada de MarshalByRefObject. Verifique se a tarefa é derivada de MarshalByRefObject ou de AppDomainIsolatedTask. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + Não há suporte para a versão "{0}" do .NET Framework. Especifique um valor da enumeração Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + + + + .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. + Não há suporte para a versão "{0}" do .NET Framework quando o destino for explicitamente o SDK do Windows, que só tem suporte no .NET 4.5 e posterior. Especifique um valor da enumeração Microsoft.Build.Utilities.TargetDotNetFrameworkVersion que seja Version45 ou acima. + + + + Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. + O Visual Studio versão "{0}" não tem suporte. Especifique um valor da enumeração Microsoft.Build.Utilities.VisualStudioVersion. + + + + When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} + Erro ao tentar gerar um caminho de assembly de referência no caminho "{0}" e o moniker de estrutura "{1}". {2} + No Error code because this resource will be used in an exception. The error code is discarded if it is included + + + Could not find directory path: {0} + Não foi possível localizar o caminho do diretório: {0} + Directory must exist + + + You do not have access to: {0} + Você não tem acesso a: {0} + Directory must have access + + + Schema validation + Validação de esquema + + UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: + "MSBUILD : Schema validation error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + + MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. + MSB5002: encerrando a tarefa executável "{0}" porque ela não foi concluída dentro do limite especificado de {1} milissegundos. + {StrBegin="MSB5002: "} + + + Parameter "{0}" cannot be null. + O parâmetro "{0}" não pode ser nulo. + + + + Parameter "{0}" cannot have zero length. + O parâmetro "{0}" não pode ter comprimento zero. + + + + Parameters "{0}" and "{1}" must have the same number of elements. + Os parâmetros "{0}" e "{1}" devem ter o mesmo número de elementos. + + + + The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. + A cadeia de caracteres de recursos "{0}" da tarefa "{1}" não foi encontrada. Confirme se o nome do recurso "{0}" foi digitado corretamente e se o recurso existe no assembly da tarefa. + + + + The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. + A tarefa "{0}" não registrou seus recursos. Para usar o método "TaskLoggingHelper.FormatResourceString()", essa tarefa deve registrar seus recursos durante a construção ou usando a propriedade "TaskResources". + LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. + + + MSB5004: The solution file has two projects named "{0}". + MSB5004: O arquivo de solução tem dois projetos denominados "{0}". + {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. + + + MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. + MSB5005: Erro ao analisar a seção do projeto para o projeto "{0}". O nome do arquivo de projeto "{1}" contém caracteres inválidos. + {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. + + + MSB5006: Error parsing project section for project "{0}". The project file name is empty. + MSB5006: Erro ao analisar a seção do projeto para o projeto "{0}". O nome do arquivo de projeto está vazio. + {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. + + + MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. + MSB5007: Erro ao analisar a seção de configuração do projeto no arquivo de solução. A entrada "{0}" não é válida. + {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. + + + MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. + MSB5008: Erro ao analisar a seção de configuração da solução no arquivo de solução. A entrada "{0}" não é válida. + {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the nested project section in solution file. + MSB5009: Erro ao analisar a seção do projeto aninhado no arquivo de solução. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. + MSB5023: Erro ao analisar a seção do projeto aninhado no arquivo de solução. Um projeto com o GUID "{0}" é listado como aninhado no projeto "{1}", mas não existe na solução. + {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. + + + MSB5010: No file format header found. + MSB5010: Nenhum cabeçalho de formato de arquivo encontrado. + {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. + + + MSB5011: Parent project GUID not found in "{0}" project dependency section. + MSB5011: O projeto pai GUID não foi encontrado na seção de dependência do projeto "{0}". + {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. + + + MSB5012: Unexpected end-of-file reached inside "{0}" project section. + MSB5012: Fim de arquivo inesperado na seção do projeto "{0}". + {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. + + + MSB5013: Error parsing a project section. + MSB5013: Erro ao analisar uma seção do projeto. + {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. + + + MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. + MSB5014: A versão de formato do arquivo não é reconhecida. O MSBuild pode ler apenas os arquivos de solução entre as versões {0}.0 e {1}.0, inclusive. + {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. + + + MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. + MSB5015: As propriedades não podem ser lidas a partir da seção WebsiteProperties do projeto "{0}". + {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. + + + Unrecognized solution version "{0}", attempting to continue. + Versão de solução não reconhecida "{0}". Tentando continuar. + + + + Solution file + Arquivo de solução + UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: + "MSBUILD : Solution file error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + MSB5019: The project file is malformed: "{0}". {1} + MSB5019: Arquivo de projeto malformado: "{0}". {1} + {StrBegin="MSB5019: "} + + + MSB5020: Could not load the project file: "{0}". {1} + MSB5020: Não foi possível carregar o arquivo de projeto: "{0}". {1} + {StrBegin="MSB5020: "} + + + MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. + MSB5021: Encerrando a tarefa executável "{0}" e seus processos filho porque o build foi cancelado. + {StrBegin="MSB5021: "} + + + This collection is read-only. + Esta coleção é somente leitura. + + + + MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. + MSB5024: não foi possível determinar um local válido para o MSBuild. Tente executar esse processo no Prompt de Comando do Desenvolvedor para o Visual Studio. + {StrBegin="MSB5024: "} + + + MSB4233: There was an exception while reading the log file: {0} + MSB4233: houve uma exceção durante a leitura do arquivo de log: {0} + {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. + + + Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. + O parâmetro "{0}" com o valor "{1}" atribuído não pode ter um caminho inválido ou caracteres de arquivo inválidos. + + + + An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} + Ocorreu uma exceção ao expandir um fileSpec com globs: fileSpec: "{0}", assumindo que se trata de um nome de arquivo. Exceção: {1} + + + + MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. + MSB5029: O valor "{0}" do atributo "{1}" no elemento <{2}> no arquivo "{3}" é um curinga que resulta na enumeração de todos os arquivos na unidade, o que provavelmente não foi planejado. Verifique se as propriedades referenciadas estão sempre definidas e se o projeto e o diretório de trabalho atual não estão na raiz da unidade. + {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its + attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ru.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ru.xlf new file mode 100644 index 00000000000..a40cb28ad14 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ru.xlf @@ -0,0 +1,349 @@ + + + + + + MSB4188: Build was canceled. + MSB4188: сборка отменена. + {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: узел сборки MSBuild не поддерживает задачи, которые выполняют обратные вызовы IBuildEngine. Если необходимо выполнить эти операции, запустите задачу в основном процессе MSBuild. Задача будет автоматически выполнена в узле задач, если для UsingTask был указан атрибут со значением "Runtime" или "Architecture" или для вызова задачи был указан атрибут со значением "MSBuildRuntime" или "MSBuildArchitecture", которые не соответствуют текущей среде выполнения или архитектуре MSBuild. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + Build started. + Сборка начата. + + + + The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). + Количество элементов в коллекции превышает доступное пространство в целевом массиве (при запуске с указанного индекса). + + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: в "{1}" обнаружена сборка, конфликтующая со сборкой задачи "{0}". + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs + Настраиваемый тип '{0}' не поддерживается, так как все пользовательские типы событий устарели. Используйте extended*EventArgs. Дополнительные сведения: https://aka.ms/msbuild/eventargs + + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + Необходимо, чтобы тип события "{0}" был сериализуемым с помощью сериализатора .NET. Событие не было сериализуемым и было пропущено. + + + + {0} ({1},{2}) + {0} ({1},{2}) + A file location to be embedded in a string. + + + MSB6005: Task attempted to log before it was initialized. Message was: {0} + MSB6005: задачей предпринята попытка вести журнал до своей инициализации. Сообщение: {0} + {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}" — недопустимое значение для параметра Importance (Важность). Допустимые значения: High (высокая), Normal (средняя) и Low (низкая). + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Перед применением окружения, полученного от родительского узла, к серверу задач в нем выполняются следующие изменения: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Задание для "{0}" значения "{1}", а не значения родительского окружения "{2}". + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + MSB4025: The project file could not be loaded. {0} + MSB4025: не удалось загрузить файл проекта. {0} + {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is + invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is + provided separately to loggers. + LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. + + + MSB4103: "{0}" is not a valid logger verbosity level. + MSB4103: "{0}" не является допустимым уровнем детализации средства ведения журнала. + {StrBegin="MSB4103: "} + + + MSBuild is expecting a valid "{0}" object. + Для MSBuild требуется допустимый объект "{0}". + + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + Длина пути {0} превышает максимально допустимую в ОС. Символов в полном имени файла должно быть не больше {1}. + + + + MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". + MSB5028: файл фильтра решения в "{0}" включает проект "{1}", который отсутствует в файле решения в "{2}". + {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. + + + MSB5025: Json in solution filter file "{0}" is incorrectly formatted. + MSB5025: код JSON в файле фильтра решения "{0}" имеет неправильный формат. + {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. + + + MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. + MSB5026: файл фильтра решения в "{0}" указывает на то, что в "{1}" будет находиться файл решения, однако этот файл отсутствует. + {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. + MSB5009: ошибка при синтаксическом анализе раздела проекта "{0}" с GUID: "{1}". Он вложен в "{2}", но этот проект не найден в решении. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. + MSB4132: версия инструментов "{0}" не распознана. Доступные версии инструментов: {1}. + {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. + + + MSB5016: The name "{0}" contains an invalid character "{1}". + MSB5016: имя "{0}" содержит недопустимый знак "{1}". + {StrBegin="MSB5016: "} + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" - зарезервированные метаданные элемента; их изменение или удаление невозможно. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The string "{0}" cannot be converted to a boolean (true/false) value. + Строка "{0}" не может быть преобразована в логическое значение (истина/ложь). + + + + MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + MSB5003: не удалось создать временный файл. Папка временных файлов переполнена, или указан неверный путь. {0} + {StrBegin="MSB5003: "} + + + MSB5018: Failed to delete the temporary file "{0}". {1} {2} + MSB5018: не удалось удалить временный файл "{0}". {1} {2} + {StrBegin="MSB5018: "} + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + Не удается применить метаданные элемента "%({0})" к пути "{1}". {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: задача "{0}" помечена атрибутом LoadInSeparateAppDomain, но не является производной от MarshalByRefObject. Задача должна быть производной от MarshalByRefObject или AppDomainIsolatedTask. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + .NET Framework версии "{0}" не поддерживается. Укажите значение из перечисления Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + + + + .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. + При явном использовании пакета Windows SDK платформа .NET Framework версии "{0}" не поддерживается; она поддерживается только платформой .NET 4.5 и более поздними версиями. Укажите значение из перечисления Microsoft.Build.Utilities.TargetDotNetFrameworkVersion версии Version45 или более поздней. + + + + Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. + Visual Studio версии "{0}" не поддерживается. Укажите значение из перечисления Microsoft.Build.Utilities.VisualStudioVersion. + + + + When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} + Ошибка при попытке создать ссылочный путь к сборке из пути "{0}" и моникер инфраструктуры "{1}". {2} + No Error code because this resource will be used in an exception. The error code is discarded if it is included + + + Could not find directory path: {0} + Не удалось найти путь к каталогу: {0} + Directory must exist + + + You do not have access to: {0} + Отсутствуют права доступа к: {0} + Directory must have access + + + Schema validation + Проверка схемы + + UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: + "MSBUILD : Schema validation error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + + MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. + MSB5002: завершается исполняемый файл задачи "{0}", так как он не закончил работу в указанный период {1} мс. + {StrBegin="MSB5002: "} + + + Parameter "{0}" cannot be null. + Параметр "{0}" не может иметь неопределенное (null) значение. + + + + Parameter "{0}" cannot have zero length. + Длина параметра "{0}" не может быть равна нулю. + + + + Parameters "{0}" and "{1}" must have the same number of elements. + Параметры "{0}" и "{1}" должны содержать одинаковое количество элементов. + + + + The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. + Не удалось найти строку ресурса "{0}" для задачи "{1}". Убедитесь, что имя ресурса "{0}" написано правильно и ресурс существует в сборке задачи. + + + + The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. + Задача "{0}" не зарегистрировала свои ресурсы. Чтобы использовать метод "TaskLoggingHelper.FormatResourceString()", эта задача должна зарегистрировать свои ресурсы во время построения или через свойство "TaskResources". + LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. + + + MSB5004: The solution file has two projects named "{0}". + MSB5004: файл решения содержит два проекта с именем "{0}". + {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. + + + MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. + MSB5005: ошибка синтаксического анализа раздела проекта "{0}". Имя файла проекта "{1}" содержит недопустимые символы. + {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. + + + MSB5006: Error parsing project section for project "{0}". The project file name is empty. + MSB5006: ошибка синтаксического анализа раздела проекта "{0}". Имя файла проекта пусто. + {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. + + + MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. + MSB5007: ошибка синтаксического анализа раздела конфигурации проекта в файле решения. Недопустимая запись "{0}". + {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. + + + MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. + MSB5008: ошибка синтаксического анализа раздела конфигурации проекта в файле решения. Недопустимая запись "{0}". + {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the nested project section in solution file. + MSB5009: ошибка синтаксического анализа вложенного раздела проекта в файле решения. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. + MSB5023: ошибка синтаксического анализа вложенного раздела проекта в файле решения. Указано, что проект с GUID "{0}" вложен в проект "{1}", но сам проект отсутствует в решении. + {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. + + + MSB5010: No file format header found. + MSB5010: заголовок формата файла не найден. + {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. + + + MSB5011: Parent project GUID not found in "{0}" project dependency section. + MSB5011: родительский проект GUID не найден в разделе зависимости проекта "{0}". + {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. + + + MSB5012: Unexpected end-of-file reached inside "{0}" project section. + MSB5012: достигнуто непредвиденное окончание файла в разделе проекта "{0}". + {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. + + + MSB5013: Error parsing a project section. + MSB5013: ошибка при синтаксическом анализе раздела проекта. + {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. + + + MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. + MSB5014: не распознана версия формата файла. MSBuild может считывать файлы решения только версий с {0}.0 по {1}.0 включительно. + {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. + + + MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. + MSB5015: не удалось прочитать свойства из раздела WebsiteProperties проекта "{0}". + {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. + + + Unrecognized solution version "{0}", attempting to continue. + Нераспознанная версия решения "{0}"; попытка продолжить. + + + + Solution file + Файл решения + UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: + "MSBUILD : Solution file error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + MSB5019: The project file is malformed: "{0}". {1} + MSB5019: файл проекта имеет неправильный формат: "{0}". {1} + {StrBegin="MSB5019: "} + + + MSB5020: Could not load the project file: "{0}". {1} + MSB5020: не удалось загрузить файл проекта: "{0}". {1} + {StrBegin="MSB5020: "} + + + MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. + MSB5021: завершается исполняемый файл задачи "{0}" и его дочерние процессы, так как сборка отменена. + {StrBegin="MSB5021: "} + + + This collection is read-only. + Эта коллекция доступна только для чтения. + + + + MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. + MSB5024: Не удалось определить допустимое расположение для MSBuild. Попробуйте запустить этот процесс из командной строки разработчика для Visual Studio. + {StrBegin="MSB5024: "} + + + MSB4233: There was an exception while reading the log file: {0} + MSB4233: возникло исключение при чтении файла журнала: {0} + {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. + + + Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. + Параметр "{0}" с назначенным значением "{1}" не может иметь недопустимый путь или недопустимые символы файлов. + + + + An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} + Возникло исключение при расширении fileSpec со стандартными масками: fileSpec: "{0}", предполагая, что это имя файла. Исключение: {1} + + + + MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. + MSB5029: значение "{0}" атрибута "{1}" в элементе <{2}> в файле "{3}" является подстановочным знаком, который приводит к перечислению всех файлов на диске, что, вероятно, не предполагалось. Убедитесь, что указанные свойства всегда определены и что проект и текущий рабочий каталог не находятся в корне диска. + {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its + attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.tr.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.tr.xlf new file mode 100644 index 00000000000..e5e76c65345 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.tr.xlf @@ -0,0 +1,349 @@ + + + + + + MSB4188: Build was canceled. + MSB4188: Derleme iptal edildi. + {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: MSBuild görev konağı IBuildEngine geri çağırmaları gerçekleştiren görevleri çalıştırmayı desteklemez. Bu işlemleri gerçekleştirmek istiyorsanız lütfen bunun yerine görevinizi çekirdek MSBuild işleminde gerçekleştirin. UsingTask öğesine bir "Runtime" veya "Architecture" değeri atfedilmişse veya görev çağrısına MSBuild’in geçerli çalışma zamanı ya da mimarisi ile eşleşmeyen bir "MSBuildRuntime" veya "MSBuildArchitecture" değeri atfedilmişse görev konağında bir görev otomatik olarak yürütülür. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + Build started. + Oluşturma başlatıldı. + + + + The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). + Koleksiyondaki öğe sayısı hedef dizideki kullanılabilir boşluğun boyutundan (belirtilen dizinden başlayarak) daha büyük. + + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: "{0}" görev derlemesi için "{1}" konumunda çakışan derleme bulundu. + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs + Tüm özel '{0}' türleri kullanım dışı bırakıldığinden özel olay türü türü desteklenmiyor. Lütfen bunun yerine Extended*EventArgs kullanın. Daha fazla bilgi: https://aka.ms/msbuild/eventargs + + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + "{0}" olay türünün .NET serileştiricisi kullanılarak serileştirilebilir olması bekleniyordu. Olay serileştirilebilir değildi ve yoksayıldı. + + + + {0} ({1},{2}) + {0} ({1},{2}) + A file location to be embedded in a string. + + + MSB6005: Task attempted to log before it was initialized. Message was: {0} + MSB6005: Görev başlatılmadan önce günlüğe yazmaya çalıştı. İleti: {0} + {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}", "Importance" parametresi için geçersiz bir değer. Geçerli değerler şunlardır: High, Normal ve Low. + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Üst düğümden alınan ortam görev ana bilgisayarına uygulanmadan önce ortamda aşağıdaki değişiklikler yapılıyor: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + '{0}', üst ortamında değeri olan '{2}' yerine '{1}' değerine ayarlanıyor. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + MSB4025: The project file could not be loaded. {0} + MSB4025: Proje dosyası yüklenemedi. {0} + {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is + invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is + provided separately to loggers. + LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. + + + MSB4103: "{0}" is not a valid logger verbosity level. + MSB4103: "{0}", geçerli bir günlükçü ayrıntı düzeyi değil. + {StrBegin="MSB4103: "} + + + MSBuild is expecting a valid "{0}" object. + MSBuild, geçerli bir "{0}" nesnesi bekliyor. + + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + Yol: {0}, işletim sisteminin en yüksek yol sınırını aşıyor. Tam dosya adı en fazla {1} karakter olmalıdır. + + + + MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". + MSB5028: "{0}" konumundaki çözüm filtresi dosyası, "{2}" konumundaki çözüm dosyasında bulunmayan "{1}" projesini içeriyor. + {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. + + + MSB5025: Json in solution filter file "{0}" is incorrectly formatted. + MSB5025: "{0}" çözüm filtresi dosyasındaki JSON hatalı biçimlendirilmiş. + {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. + + + MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. + MSB5026: "{0}" konumundaki çözüm filtresi dosyası, "{1}" konumunda bir çözüm dosyası olacağını belirtiyor, ancak bu dosya mevcut değil. + {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. + MSB5009: "{1}" GUID'si ile "{0}" proje bölümü ayrıştırılırken hata oluştu. Proje "{2}" altında iç içe yerleştirilmiş ancak bu proje çözümde bulunamadı. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. + MSB4132: Araçlar sürümü "{0}" tanınmıyor. Kullanılabilir araç sürümleri şunlardır: {1}. + {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. + + + MSB5016: The name "{0}" contains an invalid character "{1}". + MSB5016: "{0}" adı geçersiz "{1}" karakterini içeriyor. + {StrBegin="MSB5016: "} + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" ayrılmış bir öğe meta verisi olduğu için değiştirilemez veya silinemez. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The string "{0}" cannot be converted to a boolean (true/false) value. + "{0}" dizesi bir boole (doğru/yanlış) değerine dönüştürülemiyor. + + + + MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + MSB5003: Geçici bir dosya oluşturulamadı. Geçici dosyalar klasörü dolu veya yolu hatalı. {0} + {StrBegin="MSB5003: "} + + + MSB5018: Failed to delete the temporary file "{0}". {1} {2} + MSB5018: "{0}" geçici dosyası silinemedi. {1} {2} + {StrBegin="MSB5018: "} + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + "%({0})" öğe meta verisi "{1}" yoluna uygulanamıyor. {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: "{0}" görevi LoadInSeparateAppDomain özniteliğiyle işaretlenmiş, ancak MarshalByRefObject öğesinden türetilmiyor. Görevin MarshalByRefObject veya AppDomainIsolatedTask öğesinden türetildiğini denetleyin. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + .NET Framework "{0}" sürümü desteklenmiyor. Lütfen Microsoft.Build.Utilities.TargetDotNetFrameworkVersion sabit listesinden bir değer belirtin. + + + + .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. + Yalnızca .NET 4.5 ve sonraki sürümlerde desteklenen Windows SDK açıkça hedeflenirken .NET Framework "{0}" sürümü desteklenmez. Lütfen Microsoft.Build.Utilities.TargetDotNetFrameworkVersion sabit listesinden Sürüm 45 veya üzerinde bir değer belirtin. + + + + Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. + Visual Studio "{0}" sürümü desteklenmiyor. Lütfen Microsoft.Build.Utilities.VisualStudioVersion sabit listesinden bir değer belirtin. + + + + When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} + "{0}" yolundan ve "{1}" framework adından bir başvuru bütünleştirilmiş kod yolu oluşturmaya çalışılırken bir hata oluştu. {2} + No Error code because this resource will be used in an exception. The error code is discarded if it is included + + + Could not find directory path: {0} + Dizin yolu bulunamadı: {0} + Directory must exist + + + You do not have access to: {0} + Şu öğeye erişiminiz yok: {0} + Directory must have access + + + Schema validation + Şema doğrulama + + UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: + "MSBUILD : Schema validation error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + + MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. + MSB5002: "{0}" görev yürütülebilir dosyası belirtilen {1} milisaniye sınırı içinde tamamlanmadığından sonlandırılıyor. + {StrBegin="MSB5002: "} + + + Parameter "{0}" cannot be null. + "{0}" parametresi null olamaz. + + + + Parameter "{0}" cannot have zero length. + "{0}" parametresinin uzunluğu sıfır olamaz. + + + + Parameters "{0}" and "{1}" must have the same number of elements. + "{0}" ve "{1}" parametrelerinin öğe sayıları aynı olmalıdır. + + + + The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. + "{1}" görevi için "{0}" kaynak dizesi bulunamıyor. "{0}" kaynak adının doğru yazıldığını ve kaynağın görevin bütünleştirilmiş kodunda bulunduğunu doğrulayın. + + + + The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. + "{0}" görevi, kaynaklarını kaydetmedi. "TaskLoggingHelper.FormatResourceString()" metodunu kullanmak için, bu görevin oluşturma sırasında veya "TaskResources" özelliği aracılığıyla kaynaklarını kaydetmesi gerekir. + LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. + + + MSB5004: The solution file has two projects named "{0}". + MSB5004: Çözüm dosyasında "{0}" adlı iki proje var. + {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. + + + MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. + MSB5005: "{0}" projesi için proje bölümü ayrıştırma hatası. "{1}" proje dosyası adı geçersiz karakterler içeriyor. + {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. + + + MSB5006: Error parsing project section for project "{0}". The project file name is empty. + MSB5006: "{0}" projesi için proje bölümü ayrıştırma hatası. Proje dosyası adı boş. + {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. + + + MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. + MSB5007: Çözüm dosyasında proje yapılandırma bölümünü ayrıştırma hatası. "{0}" girdisi geçersiz. + {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. + + + MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. + MSB5008: Çözüm dosyasında çözüm yapılandırma bölümünü ayrıştırma hatası. "{0}" girdisi geçersiz. + {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the nested project section in solution file. + MSB5009: Çözüm dosyasındaki iç içe proje bölümünü ayrıştırma hatası oluştu. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. + MSB5023: Çözüm dosyasındaki iç içe proje bölümünü ayrıştırma hatası. GUID’si "{0}" olan bir proje, "{1}" projesi altında iç içe olarak listelendi, ancak çözümde yok. + {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. + + + MSB5010: No file format header found. + MSB5010: Dosya biçimi üstbilgisi bulunamadı. + {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. + + + MSB5011: Parent project GUID not found in "{0}" project dependency section. + MSB5011: Üst proje GUID'i, "{0}" proje bağımlılığı bölümünde bulunamadı. + {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. + + + MSB5012: Unexpected end-of-file reached inside "{0}" project section. + MSB5012: "{0}" proje bölümünde beklenmeyen şekilde dosya sonuna ulaşıldı. + {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. + + + MSB5013: Error parsing a project section. + MSB5013: Proje bölümünü ayrıştırma hatası oluştu. + {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. + + + MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. + MSB5014: Dosya biçimi sürümü tanınmıyor. MSBuild yalnızca {0}.0 ile {1}.0 (dahil) sürümleri arasındaki çözüm dosyalarını okuyabilir. + {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. + + + MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. + MSB5015: Özellikler, "{0}" projesinin WebsiteProperties bölümünden okunamadı. + {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. + + + Unrecognized solution version "{0}", attempting to continue. + Tanınmayan çözüm sürümü "{0}"; devam edilmeye çalışılıyor. + + + + Solution file + Çözüm dosyası + UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: + "MSBUILD : Solution file error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + MSB5019: The project file is malformed: "{0}". {1} + MSB5019: Proje dosyası yanlış biçimlendirilmiş: "{0}". {1} + {StrBegin="MSB5019: "} + + + MSB5020: Could not load the project file: "{0}". {1} + MSB5020: Proje dosyası yüklenemedi: "{0}". {1} + {StrBegin="MSB5020: "} + + + MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. + MSB5021: Derleme iptal edildiğinden "{0}" görev yürütülebilir dosyası ve bunun alt öğeleri sonlandırılıyor. + {StrBegin="MSB5021: "} + + + This collection is read-only. + Bu koleksiyon salt okunur. + + + + MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. + MSB5024: MSBuild için geçerli bir konum belirlenemedi. Bu işlemi Visual Studio için Geliştirici Komut İstemi’nde çalıştırmayı deneyin. + {StrBegin="MSB5024: "} + + + MSB4233: There was an exception while reading the log file: {0} + MSB4233: Günlük dosyası okunurken bir özel durum oluştu: {0} + {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. + + + Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. + "{1}" değeri atanan "{0}" parametresinde geçersiz yol veya geçersiz dosya karakterleri bulunamaz. + + + + An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} + globs: fileSpec: "{0}" ile bir fileSpec genişletilirken özel bir durum oluştu, bunun bir dosya adı olduğu varsayıldı. Özel Durum: {1} + + + + MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. + MSB5029: "{0}" dosyasındaki <{1}> öğesinde "{2}" özniteliğinin "{3}" değeri, sürücüdeki tüm dosyaların numaralandırılmasıyla sonuçlanan (büyük olasılıkla bunun olması amaçlanmıyordu) bir joker karakterdir. Başvurulan özelliklerin her zaman tanımlandığını ve projenin ve geçerli çalışma dizininin sürücü kökünde bulunmadığını kontrol edin. + {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its + attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.xlf new file mode 100644 index 00000000000..3d8ccfdc9e1 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.xlf @@ -0,0 +1,235 @@ + + + + + + + MSB4188: Build was canceled. + {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + Build started. + + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + + + + {0} ({1},{2}) + A file location to be embedded in a string. + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + MSB4025: The project file could not be loaded. {0} + {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is + invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is + provided separately to loggers. + LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. + + + MSB4103: "{0}" is not a valid logger verbosity level. + {StrBegin="MSB4103: "} + + + MSBuild is expecting a valid "{0}" object. + + + + MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. + {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. + + + MSB5016: The name "{0}" contains an invalid character "{1}". + {StrBegin="MSB5016: "} + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The string "{0}" cannot be converted to a boolean (true/false) value. + + + + MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + {StrBegin="MSB5003: "} + + + MSB5018: Failed to delete the temporary file "{0}". {1} + {StrBegin="MSB5018: "} + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + + + + .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. + + + + Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. + + + + When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} + No Error code because this resource will be used in an exception. The error code is discarded if it is included + + + Could not find directory path: {0} + Directory must exist + + + You do not have access to: {0} + Directory must have access + + + Schema validation + + UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: + "MSBUILD : Schema validation error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + + MSB5002: The task executable has not completed within the specified limit of {0} milliseconds, terminating. + {StrBegin="MSB5002: "} + + + Parameter "{0}" cannot be null. + + + + Parameter "{0}" cannot have zero length. + + + + Parameters "{0}" and "{1}" must have the same number of elements. + + + + The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. + + + + The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. + LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. + + + MSB5004: The solution file has two projects named "{0}". + {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. + + + MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. + {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. + + + MSB5006: Error parsing project section for project "{0}". The project file name is empty. + {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. + + + MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. + {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. + + + MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. + {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the nested project section in solution file. + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. + {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. + + + MSB5010: No file format header found. + {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. + + + MSB5011: Parent project GUID not found in "{0}" project dependency section. + {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. + + + MSB5012: Unexpected end-of-file reached inside "{0}" project section. + {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. + + + MSB5013: Error parsing a project section. + {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. + + + MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. + {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. + + + MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. + {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. + + + Unrecognized solution version "{0}", attempting to continue. + + + + Solution file + UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: + "MSBUILD : Solution file error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + MSB5019: The project file is malformed: "{0}". {1} + {StrBegin="MSB5019: "} + + + MSB5020: Could not load the project file: "{0}". {1} + {StrBegin="MSB5020: "} + + + MSB5021: "{0}" and its child processes are being terminated in order to cancel the build. + {StrBegin="MSB5021: "} + + + This collection is read-only. + + + + MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. + {StrBegin="MSB5021: "} + + + MSB4233: There was an exception while reading the log file: {0} + {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. + + + Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. + + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.zh-Hans.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.zh-Hans.xlf new file mode 100644 index 00000000000..f0540b9f5e0 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.zh-Hans.xlf @@ -0,0 +1,349 @@ + + + + + + MSB4188: Build was canceled. + MSB4188: 已取消生成。 + {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: MSBuild 任务宿主不支持运行执行 IBuildEngine 回调的任务。如果需要执行这些操作,请改为在核心 MSBuild 进程中运行您的任务。如果 UsingTask 已用“Runtime”或“Architecture”值特性化,或者任务调用已用“MSBuildRuntime”或“MSBuildArchitecture”值(与 MSBuild 的当前运行时或体系结构不匹配)特性化,则将自动在任务宿主中执行任务。 + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + Build started. + 已启动生成。 + + + + The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). + 集合中的元素数大于目标阵列中的可用空间 (从指定索引开始时)。 + + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: 在“{1}”处发现了与任务程序集“{0}”冲突的程序集。 + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs + 不支持自定义事件类型 '{0}',因为已弃用所有自定义事件类型。请改用 Extended*EventArgs。详细信息: https://aka.ms/msbuild/eventargs + + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + 事件类型“{0}”应可以使用 .NET 序列化程序进行序列化。此事件不可序列化,已忽略它。 + + + + {0} ({1},{2}) + {0} ({1},{2}) + A file location to be embedded in a string. + + + MSB6005: Task attempted to log before it was initialized. Message was: {0} + MSB6005: 任务尚未初始化就尝试进行日志记录。消息为: {0} + {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: “{0}”是无效的“Importance”参数值。有效值包括: High、Normal 和 Low。 + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + 先对从父节点收到的环境进行以下修改,然后再将其应用于任务宿主: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + 将“{0}”设置为“{1}”,而不是父环境的值“{2}”。 + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + MSB4025: The project file could not be loaded. {0} + MSB4025: 未能加载项目文件。{0} + {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is + invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is + provided separately to loggers. + LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. + + + MSB4103: "{0}" is not a valid logger verbosity level. + MSB4103: "{0}" 不是有效的记录器详细程度。 + {StrBegin="MSB4103: "} + + + MSBuild is expecting a valid "{0}" object. + MSBuild 需要有效的“{0}”对象。 + + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + 路径: {0} 超过 OS 最大路径限制。完全限定的文件名必须少于 {1} 个字符。 + + + + MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". + MSB5028: 位于“{0}”的解决方案筛选器文件包含“{2}”处的解决方案文件中没有的项目“{1}”。 + {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. + + + MSB5025: Json in solution filter file "{0}" is incorrectly formatted. + MSB5025: 解决方案筛选器文件“{0}”中的 JSON 的格式不正确。 + {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. + + + MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. + MSB5026: 位于“{0}”的解决方案筛选器文件指定“{1}”处将存在一个解决方案文件,但该文件不存在。 + {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. + MSB5009: 分析 GUID 为 "{1}" 的项目 "{0}" 部分时出错。它嵌套在 "{2}" 下,但在解决方案中未找到该项目。 + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. + MSB4132: 无法识别工具版本“{0}”。可用的工具版本为 {1}。 + {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. + + + MSB5016: The name "{0}" contains an invalid character "{1}". + MSB5016: 名称“{0}”包含无效字符“{1}”。 + {StrBegin="MSB5016: "} + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + “{0}”是保留的项元数据,不能修改或删除。 + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The string "{0}" cannot be converted to a boolean (true/false) value. + 无法将字符串“{0}”转换为布尔值(true/false)。 + + + + MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + MSB5003: 未能创建临时文件。临时文件文件夹已满或其路径不正确。{0} + {StrBegin="MSB5003: "} + + + MSB5018: Failed to delete the temporary file "{0}". {1} {2} + MSB5018: 未能删除临时文件“{0}”。{1} {2} + {StrBegin="MSB5018: "} + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + 无法将项元数据“%({0})”应用于路径“{1}”。{2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: “{0}”任务已标记为 LoadInSeparateAppDomain 特性,但未派生自 MarshalByRefObject。请检查该任务是派生自 MarshalByRefObject 还是 AppDomainIsolatedTask。 + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + 不支持 .NET Framework 版本“{0}”。请指定枚举 Microsoft.Build.Utilities.TargetDotNetFrameworkVersion 中的某个值。 + + + + .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. + 当明确以 Windows SDK 为目标时,不支持 .NET Framework 版本“{0}”,Windows SDK 只在 .NET 4.5 及更高版本上受支持。请指定枚举 Microsoft.Build.Utilities.TargetDotNetFrameworkVersion 中 Version45 或以上的值。 + + + + Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. + 不支持 Visual Studio 版本“{0}”。请指定枚举 Microsoft.Build.Utilities.VisualStudioVersion 中的某个值。 + + + + When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} + 尝试从路径“{0}”和框架名字对象“{1}”生成引用程序集路径时出错。{2} + No Error code because this resource will be used in an exception. The error code is discarded if it is included + + + Could not find directory path: {0} + 未能找到目录路径: {0} + Directory must exist + + + You do not have access to: {0} + 您无权访问 {0} + Directory must have access + + + Schema validation + 架构验证 + + UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: + "MSBUILD : Schema validation error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + + MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. + MSB5002: 终止任务可执行文件“{0}”,因为它未在指定的 {1} 毫秒限制内完成。 + {StrBegin="MSB5002: "} + + + Parameter "{0}" cannot be null. + 参数“{0}”不能为 null。 + + + + Parameter "{0}" cannot have zero length. + 参数“{0}”的长度不能为零。 + + + + Parameters "{0}" and "{1}" must have the same number of elements. + 参数“{0}”和“{1}”的元素数目必须相同。 + + + + The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. + 找不到“{1}”任务的资源字符串“{0}”。请确认资源名称“{0}”的拼写正确,并且任务的程序集中存在该资源。 + + + + The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. + 任务“{0}”尚未注册其资源。此任务需要在构造期间或通过“TaskResources”属性注册其资源,然后才能使用“TaskLoggingHelper.FormatResourceString()”方法。 + LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. + + + MSB5004: The solution file has two projects named "{0}". + MSB5004: 解决方案文件有两个名为“{0}”的项目。 + {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. + + + MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. + MSB5005: 分析项目“{0}”的项目节时出错。项目文件名“{1}”包含无效字符。 + {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. + + + MSB5006: Error parsing project section for project "{0}". The project file name is empty. + MSB5006: 分析项目“{0}”的项目节时出错。项目文件名为空。 + {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. + + + MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. + MSB5007: 分析解决方案文件中的项目配置节时出错。“{0}”项无效。 + {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. + + + MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. + MSB5008: 分析解决方案文件中的解决方案配置节时出错。“{0}”项无效。 + {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the nested project section in solution file. + MSB5009: 分析解决方案文件中的嵌套项目节时出错。 + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. + MSB5023: 分析解决方案文件中的嵌套项目节时出错。GUID 为“{0}”的项目列为嵌套在项目“{1}”下,但不存在于解决方案中。 + {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. + + + MSB5010: No file format header found. + MSB5010: 未找到文件格式头。 + {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. + + + MSB5011: Parent project GUID not found in "{0}" project dependency section. + MSB5011: 在“{0}”项目依赖项节中未找到父项目 GUID。 + {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. + + + MSB5012: Unexpected end-of-file reached inside "{0}" project section. + MSB5012: 在“{0}”项目节内部意外到达文件尾。 + {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. + + + MSB5013: Error parsing a project section. + MSB5013: 分析项目节时出错。 + {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. + + + MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. + MSB5014: 无法识别文件格式版本。MSBuild 只能读取版本 {0}.0 到 {1}.0 之间(包括这两个版本)的解决方案文件。 + {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. + + + MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. + MSB5015: 未能从“{0}”项目的 WebsiteProperties 节中读取属性。 + {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. + + + Unrecognized solution version "{0}", attempting to continue. + 无法识别解决方案版本“{0}”,正在尝试继续。 + + + + Solution file + 解决方案文件 + UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: + "MSBUILD : Solution file error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + MSB5019: The project file is malformed: "{0}". {1} + MSB5019: 项目文件格式不正确: “{0}”。{1} + {StrBegin="MSB5019: "} + + + MSB5020: Could not load the project file: "{0}". {1} + MSB5020: 未能加载项目文件: “{0}”。{1} + {StrBegin="MSB5020: "} + + + MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. + MSB5021: 终止任务可执行文件“{0}”及其子进程,因为已取消生成。 + {StrBegin="MSB5021: "} + + + This collection is read-only. + 此集合是只读的。 + + + + MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. + MSB5024: 无法确定到 MSBuild 的有效位置。请尝试从 Visual Studio 开发人员命令提示处运行此进程。 + {StrBegin="MSB5024: "} + + + MSB4233: There was an exception while reading the log file: {0} + MSB4233: 读取日志文件时出现异常: {0} + {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. + + + Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. + 分配有值“{1}”的参数“{0}”不可具有无效路径或无效的文件字符。 + + + + An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} + 使用 glob 展开 fileSpec 时发生异常: fileSpec:“{0}”,系统假定它是文件名。异常: {1} + + + + MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. + MSB5029: 文件“{3}”中元素 <{2}> 中“{1}”属性的值“{0}”是通配符,可导致枚举驱动器上的所有文件,这可能不是预期的行为。确认始终定义了引用的属性,并且项目和当前工作目录不在驱动器根目录下。 + {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its + attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.zh-Hant.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.zh-Hant.xlf new file mode 100644 index 00000000000..a7e8187bddb --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.zh-Hant.xlf @@ -0,0 +1,349 @@ + + + + + + MSB4188: Build was canceled. + MSB4188: 建置已取消。 + {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: MSBuild 工作主機不支援會執行 IBuildEngine 回撥的工作。若您想要執行這些作業,請改為在核心 MSBuild 處理序執行您的工作。若 UsingTask 已由 "Runtime" 或 "Architecture" 值賦予屬性,或工作引動過程已由 "MSBuildRuntime" 或 "MSBuildArchitecture" 值賦予屬性 (不符合 MSBuild 的目前執行階段或結構),則工作將自動在工作主機中執行。 + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + Build started. + 已經開始建置。 + + + + The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). + 集合中的元素數大於目標陣列中的可用空間 (從指定索引開始時)。 + + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: 已在 "{1}" 中發現工作組件 "{0}" 的衝突組件。 + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs + 不支援自定義事件類型 '{0}',因為所有自定義事件類型都已過時。請改用 Extended*EventArgs。更多資訊: https://aka.ms/msbuild/eventargs + + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + 事件類型 "{0}" 應該可以使用 .NET 序列化程式序列化。此事件不可序列化,已略過。 + + + + {0} ({1},{2}) + {0} ({1},{2}) + A file location to be embedded in a string. + + + MSB6005: Task attempted to log before it was initialized. Message was: {0} + MSB6005: 工作在初始化之前就嘗試記錄。訊息為: {0} + {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. + + + MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. + MSB3511: "{0}" 對 "Importance" 參數而言為無效值。有效值為: High、Normal 和 Low。 + {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. + The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown + LOCALIZATION: "Importance" should not be localized. + High should not be localized. + Normal should not be localized. + Low should not be localized. + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + 在套用到工作主機之前,對從父節點接收的環境進行下列修改: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + 將 '{0}' 設定為 '{1}' 而非父環境的值 '{2}。 + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + MSB4025: The project file could not be loaded. {0} + MSB4025: 無法載入專案檔。{0} + {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is + invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is + provided separately to loggers. + LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. + + + MSB4103: "{0}" is not a valid logger verbosity level. + MSB4103: "{0}" 不是有效的記錄器詳細程度等級。 + {StrBegin="MSB4103: "} + + + MSBuild is expecting a valid "{0}" object. + MSBuild 需要有效的 "{0}" 物件。 + + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + 路徑: {0} 超過 OS 路徑上限。完整檔案名稱必須少於 {1} 個字元。 + + + + MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". + MSB5028: 位於 "{0}" 的解決方案篩選檔案包含專案 "{1}",該專案不在位於 "{2}" 的解決方案檔案中。 + {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. + + + MSB5025: Json in solution filter file "{0}" is incorrectly formatted. + MSB5025: 解決方案篩選檔案 "{0}" 中的 Json 格式不正確。 + {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. + + + MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. + MSB5026: 位於 "{0}" 的解決方案篩選檔案指定將會有位於 "{1}" 的解決方案檔案,但該檔案並不存在。 + {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. + MSB5009: 剖析 GUID 為 "{1}" 的專案 "{0}" 區段時發生錯誤。其為 "{2}" 下方的巢狀專案,但在解決方案中找不到該專案。 + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. + MSB4132: 工具版本 "{0}" 無法辨認。可用的工具版本為 {1}。 + {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. + + + MSB5016: The name "{0}" contains an invalid character "{1}". + MSB5016: 名稱 "{0}" 包含無效字元 "{1}"。 + {StrBegin="MSB5016: "} + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" 是保留的項目中繼資料,不能修改或刪除。 + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The string "{0}" cannot be converted to a boolean (true/false) value. + 無法將字串 "{0}" 轉換成布林值 (true/false)。 + + + + MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + MSB5003: 無法建立暫存檔案。暫存檔案資料夾已滿或其路徑錯誤。{0} + {StrBegin="MSB5003: "} + + + MSB5018: Failed to delete the temporary file "{0}". {1} {2} + MSB5018: 無法刪除暫存檔 "{0}"。{1}{2} + {StrBegin="MSB5018: "} + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + 無法將項目中繼資料 "%({0})" 套用至路徑 "{1}"。{2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: "{0}" 工作已經以屬性 LoadInSeparateAppDomain 標記,但不是衍生自 MarshalByRefObject。請檢查工作是否衍生自 MarshalByRefObject 或 AppDomainIsolatedTask。 + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. + 不支援 .NET Framework 版本 "{0}"。請從列舉 Microsoft.Build.Utilities.TargetDotNetFrameworkVersion 中指定值。 + + + + .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. + 明確地以 Windows SDK 為目標時,不支援 .NET Framework 版本 "{0}",只有 .NET 4.5 及更新版本才予以支援。請從 Version45 (含) 以上版本的列舉 Microsoft.Build.Utilities.TargetDotNetFrameworkVersion 中指定值。 + + + + Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. + 不支援 Visual Studio 版本 "{0}"。請從列舉 Microsoft.Build.Utilities.VisualStudioVersion 中指定值。 + + + + When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} + 嘗試從路徑 "{0}" 和架構 Moniker "{1}" 產生參考組件路徑時,發生錯誤。{2} + No Error code because this resource will be used in an exception. The error code is discarded if it is included + + + Could not find directory path: {0} + 找不到目錄路徑: {0} + Directory must exist + + + You do not have access to: {0} + 您沒有存取權限: {0} + Directory must have access + + + Schema validation + 結構描述驗證 + + UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: + "MSBUILD : Schema validation error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + + MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. + MSB5002: 因為工作可執行檔 "{0}" 未於指定的 {1} 毫秒限制內完成,所以即將予以終止。 + {StrBegin="MSB5002: "} + + + Parameter "{0}" cannot be null. + 參數 "{0}" 不能為 null。 + + + + Parameter "{0}" cannot have zero length. + 參數 "{0}" 長度不能為零。 + + + + Parameters "{0}" and "{1}" must have the same number of elements. + 參數 "{0}" 和 "{1}" 的項目數目必須相同。 + + + + The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. + 找不到工作 "{1}" 的資源字串 "{0}"。請確認資源名稱 "{0}" 拼寫正確,且資源位於這項工作的組件中。 + + + + The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. + "{0}" 工作尚未註冊其資源。若要使用 "TaskLoggingHelper.FormatResourceString()" 方法,這項工作必須於建構期間或透過 "TaskResources" 屬性註冊其資源。 + LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. + + + MSB5004: The solution file has two projects named "{0}". + MSB5004: 方案檔有兩個名為 "{0}" 的專案。 + {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. + + + MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. + MSB5005: 剖析專案 "{0}" 的專案區段時發生錯誤。專案檔名稱 "{1}" 包含無效的字元。 + {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. + + + MSB5006: Error parsing project section for project "{0}". The project file name is empty. + MSB5006: 剖析專案 "{0}" 的專案區段時發生錯誤。專案檔名稱為空白。 + {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. + + + MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. + MSB5007: 剖析方案檔中的專案組態區段時發生錯誤。項目 "{0}" 無效。 + {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. + + + MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. + MSB5008: 剖析方案檔中的方案組態區段時發生錯誤。項目 "{0}" 無效。 + {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. + + + MSB5009: Error parsing the nested project section in solution file. + MSB5009: 剖析方案檔中的巢狀專案區段時發生錯誤。 + {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. + + + MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. + MSB5023: 剖析方案檔中的巢狀專案區段時發生錯誤。GUID 為 "{0}" 的專案列為專案 "{1}" 下的巢狀專案,但不存在於解決方案中。 + {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. + + + MSB5010: No file format header found. + MSB5010: 找不到檔案格式標頭。 + {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. + + + MSB5011: Parent project GUID not found in "{0}" project dependency section. + MSB5011: 在 "{0}" 專案相依性區段中找不到父專案 GUID。 + {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. + + + MSB5012: Unexpected end-of-file reached inside "{0}" project section. + MSB5012: 在 "{0}" 專案區段內部發現未預期的檔案結尾。 + {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. + + + MSB5013: Error parsing a project section. + MSB5013: 剖析專案區段時發生錯誤。 + {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. + + + MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. + MSB5014: 無法識別檔案格式版本。MSBuild 只能讀取版本 {0}.0 到 {1}.0 (含) 的方案檔。 + {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. + + + MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. + MSB5015: 無法從 "{0}" 專案的 WebsiteProperties 區段讀取屬性。 + {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. + + + Unrecognized solution version "{0}", attempting to continue. + 無法辨認的方案版本 "{0}",正在嘗試繼續處理。 + + + + Solution file + 方案檔 + UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is + displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: + "MSBUILD : Solution file error MSB0000: This is an error." + LOCALIZATION: This fragment needs to be localized. + + + MSB5019: The project file is malformed: "{0}". {1} + MSB5019: 專案檔格式不正確: "{0}"。{1} + {StrBegin="MSB5019: "} + + + MSB5020: Could not load the project file: "{0}". {1} + MSB5020: 無法載入專案檔: "{0}"。{1} + {StrBegin="MSB5020: "} + + + MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. + MSB5021: 因為建置已取消,所以即將終止工作可執行檔 "{0}" 及其子處理序。 + {StrBegin="MSB5021: "} + + + This collection is read-only. + 這是一個唯讀集合。 + + + + MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. + MSB5024: 無法判斷 MSBuild 的有效位置。請嘗試從 Visual Studio 的開發人員命令提示字元執行此處理序。 + {StrBegin="MSB5024: "} + + + MSB4233: There was an exception while reading the log file: {0} + MSB4233: 讀取記錄檔時發生錯誤: {0} + {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. + + + Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. + 指派值為 "{1}" 的參數 "{0}" 不得具有無效的路徑或檔案字元。 + + + + An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} + 展開具有 glob 的 fileSpec 時發生例外狀況: fileSpec: "{0}" (假設它是檔案名稱)。例外狀況: {1} + + + + MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. + MSB5029: 檔案「{3}」元素 <{2}> 中屬性「{1}」的值「{0}」是萬用字元,導致列舉磁碟機上的所有檔案,這很可能不是預期的結果。檢查參考的屬性是否一直定義,以及專案與目前的工作目錄是否不在磁碟機根目錄中。 + {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its + attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs b/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs new file mode 100644 index 00000000000..44b243ab0f0 --- /dev/null +++ b/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs @@ -0,0 +1,169 @@ +// 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.IO; +using System.Reflection; +using System.Diagnostics; + +#if FEATURE_ASSEMBLYLOADCONTEXT +using System.Runtime.Loader; +#endif +using Microsoft.Build.Shared; +using Microsoft.Build.Shared.FileSystem; + +#nullable disable + +namespace Microsoft.Build.BackEnd.Logging +{ + /// + /// This is a helper class to install an AssemblyResolver event handler in whatever AppDomain this class is created in. + /// + internal class TaskEngineAssemblyResolver +#if FEATURE_APPDOMAIN + : MarshalByRefObject +#endif + { + /// + /// This public default constructor is needed so that instances of this class can be created by NDP. + /// + internal TaskEngineAssemblyResolver() + { + // do nothing + } + + /// + /// Initializes the instance. + /// + /// + internal void Initialize(string taskAssemblyFileToResolve) + { + _taskAssemblyFile = taskAssemblyFileToResolve; + } + + /// + /// Installs an AssemblyResolve handler in the current AppDomain. This class can be created in any AppDomain, + /// so it's possible to create an AppDomain, create an instance of this class in it and use this method to install + /// an event handler in that AppDomain. Since the event handler instance is stored internally, this method + /// should only be called once before a corresponding call to RemoveHandler (not that it would make sense to do + /// anything else). + /// + internal void InstallHandler() + { +#if FEATURE_APPDOMAIN + Debug.Assert(_eventHandler == null, "The TaskEngineAssemblyResolver.InstallHandler method should only be called once!"); + + _eventHandler = new ResolveEventHandler(ResolveAssembly); + + AppDomain.CurrentDomain.AssemblyResolve += _eventHandler; +#else + _eventHandler = new Func(ResolveAssembly); + + AssemblyLoadContext.Default.Resolving += _eventHandler; +#endif + } + + + + /// + /// Removes the event handler. + /// + internal void RemoveHandler() + { + if (_eventHandler != null) + { +#if FEATURE_APPDOMAIN + AppDomain.CurrentDomain.AssemblyResolve -= _eventHandler; +#else + AssemblyLoadContext.Default.Resolving -= _eventHandler; +#endif + _eventHandler = null; + } + else + { + Debug.Assert(false, "There is no handler to remove."); + } + } + + +#if FEATURE_APPDOMAIN + /// + /// This is an assembly resolution handler necessary for fixing up types instantiated in different + /// AppDomains and loaded with a Assembly.LoadFrom equivalent call. See comments in TaskEngine.ExecuteTask + /// for more details. + /// + /// + /// + /// + internal Assembly ResolveAssembly(object sender, ResolveEventArgs args) +#else + private Assembly ResolveAssembly(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName) +#endif + { + // Is this our task assembly? + if (_taskAssemblyFile != null) + { + if (FileSystems.Default.FileExists(_taskAssemblyFile)) + { + try + { +#if FEATURE_APPDOMAIN + AssemblyNameExtension taskAssemblyName = new AssemblyNameExtension(AssemblyName.GetAssemblyName(_taskAssemblyFile)); + AssemblyNameExtension argAssemblyName = new AssemblyNameExtension(args.Name); + + if (taskAssemblyName.Equals(argAssemblyName)) + { +#if (!CLR2COMPATIBILITY) + return Assembly.UnsafeLoadFrom(_taskAssemblyFile); +#else + return Assembly.LoadFrom(_taskAssemblyFile); +#endif + + } +#else // !FEATURE_APPDOMAIN + AssemblyNameExtension taskAssemblyName = new AssemblyNameExtension(AssemblyLoadContext.GetAssemblyName(_taskAssemblyFile)); + AssemblyNameExtension argAssemblyName = new AssemblyNameExtension(assemblyName); + if (taskAssemblyName.Equals(argAssemblyName)) + { + return AssemblyLoadContext.Default.LoadFromAssemblyPath(_taskAssemblyFile); + } +#endif + } + // any problems with the task assembly? return null. + catch (FileNotFoundException) + { + return null; + } + catch (BadImageFormatException) + { + return null; + } + } + } + + // otherwise, have a nice day. + return null; + } + +#if FEATURE_APPDOMAIN + /// + /// Overridden to give this class infinite lease time. Otherwise we end up with a limited + /// lease (5 minutes I think) and instances can expire if they take long time processing. + /// + [System.Security.SecurityCritical] + public override object InitializeLifetimeService() + { + // null means infinite lease time + return null; + } + + + // we have to store the event handler instance in case we have to remove it + private ResolveEventHandler _eventHandler = null; +#else + private Func _eventHandler = null; +#endif + // path to the task assembly, but only if it's loaded using LoadFrom. If it's loaded with Load, this is null. + private string _taskAssemblyFile = null; + } +} diff --git a/src/MSBuildTaskHost/TaskHostConfiguration.cs b/src/MSBuildTaskHost/TaskHostConfiguration.cs new file mode 100644 index 00000000000..ddf1d1c1a5d --- /dev/null +++ b/src/MSBuildTaskHost/TaskHostConfiguration.cs @@ -0,0 +1,557 @@ +// 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; +using System.Diagnostics; +using System.Globalization; +using Microsoft.Build.Execution; +using Microsoft.Build.Shared; + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + /// + /// TaskHostConfiguration contains information needed for the task host to + /// configure itself for to execute a particular task. + /// + internal class TaskHostConfiguration : INodePacket + { + /// + /// The node id (of the parent node, to make the logging work out) + /// + private int _nodeId; + + /// + /// The startup directory + /// + private string _startupDirectory; + + /// + /// The process environment. + /// + private Dictionary _buildProcessEnvironment; + + /// + /// The culture + /// + private CultureInfo _culture = CultureInfo.CurrentCulture; + + /// + /// The UI culture. + /// + private CultureInfo _uiCulture = CultureInfo.CurrentUICulture; + +#if FEATURE_APPDOMAIN + /// + /// The AppDomainSetup that we may want to use on AppDomainIsolated tasks. + /// + private AppDomainSetup _appDomainSetup; +#endif + + /// + /// Line number where the instance of this task is defined. + /// + private int _lineNumberOfTask; + + /// + /// Column number where the instance of this task is defined. + /// + private int _columnNumberOfTask; + + /// + /// Project file where the instance of this task is defined. + /// + private string _projectFileOfTask; + + /// + /// ContinueOnError flag for this particular task. + /// + private bool _continueOnError; + + /// + /// Name of the task to be executed on the task host. + /// + private string _taskName; + + /// + /// Location of the assembly containing the task to be executed. + /// + private string _taskLocation; + + /// + /// Whether task inputs are logged. + /// + private bool _isTaskInputLoggingEnabled; + + /// + /// Target name that is requesting the task execution. + /// + private string _targetName; + + /// + /// Project file path that is requesting the task execution. + /// + private string _projectFile; + +#if !NET35 + private HostServices _hostServices; +#endif + + /// + /// The set of parameters to apply to the task prior to execution. + /// + private Dictionary _taskParameters; + + private Dictionary _globalParameters; + + private ICollection _warningsAsErrors; + private ICollection _warningsNotAsErrors; + + private ICollection _warningsAsMessages; + +#if FEATURE_APPDOMAIN + /// + /// Initializes a new instance of the class. + /// + /// The ID of the node being configured. + /// The startup directory for the task being executed. + /// The set of environment variables to apply to the task execution process. + /// The culture of the thread that will execute the task. + /// The UI culture of the thread that will execute the task. + /// The host services to be used by the task host. + /// The AppDomainSetup that may be used to pass information to an AppDomainIsolated task. + /// The line number of the location from which this task was invoked. + /// The column number of the location from which this task was invoked. + /// The project file from which this task was invoked. + /// A flag to indicate whether to continue with the build after the task fails. + /// The name of the task. + /// The location of the assembly from which the task is to be loaded. + /// The name of the target that is requesting the task execution. + /// The project path that invokes the task. + /// A flag to indicate whether task inputs are logged. + /// The parameters to apply to the task. + /// The global properties for the current project. + /// A collection of warning codes to be treated as errors. + /// A collection of warning codes not to be treated as errors. + /// A collection of warning codes to be treated as messages. +#else + /// + /// Initializes a new instance of the class. + /// + /// The ID of the node being configured. + /// The startup directory for the task being executed. + /// The set of environment variables to apply to the task execution process. + /// The culture of the thread that will execute the task. + /// The UI culture of the thread that will execute the task. + /// The host services to be used by the task host. + /// The line number of the location from which this task was invoked. + /// The column number of the location from which this task was invoked. + /// The project file from which this task was invoked. + /// A flag to indicate whether to continue with the build after the task fails. + /// The name of the task. + /// The location of the assembly from which the task is to be loaded. + /// The name of the target that is requesting the task execution. + /// The project path that invokes the task. + /// A flag to indicate whether task inputs are logged. + /// The parameters to apply to the task. + /// The global properties for the current project. + /// A collection of warning codes to be treated as errors. + /// A collection of warning codes not to be treated as errors. + /// A collection of warning codes to be treated as messages. +#endif + public TaskHostConfiguration( + int nodeId, + string startupDirectory, + IDictionary buildProcessEnvironment, + CultureInfo culture, + CultureInfo uiCulture, +#if !NET35 + HostServices hostServices, +#endif +#if FEATURE_APPDOMAIN + AppDomainSetup appDomainSetup, +#endif + int lineNumberOfTask, + int columnNumberOfTask, + string projectFileOfTask, + bool continueOnError, + string taskName, + string taskLocation, + string targetName, + string projectFile, + bool isTaskInputLoggingEnabled, + IDictionary taskParameters, + Dictionary globalParameters, + ICollection warningsAsErrors, + ICollection warningsNotAsErrors, + ICollection warningsAsMessages) + { + ErrorUtilities.VerifyThrowInternalLength(taskName, nameof(taskName)); + ErrorUtilities.VerifyThrowInternalLength(taskLocation, nameof(taskLocation)); + + _nodeId = nodeId; + _startupDirectory = startupDirectory; + + if (buildProcessEnvironment != null) + { + _buildProcessEnvironment = buildProcessEnvironment as Dictionary; + + if (_buildProcessEnvironment == null) + { + _buildProcessEnvironment = new Dictionary(buildProcessEnvironment); + } + } + + _culture = culture; + _uiCulture = uiCulture; +#if !NET35 + _hostServices = hostServices; +#endif +#if FEATURE_APPDOMAIN + _appDomainSetup = appDomainSetup; +#endif + _lineNumberOfTask = lineNumberOfTask; + _columnNumberOfTask = columnNumberOfTask; + _projectFileOfTask = projectFileOfTask; + _projectFile = projectFile; + _continueOnError = continueOnError; + _taskName = taskName; + _taskLocation = taskLocation; + _targetName = targetName; + _isTaskInputLoggingEnabled = isTaskInputLoggingEnabled; + _warningsAsErrors = warningsAsErrors; + _warningsNotAsErrors = warningsNotAsErrors; + _warningsAsMessages = warningsAsMessages; + + if (taskParameters != null) + { + _taskParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (KeyValuePair parameter in taskParameters) + { + _taskParameters[parameter.Key] = new TaskParameter(parameter.Value); + } + } + + _globalParameters = globalParameters ?? new Dictionary(); + } + + /// + /// Constructor for deserialization. + /// + private TaskHostConfiguration() + { + } + + /// + /// The node id + /// + public int NodeId + { + [DebuggerStepThrough] + get + { return _nodeId; } + } + + /// + /// The startup directory + /// + public string StartupDirectory + { + [DebuggerStepThrough] + get + { return _startupDirectory; } + } + + /// + /// The process environment. + /// + public Dictionary BuildProcessEnvironment + { + [DebuggerStepThrough] + get + { return _buildProcessEnvironment; } + } + + /// + /// The culture + /// + public CultureInfo Culture + { + [DebuggerStepThrough] + get + { return _culture; } + } + + /// + /// The UI culture. + /// + public CultureInfo UICulture + { + [DebuggerStepThrough] + get + { return _uiCulture; } + } + +#if FEATURE_APPDOMAIN + /// + /// The AppDomain configuration bytes that we may want to use to initialize + /// AppDomainIsolated tasks. + /// + public AppDomainSetup AppDomainSetup + { + [DebuggerStepThrough] + get + { return _appDomainSetup; } + } +#endif + +#if !NET35 + /// + /// The HostServices to be used by the task host. + /// + public HostServices HostServices + { + [DebuggerStepThrough] + get + { return _hostServices; } + } +#endif + + /// + /// Line number where the instance of this task is defined. + /// + public int LineNumberOfTask + { + [DebuggerStepThrough] + get + { return _lineNumberOfTask; } + } + + /// + /// Column number where the instance of this task is defined. + /// + public int ColumnNumberOfTask + { + [DebuggerStepThrough] + get + { return _columnNumberOfTask; } + } + + /// + /// Project file path that is requesting the task execution. + /// + public string ProjectFile + { + [DebuggerStepThrough] + get + { return _projectFile; } + } + + /// + /// Target name that is requesting the task execution. + /// + public string TargetName + { + [DebuggerStepThrough] + get + { return _targetName; } + } + + /// + /// ContinueOnError flag for this particular task + /// + public bool ContinueOnError + { + [DebuggerStepThrough] + get + { return _continueOnError; } + } + + /// + /// Project file where the instance of this task is defined. + /// + public string ProjectFileOfTask + { + [DebuggerStepThrough] + get + { return _projectFileOfTask; } + } + + /// + /// Name of the task to execute. + /// + public string TaskName + { + [DebuggerStepThrough] + get + { return _taskName; } + } + + /// + /// Path to the assembly to load the task from. + /// + public string TaskLocation + { + [DebuggerStepThrough] + get + { return _taskLocation; } + } + + /// + /// Returns if the build is configured to log all task inputs. + /// + public bool IsTaskInputLoggingEnabled + { + [DebuggerStepThrough] + get + { return _isTaskInputLoggingEnabled; } + } + + /// + /// Parameters to set on the instantiated task prior to execution. + /// + public Dictionary TaskParameters + { + [DebuggerStepThrough] + get + { return _taskParameters; } + } + + /// + /// Gets the global properties for the current project. + /// + public Dictionary GlobalProperties + { + [DebuggerStepThrough] + get + { return _globalParameters; } + } + + /// + /// The NodePacketType of this NodePacket + /// + public NodePacketType Type + { + [DebuggerStepThrough] + get + { return NodePacketType.TaskHostConfiguration; } + } + + public ICollection WarningsAsErrors + { + [DebuggerStepThrough] + get + { + return _warningsAsErrors; + } + } + + public ICollection WarningsNotAsErrors + { + [DebuggerStepThrough] + get + { + return _warningsNotAsErrors; + } + } + + public ICollection WarningsAsMessages + { + [DebuggerStepThrough] + get + { + return _warningsAsMessages; + } + } + + /// + /// Translates the packet to/from binary form. + /// + /// The translator to use. + public void Translate(ITranslator translator) + { + translator.Translate(ref _nodeId); + translator.Translate(ref _startupDirectory); + translator.TranslateDictionary(ref _buildProcessEnvironment, StringComparer.OrdinalIgnoreCase); + translator.TranslateCulture(ref _culture); + translator.TranslateCulture(ref _uiCulture); +#if FEATURE_APPDOMAIN + // The packet version is used to determine if the AppDomain configuration should be serialized. + // If the packet version is bigger then 0, it means the task host will running under .NET. + // Although MSBuild.exe runs under .NET Framework and has AppDomain support, + // we don't transmit AppDomain config when communicating with dotnet.exe (it is not supported in .NET 5+). + if (translator.NegotiatedPacketVersion == 0) + { + byte[] appDomainConfigBytes = null; + + // Set the configuration bytes just before serialization in case the SetConfigurationBytes was invoked during lifetime of this instance. + if (translator.Mode == TranslationDirection.WriteToStream) + { + appDomainConfigBytes = _appDomainSetup?.GetConfigurationBytes(); + } + + translator.Translate(ref appDomainConfigBytes); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + _appDomainSetup = new AppDomainSetup(); + _appDomainSetup.SetConfigurationBytes(appDomainConfigBytes); + } + } +#endif + translator.Translate(ref _lineNumberOfTask); + translator.Translate(ref _columnNumberOfTask); + translator.Translate(ref _projectFileOfTask); + translator.Translate(ref _taskName); + translator.Translate(ref _taskLocation); + if (translator.NegotiatedPacketVersion >= 2) + { + translator.Translate(ref _targetName); + translator.Translate(ref _projectFile); +#if !NET35 + translator.Translate(ref _hostServices); +#endif + } + + translator.Translate(ref _isTaskInputLoggingEnabled); + translator.TranslateDictionary(ref _taskParameters, StringComparer.OrdinalIgnoreCase, TaskParameter.FactoryForDeserialization); + translator.Translate(ref _continueOnError); + translator.TranslateDictionary(ref _globalParameters, StringComparer.OrdinalIgnoreCase); + translator.Translate(collection: ref _warningsAsErrors, + objectTranslator: (ITranslator t, ref string s) => t.Translate(ref s), +#if CLR2COMPATIBILITY + collectionFactory: count => new HashSet(StringComparer.OrdinalIgnoreCase)); +#else + collectionFactory: count => new HashSet(count, StringComparer.OrdinalIgnoreCase)); +#endif + translator.Translate(collection: ref _warningsNotAsErrors, + objectTranslator: (ITranslator t, ref string s) => t.Translate(ref s), +#if CLR2COMPATIBILITY + collectionFactory: count => new HashSet(StringComparer.OrdinalIgnoreCase)); +#else + collectionFactory: count => new HashSet(count, StringComparer.OrdinalIgnoreCase)); +#endif + translator.Translate(collection: ref _warningsAsMessages, + objectTranslator: (ITranslator t, ref string s) => t.Translate(ref s), +#if CLR2COMPATIBILITY + collectionFactory: count => new HashSet(StringComparer.OrdinalIgnoreCase)); +#else + collectionFactory: count => new HashSet(count, StringComparer.OrdinalIgnoreCase)); +#endif + } + + /// + /// Factory for deserialization. + /// + internal static INodePacket FactoryForDeserialization(ITranslator translator) + { + TaskHostConfiguration configuration = new TaskHostConfiguration(); + configuration.Translate(translator); + + return configuration; + } + } +} diff --git a/src/MSBuildTaskHost/TaskHostTaskCancelled.cs b/src/MSBuildTaskHost/TaskHostTaskCancelled.cs new file mode 100644 index 00000000000..f56e61a34d9 --- /dev/null +++ b/src/MSBuildTaskHost/TaskHostTaskCancelled.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + /// + /// TaskHostTaskCancelled informs the task host that the task it is + /// currently executing has been canceled. + /// + internal class TaskHostTaskCancelled : INodePacket + { + /// + /// Constructor + /// + public TaskHostTaskCancelled() + { + } + + /// + /// The type of this NodePacket + /// + public NodePacketType Type + { + get { return NodePacketType.TaskHostTaskCancelled; } + } + + /// + /// Translates the packet to/from binary form. + /// + /// The translator to use. + public void Translate(ITranslator translator) + { + // Do nothing -- this packet doesn't contain any parameters. + } + + /// + /// Factory for deserialization. + /// + internal static INodePacket FactoryForDeserialization(ITranslator translator) + { + TaskHostTaskCancelled taskCancelled = new TaskHostTaskCancelled(); + taskCancelled.Translate(translator); + return taskCancelled; + } + } +} diff --git a/src/MSBuildTaskHost/TaskHostTaskComplete.cs b/src/MSBuildTaskHost/TaskHostTaskComplete.cs new file mode 100644 index 00000000000..6bded722522 --- /dev/null +++ b/src/MSBuildTaskHost/TaskHostTaskComplete.cs @@ -0,0 +1,265 @@ +// 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; +using System.Diagnostics; +#if !CLR2COMPATIBILITY && FEATURE_REPORTFILEACCESSES +using Microsoft.Build.Experimental.FileAccess; +#endif +using Microsoft.Build.Shared; + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + /// + /// How the task completed -- successful, failed, or crashed + /// + internal enum TaskCompleteType + { + /// + /// Task execution succeeded + /// + Success, + + /// + /// Task execution failed + /// + Failure, + + /// + /// Task crashed during initialization steps -- loading the task, + /// validating or setting the parameters, etc. + /// + CrashedDuringInitialization, + + /// + /// Task crashed while being executed + /// + CrashedDuringExecution, + + /// + /// Task crashed after being executed + /// -- Getting outputs, etc + /// + CrashedAfterExecution + } + + /// + /// TaskHostTaskComplete contains all the information the parent node + /// needs from the task host on completion of task execution. + /// + internal class TaskHostTaskComplete : INodePacket + { +#if FEATURE_REPORTFILEACCESSES + private List _fileAccessData; +#endif + + /// + /// Result of the task's execution. + /// + private TaskCompleteType _taskResult; + + /// + /// If the task threw an exception during its initialization or execution, + /// save it here. + /// + private Exception _taskException; + + /// + /// If there's an additional message that should be attached to the error + /// logged beyond "task X failed unexpectedly", save it here. May be null. + /// + private string _taskExceptionMessage; + + /// + /// If the message saved in taskExceptionMessage requires arguments, save + /// them here. May be null. + /// + private string[] _taskExceptionMessageArgs; + + /// + /// The set of parameters / values from the task after it finishes execution. + /// + private Dictionary _taskOutputParameters = null; + + /// + /// The process environment at the end of task execution. + /// + private Dictionary _buildProcessEnvironment = null; + + +#pragma warning disable CS1572 // XML comment has a param tag, but there is no parameter by that name. Justification: xmldoc doesn't seem to interact well with #ifdef of params. + /// + /// Initializes a new instance of the class. + /// + /// The result of the task's execution. + /// The file accesses reported by the task. + /// The build process environment as it was at the end of the task's execution. +#pragma warning restore CS1572 // XML comment has a param tag, but there is no parameter by that name + public TaskHostTaskComplete( + OutOfProcTaskHostTaskResult result, +#if FEATURE_REPORTFILEACCESSES + List fileAccessData, +#endif + IDictionary buildProcessEnvironment) + { + ErrorUtilities.VerifyThrowInternalNull(result); + + _taskResult = result.Result; + _taskException = result.TaskException; + _taskExceptionMessage = result.ExceptionMessage; + _taskExceptionMessageArgs = result.ExceptionMessageArgs; +#if FEATURE_REPORTFILEACCESSES + _fileAccessData = fileAccessData; +#endif + + if (result.FinalParameterValues != null) + { + _taskOutputParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (KeyValuePair parameter in result.FinalParameterValues) + { + _taskOutputParameters[parameter.Key] = new TaskParameter(parameter.Value); + } + } + + if (buildProcessEnvironment != null) + { + _buildProcessEnvironment = buildProcessEnvironment as Dictionary; + + if (_buildProcessEnvironment == null) + { + _buildProcessEnvironment = new Dictionary(buildProcessEnvironment); + } + } + } + + /// + /// For deserialization. + /// + private TaskHostTaskComplete() + { + } + + /// + /// Result of the task's execution. + /// + public TaskCompleteType TaskResult + { + [DebuggerStepThrough] + get + { return _taskResult; } + } + + /// + /// If the task threw an exception during its initialization or execution, + /// save it here. + /// + public Exception TaskException + { + [DebuggerStepThrough] + get + { return _taskException; } + } + + /// + /// If there's an additional message that should be attached to the error + /// logged beyond "task X failed unexpectedly", put it here. May be null. + /// + public string TaskExceptionMessage + { + [DebuggerStepThrough] + get + { return _taskExceptionMessage; } + } + + /// + /// If there are arguments that need to be formatted into the message being + /// sent, set them here. May be null. + /// + public string[] TaskExceptionMessageArgs + { + [DebuggerStepThrough] + get + { return _taskExceptionMessageArgs; } + } + + /// + /// Task parameters and their values after the task has finished. + /// + public Dictionary TaskOutputParameters + { + [DebuggerStepThrough] + get + { + if (_taskOutputParameters == null) + { + _taskOutputParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + return _taskOutputParameters; + } + } + + /// + /// The process environment. + /// + public Dictionary BuildProcessEnvironment + { + [DebuggerStepThrough] + get + { return _buildProcessEnvironment; } + } + + /// + /// The type of this packet. + /// + public NodePacketType Type + { + get { return NodePacketType.TaskHostTaskComplete; } + } + +#if FEATURE_REPORTFILEACCESSES + /// + /// Gets the file accesses reported by the task. + /// + public List FileAccessData + { + [DebuggerStepThrough] + get => _fileAccessData; + } +#endif + + /// + /// Translates the packet to/from binary form. + /// + /// The translator to use. + public void Translate(ITranslator translator) + { + translator.TranslateEnum(ref _taskResult, (int)_taskResult); + translator.TranslateException(ref _taskException); + translator.Translate(ref _taskExceptionMessage); + translator.Translate(ref _taskExceptionMessageArgs); + translator.TranslateDictionary(ref _taskOutputParameters, StringComparer.OrdinalIgnoreCase, TaskParameter.FactoryForDeserialization); + translator.TranslateDictionary(ref _buildProcessEnvironment, StringComparer.OrdinalIgnoreCase); +#if FEATURE_REPORTFILEACCESSES + translator.Translate(ref _fileAccessData, + (ITranslator translator, ref FileAccessData data) => ((ITranslatable)data).Translate(translator)); +#else + bool hasFileAccessData = false; + translator.Translate(ref hasFileAccessData); +#endif + } + + /// + /// Factory for deserialization. + /// + internal static INodePacket FactoryForDeserialization(ITranslator translator) + { + TaskHostTaskComplete taskComplete = new TaskHostTaskComplete(); + taskComplete.Translate(translator); + return taskComplete; + } + } +} diff --git a/src/MSBuildTaskHost/TaskLoader.cs b/src/MSBuildTaskHost/TaskLoader.cs new file mode 100644 index 00000000000..602a36871ed --- /dev/null +++ b/src/MSBuildTaskHost/TaskLoader.cs @@ -0,0 +1,207 @@ +// 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.Reflection; +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Shared +{ + /// + /// Class for loading tasks + /// + internal static class TaskLoader + { +#if FEATURE_APPDOMAIN + /// + /// For saving the assembly that was loaded by the TypeLoader + /// We only use this when the assembly failed to load properly into the appdomain + /// + private static LoadedType? s_resolverLoadedType; +#endif + + /// + /// Delegate for logging task loading errors. + /// + internal delegate void LogError(string taskLocation, int taskLine, int taskColumn, string message, params object[] messageArgs); + + /// + /// Checks if the given type is a task factory. + /// + /// This method is used as a type filter delegate. + /// true, if specified type is a task + internal static bool IsTaskClass(Type type, object unused) + { + return type.GetTypeInfo().IsClass && !type.GetTypeInfo().IsAbstract && ( + type.GetTypeInfo().GetInterface("Microsoft.Build.Framework.ITask") != null); + } + + /// + /// Creates an ITask instance and returns it. + /// +#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter + internal static ITask? CreateTask( + LoadedType loadedType, + string taskName, + string taskLocation, + int taskLine, + int taskColumn, + LogError logError, +#if FEATURE_APPDOMAIN + AppDomainSetup appDomainSetup, + Action appDomainCreated, +#endif + bool isOutOfProc +#if FEATURE_APPDOMAIN + , out AppDomain? taskAppDomain +#endif + ) +#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter + { +#if FEATURE_APPDOMAIN + bool separateAppDomain = loadedType.HasLoadInSeparateAppDomainAttribute; + s_resolverLoadedType = null; + taskAppDomain = null; + ITask? taskInstanceInOtherAppDomain = null; +#endif + + try + { +#if FEATURE_APPDOMAIN + if (separateAppDomain) + { + if (!loadedType.IsMarshalByRef) + { + logError( + taskLocation, + taskLine, + taskColumn, + "TaskNotMarshalByRef", + taskName); + + return null; + } + else + { + // Our task depend on this name to be precisely that, so if you change it make sure + // you also change the checks in the tasks run in separate AppDomains. Better yet, just don't change it. + + // Make sure we copy the appdomain configuration and send it to the appdomain we create so that if the creator of the current appdomain + // has done the binding redirection in code, that we will get those settings as well. + AppDomainSetup appDomainInfo = new AppDomainSetup(); + + // Get the current app domain setup settings + byte[] currentAppdomainBytes = appDomainSetup.GetConfigurationBytes(); + + // Apply the appdomain settings to the new appdomain before creating it + appDomainInfo.SetConfigurationBytes(currentAppdomainBytes); + + if (BuildEnvironmentHelper.Instance.RunningTests) + { + // Prevent the new app domain from looking in the VS test runner location. If this + // is not done, we will not be able to find Microsoft.Build.* assemblies. + appDomainInfo.ApplicationBase = BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory; + appDomainInfo.ConfigurationFile = BuildEnvironmentHelper.Instance.CurrentMSBuildConfigurationFile; + } + + AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolver; + s_resolverLoadedType = loadedType; + + taskAppDomain = AppDomain.CreateDomain(isOutOfProc ? "taskAppDomain (out-of-proc)" : "taskAppDomain (in-proc)", null, appDomainInfo); + + if (loadedType.LoadedAssembly != null) + { + taskAppDomain.Load(loadedType.LoadedAssemblyName); + } + + // Hook up last minute dumping of any exceptions + taskAppDomain.UnhandledException += ExceptionHandling.UnhandledExceptionHandler; + appDomainCreated?.Invoke(taskAppDomain); + } + } + else +#endif + { + // perf improvement for the same appdomain case - we already have the type object + // and don't want to go through reflection to recreate it from the name. + return (ITask?)Activator.CreateInstance(loadedType.Type); + } + +#if FEATURE_APPDOMAIN + if (loadedType.Assembly.AssemblyFile != null) + { + taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceFromAndUnwrap(loadedType.Assembly.AssemblyFile, loadedType.Type.FullName); + + // this will force evaluation of the task class type and try to load the task assembly + Type taskType = taskInstanceInOtherAppDomain.GetType(); + + // If the types don't match, we have a problem. It means that our AppDomain was able to load + // a task assembly using Load, and loaded a different one. I don't see any other choice than + // to fail here. + if (taskType != loadedType.Type) + { + logError( + taskLocation, + taskLine, + taskColumn, + "ConflictingTaskAssembly", + loadedType.Assembly.AssemblyFile, + loadedType.Type.GetTypeInfo().Assembly.Location); + + taskInstanceInOtherAppDomain = null; + } + } + else + { + taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceAndUnwrap(loadedType.Type.GetTypeInfo().Assembly.FullName, loadedType.Type.FullName); + } + + return taskInstanceInOtherAppDomain; +#endif + } + finally + { +#if FEATURE_APPDOMAIN + // Don't leave appdomains open + if (taskAppDomain != null && taskInstanceInOtherAppDomain == null) + { + AppDomain.Unload(taskAppDomain); + RemoveAssemblyResolver(); + } +#endif + } + } + +#if FEATURE_APPDOMAIN + /// + /// This is a resolver to help created AppDomains when they are unable to load an assembly into their domain we will help + /// them succeed by providing the already loaded one in the currentdomain so that they can derive AssemblyName info from it + /// + internal static Assembly? AssemblyResolver(object sender, ResolveEventArgs args) + { + if (args.Name.Equals(s_resolverLoadedType?.LoadedAssemblyName?.FullName, StringComparison.OrdinalIgnoreCase)) + { + if (s_resolverLoadedType == null || s_resolverLoadedType.Path == null) + { + return null; + } + return s_resolverLoadedType.LoadedAssembly ?? Assembly.Load(s_resolverLoadedType.Path); + } + + return null; + } + + /// + /// Check if we added a resolver and remove it + /// + internal static void RemoveAssemblyResolver() + { + if (s_resolverLoadedType != null) + { + AppDomain.CurrentDomain.AssemblyResolve -= AssemblyResolver; + s_resolverLoadedType = null; + } + } +#endif + } +} diff --git a/src/MSBuildTaskHost/TaskParameter.cs b/src/MSBuildTaskHost/TaskParameter.cs new file mode 100644 index 00000000000..6c54e0e8f06 --- /dev/null +++ b/src/MSBuildTaskHost/TaskParameter.cs @@ -0,0 +1,948 @@ +// 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; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using Microsoft.Build.Collections; + +#if FEATURE_APPDOMAIN +using System.Runtime.Remoting; +using System.Security; +#endif +using Microsoft.Build.Framework; +using Microsoft.Build.Shared; + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + /// + /// Type of parameter, used to figure out how to serialize it. + /// + internal enum TaskParameterType + { + /// + /// Parameter is null. + /// + Null, + + /// + /// Parameter is of a type described by a . + /// + PrimitiveType, + + /// + /// Parameter is an array of a type described by a . + /// + PrimitiveTypeArray, + + /// + /// Parameter is a value type. Note: Must be . + /// + ValueType, + + /// + /// Parameter is an array of value types. Note: Must be . + /// + ValueTypeArray, + + /// + /// Parameter is an ITaskItem. + /// + ITaskItem, + + /// + /// Parameter is an array of ITaskItems. + /// + ITaskItemArray, + + /// + /// An invalid parameter -- the value of this parameter contains the exception + /// that is thrown when trying to access it. + /// + Invalid, + } + + /// + /// Wrapper for task parameters, to allow proper serialization even + /// in cases where the parameter is not .NET serializable. + /// + internal class TaskParameter : +#if FEATURE_APPDOMAIN + MarshalByRefObject, +#endif + ITranslatable + { + /// + /// The TaskParameterType of the wrapped parameter. + /// + private TaskParameterType _parameterType; + + /// + /// The of the wrapped parameter if it's a primitive type. + /// + private TypeCode _parameterTypeCode; + + /// + /// The actual task parameter that we're wrapping + /// + private object _wrappedParameter; + + /// + /// Create a new TaskParameter + /// + public TaskParameter(object wrappedParameter) + { + if (wrappedParameter == null) + { + _parameterType = TaskParameterType.Null; + _wrappedParameter = null; + return; + } + + Type wrappedParameterType = wrappedParameter.GetType(); + + if (wrappedParameter is Exception) + { + _parameterType = TaskParameterType.Invalid; + _wrappedParameter = wrappedParameter; + return; + } + + // It's not null or invalid, so it should be a valid parameter type. + ErrorUtilities.VerifyThrow( + TaskParameterTypeVerifier.IsValidInputParameter(wrappedParameterType) || TaskParameterTypeVerifier.IsValidOutputParameter(wrappedParameterType), + "How did we manage to get a task parameter of type {0} that isn't a valid parameter type?", + wrappedParameterType); + + if (wrappedParameterType.IsArray) + { + TypeCode typeCode = Type.GetTypeCode(wrappedParameterType.GetElementType()); + if (typeCode != TypeCode.Object && typeCode != TypeCode.DBNull) + { + _parameterType = TaskParameterType.PrimitiveTypeArray; + _parameterTypeCode = typeCode; + _wrappedParameter = wrappedParameter; + } + else if (typeof(ITaskItem[]).GetTypeInfo().IsAssignableFrom(wrappedParameterType.GetTypeInfo())) + { + _parameterType = TaskParameterType.ITaskItemArray; + ITaskItem[] inputAsITaskItemArray = (ITaskItem[])wrappedParameter; + ITaskItem[] taskItemArrayParameter = new ITaskItem[inputAsITaskItemArray.Length]; + + for (int i = 0; i < inputAsITaskItemArray.Length; i++) + { + if (inputAsITaskItemArray[i] != null) + { + taskItemArrayParameter[i] = new TaskParameterTaskItem(inputAsITaskItemArray[i]); + } + } + + _wrappedParameter = taskItemArrayParameter; + } + else if (wrappedParameterType.GetElementType().GetTypeInfo().IsValueType) + { + _parameterType = TaskParameterType.ValueTypeArray; + _wrappedParameter = wrappedParameter; + } + else + { + ErrorUtilities.ThrowInternalErrorUnreachable(); + } + } + else + { + // scalar parameter + // Preserve enums as strings: the enum type itself may not + // be loaded on the other side of the serialization, but + // we would convert to string anyway after pulling the + // task output into a property or item. + if (wrappedParameterType.IsEnum) + { + wrappedParameter = (string)Convert.ChangeType(wrappedParameter, typeof(string), CultureInfo.InvariantCulture); + wrappedParameterType = typeof(string); + } + + TypeCode typeCode = Type.GetTypeCode(wrappedParameterType); + if (typeCode != TypeCode.Object && typeCode != TypeCode.DBNull) + { + _parameterType = TaskParameterType.PrimitiveType; + _parameterTypeCode = typeCode; + _wrappedParameter = wrappedParameter; + } + else if (typeof(ITaskItem).IsAssignableFrom(wrappedParameterType)) + { + _parameterType = TaskParameterType.ITaskItem; + _wrappedParameter = new TaskParameterTaskItem((ITaskItem)wrappedParameter); + } + else if (wrappedParameterType.GetTypeInfo().IsValueType) + { + _parameterType = TaskParameterType.ValueType; + _wrappedParameter = wrappedParameter; + } + else + { + ErrorUtilities.ThrowInternalErrorUnreachable(); + } + } + } + + /// + /// Constructor for deserialization. + /// + private TaskParameter() + { + } + + /// + /// The TaskParameterType of the wrapped parameter. + /// + public TaskParameterType ParameterType => _parameterType; + + /// + /// The of the wrapper parameter if it's a primitive or array of primitives. + /// + public TypeCode ParameterTypeCode => _parameterTypeCode; + + /// + /// The actual task parameter that we're wrapping. + /// + public object WrappedParameter => _wrappedParameter; + + /// + /// TaskParameter's ToString should just pass through to whatever it's wrapping. + /// + public override string ToString() + { + return (WrappedParameter == null) ? String.Empty : WrappedParameter.ToString(); + } + + /// + /// Serialize / deserialize this item. + /// + public void Translate(ITranslator translator) + { + translator.TranslateEnum(ref _parameterType, (int)_parameterType); + + switch (_parameterType) + { + case TaskParameterType.Null: + _wrappedParameter = null; + break; + case TaskParameterType.PrimitiveType: + TranslatePrimitiveType(translator); + break; + case TaskParameterType.PrimitiveTypeArray: + TranslatePrimitiveTypeArray(translator); + break; + case TaskParameterType.ValueType: + TranslateValueType(translator); + break; + case TaskParameterType.ValueTypeArray: + TranslateValueTypeArray(translator); + break; + case TaskParameterType.ITaskItem: + TranslateITaskItem(translator); + break; + case TaskParameterType.ITaskItemArray: + TranslateITaskItemArray(translator); + break; + case TaskParameterType.Invalid: + Exception exceptionParam = (Exception)_wrappedParameter; + translator.TranslateException(ref exceptionParam); + _wrappedParameter = exceptionParam; + break; + default: + ErrorUtilities.ThrowInternalErrorUnreachable(); + break; + } + } + +#if FEATURE_APPDOMAIN + /// + /// Overridden to give this class infinite lease time. Otherwise we end up with a limited + /// lease (5 minutes I think) and instances can expire if they take long time processing. + /// + [SecurityCritical] + public override object InitializeLifetimeService() + { + // null means infinite lease time + return null; + } +#endif + + /// + /// Factory for deserialization. + /// + internal static TaskParameter FactoryForDeserialization(ITranslator translator) + { + TaskParameter taskParameter = new(); + taskParameter.Translate(translator); + return taskParameter; + } + + /// + /// Serialize / deserialize this item. + /// + private void TranslateITaskItemArray(ITranslator translator) + { + ITaskItem[] wrappedItems = (ITaskItem[])_wrappedParameter; + int length = wrappedItems?.Length ?? 0; + translator.Translate(ref length); + wrappedItems ??= new ITaskItem[length]; + + for (int i = 0; i < wrappedItems.Length; i++) + { + TaskParameterTaskItem taskItem = (TaskParameterTaskItem)wrappedItems[i]; + translator.Translate(ref taskItem, TaskParameterTaskItem.FactoryForDeserialization); + wrappedItems[i] = taskItem; + } + + _wrappedParameter = wrappedItems; + } + + /// + /// Serialize / deserialize this item. + /// + private void TranslateITaskItem(ITranslator translator) + { + TaskParameterTaskItem taskItem = (TaskParameterTaskItem)_wrappedParameter; + translator.Translate(ref taskItem, TaskParameterTaskItem.FactoryForDeserialization); + _wrappedParameter = taskItem; + } + + /// + /// Serializes or deserializes a primitive type value wrapped by this . + /// + private void TranslatePrimitiveType(ITranslator translator) + { + translator.TranslateEnum(ref _parameterTypeCode, (int)_parameterTypeCode); + + switch (_parameterTypeCode) + { + case TypeCode.Boolean: + bool boolParam = _wrappedParameter is bool wrappedBool ? wrappedBool : default; + translator.Translate(ref boolParam); + _wrappedParameter = boolParam; + break; + + case TypeCode.Byte: + byte byteParam = _wrappedParameter is byte wrappedByte ? wrappedByte : default; + translator.Translate(ref byteParam); + _wrappedParameter = byteParam; + break; + + case TypeCode.Int16: + short shortParam = _wrappedParameter is short wrappedShort ? wrappedShort : default; + translator.Translate(ref shortParam); + _wrappedParameter = shortParam; + break; + + case TypeCode.UInt16: + ushort ushortParam = _wrappedParameter is ushort wrappedUShort ? wrappedUShort : default; + translator.Translate(ref ushortParam); + _wrappedParameter = ushortParam; + break; + + case TypeCode.Int64: + long longParam = _wrappedParameter is long wrappedLong ? wrappedLong : default; + translator.Translate(ref longParam); + _wrappedParameter = longParam; + break; + + case TypeCode.Double: + double doubleParam = _wrappedParameter is double wrappedDouble ? wrappedDouble : default; + translator.Translate(ref doubleParam); + _wrappedParameter = doubleParam; + break; + + case TypeCode.String: + string stringParam = (string)_wrappedParameter; + translator.Translate(ref stringParam); + _wrappedParameter = stringParam; + break; + + case TypeCode.DateTime: + DateTime dateTimeParam = _wrappedParameter is DateTime wrappedDateTime ? wrappedDateTime : default; + translator.Translate(ref dateTimeParam); + _wrappedParameter = dateTimeParam; + break; + + default: + // Fall back to converting to/from string for types that don't have ITranslator support. + string stringValue = null; + if (translator.Mode == TranslationDirection.WriteToStream) + { + stringValue = (string)Convert.ChangeType(_wrappedParameter, typeof(string), CultureInfo.InvariantCulture); + } + + translator.Translate(ref stringValue); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + _wrappedParameter = Convert.ChangeType(stringValue, _parameterTypeCode, CultureInfo.InvariantCulture); + } + break; + } + } + + /// + /// Serializes or deserializes an array of primitive type values wrapped by this . + /// + private void TranslatePrimitiveTypeArray(ITranslator translator) + { + translator.TranslateEnum(ref _parameterTypeCode, (int)_parameterTypeCode); + + switch (_parameterTypeCode) + { + case TypeCode.Boolean: + bool[] boolArrayParam = (bool[])_wrappedParameter; + translator.Translate(ref boolArrayParam); + _wrappedParameter = boolArrayParam; + break; + + case TypeCode.Int32: + int[] intArrayParam = (int[])_wrappedParameter; + translator.Translate(ref intArrayParam); + _wrappedParameter = intArrayParam; + break; + + case TypeCode.String: + string[] stringArrayParam = (string[])_wrappedParameter; + translator.Translate(ref stringArrayParam); + _wrappedParameter = stringArrayParam; + break; + + default: + // Fall back to converting to/from string for types that don't have ITranslator support. + if (translator.Mode == TranslationDirection.WriteToStream) + { + Array array = (Array)_wrappedParameter; + int length = array.Length; + + translator.Translate(ref length); + + for (int i = 0; i < length; i++) + { + string valueString = Convert.ToString(array.GetValue(i), CultureInfo.InvariantCulture); + translator.Translate(ref valueString); + } + } + else + { + Type elementType = _parameterTypeCode switch + { + TypeCode.Char => typeof(char), + TypeCode.SByte => typeof(sbyte), + TypeCode.Byte => typeof(byte), + TypeCode.Int16 => typeof(short), + TypeCode.UInt16 => typeof(ushort), + TypeCode.UInt32 => typeof(uint), + TypeCode.Int64 => typeof(long), + TypeCode.UInt64 => typeof(ulong), + TypeCode.Single => typeof(float), + TypeCode.Double => typeof(double), + TypeCode.Decimal => typeof(decimal), + TypeCode.DateTime => typeof(DateTime), + _ => throw new NotImplementedException(), + }; + + int length = 0; + translator.Translate(ref length); + + Array array = Array.CreateInstance(elementType, length); + for (int i = 0; i < length; i++) + { + string valueString = null; + translator.Translate(ref valueString); + array.SetValue(Convert.ChangeType(valueString, _parameterTypeCode, CultureInfo.InvariantCulture), i); + } + _wrappedParameter = array; + } + break; + } + } + + /// + /// Serializes or deserializes the value type instance wrapped by this . + /// + /// + /// The value type is converted to/from string using the class. Note that we require + /// task parameter types to be so this conversion is guaranteed to work for parameters + /// that have made it this far. + /// + private void TranslateValueType(ITranslator translator) + { + string valueString = null; + if (translator.Mode == TranslationDirection.WriteToStream) + { + valueString = (string)Convert.ChangeType(_wrappedParameter, typeof(string), CultureInfo.InvariantCulture); + } + + translator.Translate(ref valueString); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + // We don't know how to convert the string back to the original value type. This is fine because output + // task parameters are anyway converted to strings by the engine (see TaskExecutionHost.GetValueOutputs) + // and input task parameters of custom value types are not supported. + _wrappedParameter = valueString; + } + } + + /// + /// Serializes or deserializes the value type array instance wrapped by this . + /// + /// + /// The array is assumed to be non-null. + /// + private void TranslateValueTypeArray(ITranslator translator) + { + if (translator.Mode == TranslationDirection.WriteToStream) + { + Array array = (Array)_wrappedParameter; + int length = array.Length; + + translator.Translate(ref length); + + for (int i = 0; i < length; i++) + { + string valueString = Convert.ToString(array.GetValue(i), CultureInfo.InvariantCulture); + translator.Translate(ref valueString); + } + } + else + { + int length = 0; + translator.Translate(ref length); + + string[] stringArray = new string[length]; + for (int i = 0; i < length; i++) + { + translator.Translate(ref stringArray[i]); + } + + // We don't know how to convert the string array back to the original value type array. + // This is fine because the engine would eventually convert it to strings anyway. + _wrappedParameter = stringArray; + } + } + + /// + /// Super simple ITaskItem derivative that we can use as a container for read items. + /// + private class TaskParameterTaskItem : +#if FEATURE_APPDOMAIN + MarshalByRefObject, +#endif + ITaskItem, + ITaskItem2, + ITranslatable +#if !TASKHOST + , IMetadataContainer +#endif + { + /// + /// The item spec + /// + private string _escapedItemSpec = null; + + /// + /// The full path to the project that originally defined this item. + /// + private string _escapedDefiningProject = null; + + /// + /// The custom metadata + /// + private Dictionary _customEscapedMetadata = null; + + /// + /// Cache for fullpath metadata + /// + private string _fullPath; + + /// + /// Constructor for serialization + /// + internal TaskParameterTaskItem(ITaskItem copyFrom) + { + if (copyFrom is ITaskItem2 copyFromAsITaskItem2) + { + _escapedItemSpec = copyFromAsITaskItem2.EvaluatedIncludeEscaped; + _escapedDefiningProject = copyFromAsITaskItem2.GetMetadataValueEscaped(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath); + + IDictionary nonGenericEscapedMetadata = copyFromAsITaskItem2.CloneCustomMetadataEscaped(); + _customEscapedMetadata = nonGenericEscapedMetadata as Dictionary; + + if (_customEscapedMetadata is null) + { + _customEscapedMetadata = new Dictionary(MSBuildNameIgnoreCaseComparer.Default); + foreach (DictionaryEntry entry in nonGenericEscapedMetadata) + { + _customEscapedMetadata[(string)entry.Key] = (string)entry.Value ?? string.Empty; + } + } + } + else + { + // If we don't have ITaskItem2 to fall back on, we have to make do with the fact that + // CloneCustomMetadata, GetMetadata, & ItemSpec returns unescaped values, and + // TaskParameterTaskItem's constructor expects escaped values, so escaping them all + // is the closest approximation to correct we can get. + _escapedItemSpec = EscapingUtilities.Escape(copyFrom.ItemSpec); + _escapedDefiningProject = EscapingUtilities.EscapeWithCaching(copyFrom.GetMetadata(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath)); + + IDictionary customMetadata = copyFrom.CloneCustomMetadata(); + _customEscapedMetadata = new Dictionary(MSBuildNameIgnoreCaseComparer.Default); + + if (customMetadata?.Count > 0) + { + foreach (DictionaryEntry entry in customMetadata) + { + _customEscapedMetadata[(string)entry.Key] = EscapingUtilities.Escape((string)entry.Value) ?? string.Empty; + } + } + } + + ErrorUtilities.VerifyThrowInternalNull(_escapedItemSpec); + } + + private TaskParameterTaskItem() + { + } + + /// + /// Gets or sets the item "specification" e.g. for disk-based items this would be the file path. + /// + /// + /// This should be named "EvaluatedInclude" but that would be a breaking change to this interface. + /// + /// The item-spec string. + public string ItemSpec + { + get + { + return (_escapedItemSpec == null) ? String.Empty : EscapingUtilities.UnescapeAll(_escapedItemSpec); + } + + set + { + _escapedItemSpec = value; + } + } + + /// + /// Gets the names of all the metadata on the item. + /// Includes the built-in metadata like "FullPath". + /// + /// The list of metadata names. + public ICollection MetadataNames + { + get + { + List metadataNames = (_customEscapedMetadata == null) ? new List() : new List(_customEscapedMetadata.Keys); + metadataNames.AddRange(FileUtilities.ItemSpecModifiers.All); + + return metadataNames; + } + } + + /// + /// Gets the number of pieces of metadata on the item. Includes + /// both custom and built-in metadata. Used only for unit testing. + /// + /// Count of pieces of metadata. + public int MetadataCount + { + get + { + int count = (_customEscapedMetadata == null) ? 0 : _customEscapedMetadata.Count; + return count + FileUtilities.ItemSpecModifiers.All.Length; + } + } + + /// + /// Returns the escaped version of this item's ItemSpec + /// + string ITaskItem2.EvaluatedIncludeEscaped + { + get + { + return _escapedItemSpec; + } + + set + { + _escapedItemSpec = value; + } + } + + public SerializableMetadata BackingMetadata => default; + + public bool HasCustomMetadata => _customEscapedMetadata?.Count > 0; + + /// + /// Allows the values of metadata on the item to be queried. + /// + /// The name of the metadata to retrieve. + /// The value of the specified metadata. + public string GetMetadata(string metadataName) + { + string metadataValue = (this as ITaskItem2).GetMetadataValueEscaped(metadataName); + return EscapingUtilities.UnescapeAll(metadataValue); + } + + /// + /// Allows a piece of custom metadata to be set on the item. + /// + /// The name of the metadata to set. + /// The metadata value. + public void SetMetadata(string metadataName, string metadataValue) + { + ErrorUtilities.VerifyThrowArgumentLength(metadataName); + + // Non-derivable metadata can only be set at construction time. + // That's why this is IsItemSpecModifier and not IsDerivableItemSpecModifier. + ErrorUtilities.VerifyThrowArgument(!FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(metadataName), "Shared.CannotChangeItemSpecModifiers", metadataName); + + _customEscapedMetadata ??= new Dictionary(MSBuildNameIgnoreCaseComparer.Default); + + _customEscapedMetadata[metadataName] = metadataValue ?? String.Empty; + } + + /// + /// Allows the removal of custom metadata set on the item. + /// + /// The name of the metadata to remove. + public void RemoveMetadata(string metadataName) + { + ErrorUtilities.VerifyThrowArgumentNull(metadataName); + ErrorUtilities.VerifyThrowArgument(!FileUtilities.ItemSpecModifiers.IsItemSpecModifier(metadataName), "Shared.CannotChangeItemSpecModifiers", metadataName); + + if (_customEscapedMetadata == null) + { + return; + } + + _customEscapedMetadata.Remove(metadataName); + } + + /// + /// Allows custom metadata on the item to be copied to another item. + /// + /// + /// RECOMMENDED GUIDELINES FOR METHOD IMPLEMENTATIONS: + /// 1) this method should NOT copy over the item-spec + /// 2) if a particular piece of metadata already exists on the destination item, it should NOT be overwritten + /// 3) if there are pieces of metadata on the item that make no semantic sense on the destination item, they should NOT be copied + /// + /// The item to copy metadata to. + public void CopyMetadataTo(ITaskItem destinationItem) + { + ErrorUtilities.VerifyThrowArgumentNull(destinationItem); + + // also copy the original item-spec under a "magic" metadata -- this is useful for tasks that forward metadata + // between items, and need to know the source item where the metadata came from + string originalItemSpec = destinationItem.GetMetadata("OriginalItemSpec"); + +#if !TASKHOST + if (_customEscapedMetadata != null && destinationItem is IMetadataContainer destinationItemAsMetadataContainer) + { + // The destination implements IMetadataContainer so we can use the ImportMetadata bulk-set operation. + IEnumerable> metadataToImport = _customEscapedMetadata + .Where(metadatum => string.IsNullOrEmpty(destinationItem.GetMetadata(metadatum.Key))); + +#if FEATURE_APPDOMAIN + if (RemotingServices.IsTransparentProxy(destinationItem)) + { + // Linq is not serializable so materialize the collection before making the call. + metadataToImport = metadataToImport.ToList(); + } +#endif + + destinationItemAsMetadataContainer.ImportMetadata(metadataToImport); + } + else +#endif + if (_customEscapedMetadata != null) + { + foreach (KeyValuePair entry in _customEscapedMetadata) + { + string value = destinationItem.GetMetadata(entry.Key); + + if (String.IsNullOrEmpty(value)) + { + destinationItem.SetMetadata(entry.Key, entry.Value); + } + } + } + + if (String.IsNullOrEmpty(originalItemSpec)) + { + destinationItem.SetMetadata("OriginalItemSpec", EscapingUtilities.Escape(ItemSpec)); + } + } + + /// + /// Get the collection of custom metadata. This does not include built-in metadata. + /// + /// + /// RECOMMENDED GUIDELINES FOR METHOD IMPLEMENTATIONS: + /// 1) this method should return a clone of the metadata + /// 2) writing to this dictionary should not be reflected in the underlying item. + /// + /// Dictionary of cloned metadata + public IDictionary CloneCustomMetadata() + { + IDictionary clonedMetadata = new Dictionary(MSBuildNameIgnoreCaseComparer.Default); + + if (_customEscapedMetadata != null) + { + foreach (KeyValuePair metadatum in _customEscapedMetadata) + { + clonedMetadata.Add(metadatum.Key, EscapingUtilities.UnescapeAll(metadatum.Value)); + } + } + + return (IDictionary)clonedMetadata; + } + +#if FEATURE_APPDOMAIN + /// + /// Overridden to give this class infinite lease time. Otherwise we end up with a limited + /// lease (5 minutes I think) and instances can expire if they take long time processing. + /// + [SecurityCritical] + public override object InitializeLifetimeService() + { + // null means infinite lease time + return null; + } +#endif + + /// + /// Returns the escaped value of the requested metadata name. + /// + string ITaskItem2.GetMetadataValueEscaped(string metadataName) + { + ErrorUtilities.VerifyThrowArgumentNull(metadataName); + + string metadataValue = null; + + if (FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(metadataName)) + { + // FileUtilities.GetItemSpecModifier is expecting escaped data, which we assume we already are. + // Passing in a null for currentDirectory indicates we are already in the correct current directory + metadataValue = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(null, _escapedItemSpec, _escapedDefiningProject, metadataName, ref _fullPath); + } + else if (_customEscapedMetadata != null) + { + _customEscapedMetadata.TryGetValue(metadataName, out metadataValue); + } + + return metadataValue ?? String.Empty; + } + + /// + /// Sets the exact metadata value given to the metadata name requested. + /// + void ITaskItem2.SetMetadataValueLiteral(string metadataName, string metadataValue) + { + SetMetadata(metadataName, EscapingUtilities.Escape(metadataValue)); + } + + /// + /// Returns a dictionary containing all metadata and their escaped forms. + /// + IDictionary ITaskItem2.CloneCustomMetadataEscaped() + { + IDictionary clonedDictionary = new Dictionary(_customEscapedMetadata); + return clonedDictionary; + } + + public IEnumerable> EnumerateMetadata() + { +#if FEATURE_APPDOMAIN + if (!AppDomain.CurrentDomain.IsDefaultAppDomain()) + { + return EnumerateMetadataEager(); + } +#endif + + return EnumerateMetadataLazy(); + } + +#if FEATURE_APPDOMAIN + private IEnumerable> EnumerateMetadataEager() + { + if (_customEscapedMetadata == null || _customEscapedMetadata.Count == 0) + { + return []; + } + + var result = new KeyValuePair[_customEscapedMetadata.Count]; + int index = 0; + foreach (var kvp in _customEscapedMetadata) + { + var unescaped = new KeyValuePair(kvp.Key, EscapingUtilities.UnescapeAll(kvp.Value)); + result[index++] = unescaped; + } + + return result; + } +#endif + + private IEnumerable> EnumerateMetadataLazy() + { + if (_customEscapedMetadata == null) + { + yield break; + } + + foreach (var kvp in _customEscapedMetadata) + { + var unescaped = new KeyValuePair(kvp.Key, EscapingUtilities.UnescapeAll(kvp.Value)); + yield return unescaped; + } + } + + public void ImportMetadata(IEnumerable> metadata) + { + foreach (KeyValuePair kvp in metadata) + { + SetMetadata(kvp.Key, kvp.Value); + } + } + + public void RemoveMetadataRange(IEnumerable metadataNames) + { + foreach (string metadataName in metadataNames) + { + RemoveMetadata(metadataName); + } + } + + public void Translate(ITranslator translator) + { + translator.Translate(ref _escapedItemSpec); + translator.Translate(ref _escapedDefiningProject); + translator.TranslateDictionary(ref _customEscapedMetadata, MSBuildNameIgnoreCaseComparer.Default); + + ErrorUtilities.VerifyThrowInternalNull(_escapedItemSpec); + ErrorUtilities.VerifyThrowInternalNull(_customEscapedMetadata); + } + + internal static TaskParameterTaskItem FactoryForDeserialization(ITranslator translator) + { + TaskParameterTaskItem taskItem = new(); + taskItem.Translate(translator); + return taskItem; + } + } + } +} diff --git a/src/MSBuildTaskHost/TaskParameterTypeVerifier.cs b/src/MSBuildTaskHost/TaskParameterTypeVerifier.cs new file mode 100644 index 00000000000..2b7d6160d84 --- /dev/null +++ b/src/MSBuildTaskHost/TaskParameterTypeVerifier.cs @@ -0,0 +1,75 @@ +// 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.Reflection; +using Microsoft.Build.Framework; +#if NET35 +using Microsoft.Build.Shared; +#endif + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + /// + /// Provide a class which can verify the correct type for both input and output parameters. + /// + internal static class TaskParameterTypeVerifier + { + /// + /// Is the parameter type a valid scalar input value + /// + internal static bool IsValidScalarInputParameter(Type parameterType) => + parameterType.GetTypeInfo().IsValueType || parameterType == typeof(string) || parameterType == typeof(ITaskItem); + + /// + /// Is the passed in parameterType a valid vector input parameter + /// + internal static bool IsValidVectorInputParameter(Type parameterType) + { + bool result = (parameterType.IsArray && parameterType.GetElementType().GetTypeInfo().IsValueType) || + parameterType == typeof(string[]) || + parameterType == typeof(ITaskItem[]); + return result; + } + + /// + /// Is the passed in value type assignable to an ITask or Itask[] object + /// + internal static bool IsAssignableToITask(Type parameterType) + { + bool result = typeof(ITaskItem[]).GetTypeInfo().IsAssignableFrom(parameterType.GetTypeInfo()) || /* ITaskItem array or derived type, or */ + typeof(ITaskItem).IsAssignableFrom(parameterType); /* ITaskItem or derived type */ + return result; + } + + /// + /// Is the passed parameter a valid value type output parameter + /// + internal static bool IsValueTypeOutputParameter(Type parameterType) + { + bool result = (parameterType.IsArray && parameterType.GetElementType().GetTypeInfo().IsValueType) || /* array of value types, or */ + parameterType == typeof(string[]) || /* string array, or */ + parameterType.GetTypeInfo().IsValueType || /* value type, or */ + parameterType == typeof(string); /* string */ + return result; + } + + /// + /// Is the parameter type a valid scalar or value type input parameter + /// + internal static bool IsValidInputParameter(Type parameterType) + { + return IsValidScalarInputParameter(parameterType) || IsValidVectorInputParameter(parameterType); + } + + /// + /// Is the parameter type a valid scalar or value type output parameter + /// + internal static bool IsValidOutputParameter(Type parameterType) + { + return IsValueTypeOutputParameter(parameterType) || IsAssignableToITask(parameterType); + } + } +} diff --git a/src/MSBuildTaskHost/TranslatorHelpers.cs b/src/MSBuildTaskHost/TranslatorHelpers.cs new file mode 100644 index 00000000000..52bf3941657 --- /dev/null +++ b/src/MSBuildTaskHost/TranslatorHelpers.cs @@ -0,0 +1,430 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +#if !TASKHOST +using System.Collections.Frozen; +using System.Collections.Immutable; +#endif +using System.Collections.Generic; +using System.Configuration.Assemblies; +using System.Globalization; +using System.Reflection; +using AssemblyHashAlgorithm = System.Configuration.Assemblies.AssemblyHashAlgorithm; + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + /// + /// This class provides helper methods to adapt from to + /// . + /// + internal static class TranslatorHelpers + { + /// + /// Translates an object implementing which does not expose a + /// public parameterless constructor. + /// + /// The reference type. + /// The translator + /// The value to be translated. + /// The factory method used to instantiate values of type T. + public static void Translate( + this ITranslator translator, + ref T instance, + NodePacketValueFactory valueFactory) where T : ITranslatable + { + if (!translator.TranslateNullable(instance)) + { + return; + } + if (translator.Mode == TranslationDirection.ReadFromStream) + { + instance = valueFactory(translator); + } + else + { + instance.Translate(translator); + } + } + + private static ObjectTranslatorWithValueFactory AdaptFactory(NodePacketValueFactory valueFactory) where T : ITranslatable + { + static void TranslateUsingValueFactory(ITranslator translator, NodePacketValueFactory valueFactory, ref T objectToTranslate) + { + translator.Translate(ref objectToTranslate, valueFactory); + } + + return TranslateUsingValueFactory; + } + + public static void Translate( + this ITranslator translator, + ref List list, + NodePacketValueFactory valueFactory) where T : class, ITranslatable + { + translator.Translate(ref list, AdaptFactory(valueFactory), valueFactory); + } + + public static void Translate( + this ITranslator translator, + ref IList list, + NodePacketValueFactory valueFactory, + NodePacketCollectionCreator collectionFactory) where L : IList where T : ITranslatable + { + translator.Translate(ref list, AdaptFactory(valueFactory), valueFactory, collectionFactory); + } + + public static void TranslateArray( + this ITranslator translator, + ref T[] array, + NodePacketValueFactory valueFactory) where T : class, ITranslatable + { + translator.TranslateArray(ref array, AdaptFactory(valueFactory), valueFactory); + } + + public static void TranslateDictionary( + this ITranslator translator, + ref Dictionary dictionary, + IEqualityComparer comparer, + NodePacketValueFactory valueFactory) where T : class, ITranslatable + { + translator.TranslateDictionary(ref dictionary, comparer, AdaptFactory(valueFactory), valueFactory); + } + + public static void InternDictionary( + this ITranslator translator, + ref Dictionary dictionary, + IEqualityComparer comparer) + { + IDictionary localDict = dictionary; + translator.TranslateDictionary( + ref localDict, + (ITranslator translator, ref string key) => translator.Intern(ref key), + (ITranslator translator, ref string val) => translator.Intern(ref val), + capacity => new Dictionary(capacity, comparer)); + dictionary = (Dictionary)localDict; + } + + public static void InternDictionary( + this ITranslator translator, + ref Dictionary dictionary, + IEqualityComparer stringComparer, + NodePacketValueFactory valueFactory) + where T : ITranslatable + { + IDictionary localDict = dictionary; + translator.TranslateDictionary( + ref localDict, + (ITranslator translator, ref string key) => translator.Intern(ref key), + AdaptFactory(valueFactory), + valueFactory, + capacity => new Dictionary(capacity, stringComparer)); + dictionary = (Dictionary)localDict; + } + + public static void InternPathDictionary( + this ITranslator translator, + ref Dictionary dictionary, + IEqualityComparer comparer) + { + IDictionary localDict = dictionary; + + // For now, assume only the value contains path-like strings (e.g. TaskItem metadata). + translator.TranslateDictionary( + ref localDict, + (ITranslator translator, ref string key) => translator.Intern(ref key), + (ITranslator translator, ref string val) => translator.InternPath(ref val), + capacity => new Dictionary(capacity, comparer)); + dictionary = (Dictionary)localDict; + } + + public static void InternPathDictionary( + this ITranslator translator, + ref Dictionary dictionary, + IEqualityComparer stringComparer, + NodePacketValueFactory valueFactory) + where T : ITranslatable + { + IDictionary localDict = dictionary; + translator.TranslateDictionary( + ref localDict, + (ITranslator translator, ref string key) => translator.InternPath(ref key), + AdaptFactory(valueFactory), + valueFactory, + capacity => new Dictionary(capacity, stringComparer)); + dictionary = (Dictionary)localDict; + } + + public static void TranslateDictionary( + this ITranslator translator, + ref D dictionary, + NodePacketValueFactory valueFactory) + where D : IDictionary, new() + where T : class, ITranslatable + { + translator.TranslateDictionary(ref dictionary, AdaptFactory(valueFactory), valueFactory); + } + + public static void TranslateDictionary( + this ITranslator translator, + ref D dictionary, + NodePacketValueFactory valueFactory, + NodePacketCollectionCreator collectionCreator) + where D : IDictionary + where T : class, ITranslatable + { + translator.TranslateDictionary(ref dictionary, AdaptFactory(valueFactory), valueFactory, collectionCreator); + } + +#if !TASKHOST + public static void TranslateDictionary( + this ITranslator translator, + ref FrozenDictionary dictionary, + IEqualityComparer comparer) + { + IDictionary localDict = dictionary; + translator.TranslateDictionary(ref localDict, capacity => new Dictionary(capacity, comparer)); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + dictionary = localDict?.ToFrozenDictionary(comparer); + } + } + + public static void TranslateDictionary( + this ITranslator translator, + ref IReadOnlyDictionary dictionary, + IEqualityComparer comparer) + { + // Defensive copy since immutable dictionaries are expected to be overwritten. + IReadOnlyDictionary localDict = dictionary; + + if (!translator.TranslateNullable(localDict)) + { + return; + } + + if (translator.Mode == TranslationDirection.WriteToStream) + { + int count = localDict.Count; + translator.Translate(ref count); + + foreach (KeyValuePair kvp in localDict) + { + string key = kvp.Key; + string value = kvp.Value; + + translator.Translate(ref key); + translator.Translate(ref value); + } + } + else + { + int count = default; + translator.Translate(ref count); + + ImmutableDictionary.Builder builder = ImmutableDictionary.Create(comparer).ToBuilder(); + + for (int i = 0; i < count; i++) + { + string key = null; + string value = null; + + translator.Translate(ref key); + translator.Translate(ref value); + + builder[key] = value; + } + + dictionary = builder.ToImmutable(); + } + } + + public static void TranslateDictionary( + this ITranslator translator, + ref ImmutableDictionary dictionary, + IEqualityComparer comparer) + { + // Defensive copy since immutable dictionaries are expected to be overwritten. + IReadOnlyDictionary localDict = dictionary; + + TranslateDictionary(translator, ref localDict, comparer); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + dictionary = (ImmutableDictionary)localDict; + } + } +#endif + + public static void TranslateHashSet( + this ITranslator translator, + ref HashSet hashSet, + NodePacketValueFactory valueFactory, + NodePacketCollectionCreator> collectionFactory) where T : class, ITranslatable + { + if (!translator.TranslateNullable(hashSet)) + { + return; + } + + int count = default; + if (translator.Mode == TranslationDirection.WriteToStream) + { + count = hashSet.Count; + } + translator.Translate(ref count); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + hashSet = collectionFactory(count); + for (int i = 0; i < count; i++) + { + T value = default; + translator.Translate(ref value, valueFactory); + hashSet.Add(value); + } + } + + if (translator.Mode == TranslationDirection.WriteToStream) + { + foreach (T item in hashSet) + { + T value = item; + translator.Translate(ref value, valueFactory); + } + } + } + + public static void Translate(this ITranslator translator, ref CultureInfo cultureInfo) + { + if (!translator.TranslateNullable(cultureInfo)) + { + return; + } + + int lcid = default; + + if (translator.Mode == TranslationDirection.WriteToStream) + { + lcid = cultureInfo.LCID; + } + + translator.Translate(ref lcid); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + cultureInfo = new CultureInfo(lcid); + } + } + + public static void Translate(this ITranslator translator, ref Version version) + { + if (!translator.TranslateNullable(version)) + { + return; + } + + int major = 0; + int minor = 0; + int build = 0; + int revision = 0; + + if (translator.Mode == TranslationDirection.WriteToStream) + { + major = version.Major; + minor = version.Minor; + build = version.Build; + revision = version.Revision; + } + + translator.Translate(ref major); + translator.Translate(ref minor); + translator.Translate(ref build); + translator.Translate(ref revision); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + if (build < 0) + { + version = new Version(major, minor); + } + else if (revision < 0) + { + version = new Version(major, minor, build); + } + else + { + version = new Version(major, minor, build, revision); + } + } + } + + public static void Translate(this ITranslator translator, ref AssemblyName assemblyName) + { + if (!translator.TranslateNullable(assemblyName)) + { + return; + } + + string name = null; + Version version = null; + AssemblyNameFlags flags = default; + ProcessorArchitecture processorArchitecture = default; + CultureInfo cultureInfo = null; + AssemblyHashAlgorithm hashAlgorithm = default; + AssemblyVersionCompatibility versionCompatibility = default; + string codeBase = null; + + byte[] publicKey = null; + byte[] publicKeyToken = null; + + if (translator.Mode == TranslationDirection.WriteToStream) + { + name = assemblyName.Name; + version = assemblyName.Version; + flags = assemblyName.Flags; + processorArchitecture = assemblyName.ProcessorArchitecture; + cultureInfo = assemblyName.CultureInfo; + hashAlgorithm = assemblyName.HashAlgorithm; + versionCompatibility = assemblyName.VersionCompatibility; + codeBase = assemblyName.CodeBase; + + publicKey = assemblyName.GetPublicKey(); // TODO: no need to serialize, public key is not used anywhere in context of RAR, only public key token + publicKeyToken = assemblyName.GetPublicKeyToken(); + } + + translator.Translate(ref name); + translator.Translate(ref version); + translator.TranslateEnum(ref flags, (int)flags); + translator.TranslateEnum(ref processorArchitecture, (int)processorArchitecture); + translator.Translate(ref cultureInfo); + translator.TranslateEnum(ref hashAlgorithm, (int)hashAlgorithm); + translator.TranslateEnum(ref versionCompatibility, (int)versionCompatibility); + translator.Translate(ref codeBase); + + translator.Translate(ref publicKey); + translator.Translate(ref publicKeyToken); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + assemblyName = new AssemblyName + { + Name = name, + Version = version, + Flags = flags, + ProcessorArchitecture = processorArchitecture, + CultureInfo = cultureInfo, + HashAlgorithm = hashAlgorithm, + VersionCompatibility = versionCompatibility, + CodeBase = codeBase, + }; + + assemblyName.SetPublicKey(publicKey); + assemblyName.SetPublicKeyToken(publicKeyToken); + } + } + } +} diff --git a/src/MSBuildTaskHost/XMakeAttributes.cs b/src/MSBuildTaskHost/XMakeAttributes.cs new file mode 100644 index 00000000000..47e15477b90 --- /dev/null +++ b/src/MSBuildTaskHost/XMakeAttributes.cs @@ -0,0 +1,483 @@ +// 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 +{ + /// + /// Contains the names of the known attributes in the XML project file. + /// + 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 KnownSpecialTaskAttributes = new HashSet { condition, continueOnError, msbuildRuntime, msbuildArchitecture, xmlns }; + + private static readonly HashSet KnownSpecialTaskAttributesIgnoreCase = new HashSet(StringComparer.OrdinalIgnoreCase) { condition, continueOnError, msbuildRuntime, msbuildArchitecture, xmlns }; + + private static readonly HashSet KnownBatchingTargetAttributes = new HashSet { name, condition, dependsOnTargets, beforeTargets, afterTargets }; + + private static readonly HashSet ValidMSBuildRuntimeValues = new HashSet(StringComparer.OrdinalIgnoreCase) { MSBuildRuntimeValues.clr2, MSBuildRuntimeValues.clr4, MSBuildRuntimeValues.currentRuntime, MSBuildRuntimeValues.net, MSBuildRuntimeValues.any }; + + private static readonly HashSet ValidMSBuildArchitectureValues = new HashSet(StringComparer.OrdinalIgnoreCase) { MSBuildArchitectureValues.x86, MSBuildArchitectureValues.x64, MSBuildArchitectureValues.arm64, MSBuildArchitectureValues.currentArchitecture, MSBuildArchitectureValues.any }; + + /// + /// 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. + /// + /// + /// true, if given attribute is a reserved task attribute + 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); + } + + /// + /// Checks if the specified attribute is a reserved task attribute with incorrect casing. + /// + /// + /// true, if the given attribute is reserved and badly cased + internal static bool IsBadlyCasedSpecialTaskAttribute(string attribute) + { + return !IsSpecialTaskAttribute(attribute) && KnownSpecialTaskAttributesIgnoreCase.Contains(attribute); + } + + /// + /// Indicates if the specified attribute cannot be used for batching targets. + /// + /// + /// true, if a target cannot batch on the given attribute + internal static bool IsNonBatchingTargetAttribute(string attribute) + { + return KnownBatchingTargetAttributes.Contains(attribute); + } + + /// + /// Returns true if the given string is a valid member of the MSBuildRuntimeValues set + /// + internal static bool IsValidMSBuildRuntimeValue(string runtime) => runtime == null || ValidMSBuildRuntimeValues.Contains(runtime); + + /// + /// Returns true if the given string is a valid member of the MSBuildArchitectureValues set + /// + internal static bool IsValidMSBuildArchitectureValue(string architecture) => architecture == null || ValidMSBuildArchitectureValues.Contains(architecture); + + /// + /// Compares two members of MSBuildRuntimeValues, returning true if they count as a match, and false otherwise. + /// + 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; + } + + /// + /// 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". + /// + 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; + } + + /// + /// Compares two members of MSBuildArchitectureValues, returning true if they count as a match, and false otherwise. + /// + 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; + } + + /// + /// 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. + /// + 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; + } + } + + /// + /// 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. + /// + 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; + } + + /// + /// Returns the MSBuildArchitecture value corresponding to the current process' architecture. + /// + /// + /// Revisit if we ever run on something other than Intel. + /// + 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; + } + + /// + /// Returns the MSBuildRuntime value corresponding to the current process' runtime. + /// + internal static string GetCurrentMSBuildRuntime() + { +#if NET40_OR_GREATER + return MSBuildRuntimeValues.clr4; +#else + return MSBuildRuntimeValues.net; +#endif + } + + /// + /// 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. + /// + 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; + } + } + } +} From b75f9a9242a67c5ec1f27655200b7d013ef02719 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 08:57:01 -0800 Subject: [PATCH 002/136] Separate $(DefineConstants) properties for net3* and net4* Split the PropertyGroup for $(DefineConstants) into two: one for net3* and one for net4* --- src/Directory.BeforeCommon.targets | 59 +++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets index 175f769783a..3e19b7e8ea0 100644 --- a/src/Directory.BeforeCommon.targets +++ b/src/Directory.BeforeCommon.targets @@ -14,7 +14,64 @@ $(DefineConstants);FEATURE_DEBUG_LAUNCH - + + $(DefineConstants);FEATURE_APARTMENT_STATE + $(DefineConstants);FEATURE_APM + $(DefineConstants);FEATURE_APPDOMAIN + true + $(DefineConstants);FEATURE_ASPNET_COMPILER + $(DefineConstants);FEATURE_ASSEMBLY_LOCATION + $(DefineConstants);FEATURE_COMPILED_XSL + $(DefineConstants);FEATURE_COMPILE_IN_TESTS + $(DefineConstants);FEATURE_CONSTRAINED_EXECUTION + $(DefineConstants);FEATURE_CODETASKFACTORY + $(DefineConstants);FEATURE_CRYPTOGRAPHIC_FACTORY_ALGORITHM_NAMES + $(DefineConstants);FEATURE_CULTUREINFO_GETCULTURES + $(DefineConstants);FEATURE_ENCODING_DEFAULT + $(DefineConstants);FEATURE_ENVIRONMENT_SYSTEMDIRECTORY + $(DefineConstants);FEATURE_FILE_TRACKER + $(DefineConstants);FEATURE_GAC + $(DefineConstants);FEATURE_HANDLEPROCESSCORRUPTEDSTATEEXCEPTIONS + $(DefineConstants);FEATURE_HTTP_LISTENER + $(DefineConstants);FEATURE_INSTALLED_MSBUILD + + $(DefineConstants);FEATURE_LEGACY_GETCURRENTDIRECTORY + + $(DefineConstants);FEATURE_LEGACY_GETFULLPATH + $(DefineConstants);FEATURE_NAMED_PIPE_SECURITY_CONSTRUCTOR + $(DefineConstants);FEATURE_PERFORMANCE_COUNTERS + $(DefineConstants);FEATURE_PIPE_SECURITY + $(DefineConstants);FEATURE_PFX_SIGNING + $(DefineConstants);FEATURE_REFLECTION_EMIT_DEBUG_INFO + $(DefineConstants);FEATURE_REGISTRY_TOOLSETS + $(DefineConstants);FEATURE_REGISTRY_SDKS + $(DefineConstants);FEATURE_REGISTRYHIVE_DYNDATA + $(DefineConstants);FEATURE_RESGEN + $(DefineConstants);FEATURE_RESGENCACHE + $(DefineConstants);FEATURE_RESOURCE_EXPOSURE + $(DefineConstants);FEATURE_RESXREADER_LIVEDESERIALIZATION + $(DefineConstants);FEATURE_RUN_EXE_IN_TESTS + $(DefineConstants);FEATURE_SECURITY_PERMISSIONS + $(DefineConstants);FEATURE_SECURITY_PRINCIPAL_WINDOWS + $(DefineConstants);FEATURE_STRONG_NAMES + $(DefineConstants);FEATURE_SYSTEM_CONFIGURATION + true + $(DefineConstants);FEATURE_TASK_GENERATERESOURCES + $(DefineConstants);FEATURE_THREAD_ABORT + $(DefineConstants);FEATURE_THREAD_CULTURE + $(DefineConstants);FEATURE_MULTIPLE_TOOLSETS + $(DefineConstants);FEATURE_NODE_REUSE + $(DefineConstants);FEATURE_NET35_TASKHOST + $(DefineConstants);FEATURE_XAML_TYPES + $(DefineConstants);FEATURE_XAMLTASKFACTORY + true + $(DefineConstants);FEATURE_XML_SCHEMA_VALIDATION + $(DefineConstants);FEATURE_WIN32_REGISTRY + $(DefineConstants);FEATURE_VISUALSTUDIOSETUP + $(DefineConstants);FEATURE_MSCOREE + + + $(DefineConstants);FEATURE_APARTMENT_STATE $(DefineConstants);FEATURE_APM $(DefineConstants);FEATURE_APPDOMAIN From 92d573b6bc38098672219a67be2b06a7210b2945 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 09:00:51 -0800 Subject: [PATCH 003/136] MSBuildTaskHost: Move all polyfills to Polyfills folder --- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 9 +++++---- .../Polyfills/CallerArgumentExpressionAttribute.cs | 0 src/MSBuildTaskHost/{ => Polyfills}/IsExternalInit.cs | 0 .../{ => Polyfills}/NullableAttributes.cs | 0 .../{Framework => }/Polyfills/StringSyntaxAttribute.cs | 0 5 files changed, 5 insertions(+), 4 deletions(-) rename src/MSBuildTaskHost/{Framework => }/Polyfills/CallerArgumentExpressionAttribute.cs (100%) rename src/MSBuildTaskHost/{ => Polyfills}/IsExternalInit.cs (100%) rename src/MSBuildTaskHost/{ => Polyfills}/NullableAttributes.cs (100%) rename src/MSBuildTaskHost/{Framework => }/Polyfills/StringSyntaxAttribute.cs (100%) diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 2f8fe51e0c8..014d9dcba7d 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -1,4 +1,4 @@ - + @@ -87,7 +87,6 @@ - @@ -103,7 +102,6 @@ - @@ -116,10 +114,13 @@ - + + + + diff --git a/src/MSBuildTaskHost/Framework/Polyfills/CallerArgumentExpressionAttribute.cs b/src/MSBuildTaskHost/Polyfills/CallerArgumentExpressionAttribute.cs similarity index 100% rename from src/MSBuildTaskHost/Framework/Polyfills/CallerArgumentExpressionAttribute.cs rename to src/MSBuildTaskHost/Polyfills/CallerArgumentExpressionAttribute.cs diff --git a/src/MSBuildTaskHost/IsExternalInit.cs b/src/MSBuildTaskHost/Polyfills/IsExternalInit.cs similarity index 100% rename from src/MSBuildTaskHost/IsExternalInit.cs rename to src/MSBuildTaskHost/Polyfills/IsExternalInit.cs diff --git a/src/MSBuildTaskHost/NullableAttributes.cs b/src/MSBuildTaskHost/Polyfills/NullableAttributes.cs similarity index 100% rename from src/MSBuildTaskHost/NullableAttributes.cs rename to src/MSBuildTaskHost/Polyfills/NullableAttributes.cs diff --git a/src/MSBuildTaskHost/Framework/Polyfills/StringSyntaxAttribute.cs b/src/MSBuildTaskHost/Polyfills/StringSyntaxAttribute.cs similarity index 100% rename from src/MSBuildTaskHost/Framework/Polyfills/StringSyntaxAttribute.cs rename to src/MSBuildTaskHost/Polyfills/StringSyntaxAttribute.cs From 04e8cd3f358c611f943e96b9de2ebbdcfd71432d Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 09:01:37 -0800 Subject: [PATCH 004/136] MSBuildTaskHost: Delete Polyfills\StringSyntaxAttribute.cs This polyfill is unused in MSBuildTaskHost. --- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - .../Polyfills/StringSyntaxAttribute.cs | 78 ------------------- 2 files changed, 79 deletions(-) delete mode 100644 src/MSBuildTaskHost/Polyfills/StringSyntaxAttribute.cs diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 014d9dcba7d..6e64c260ded 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -120,7 +120,6 @@ - diff --git a/src/MSBuildTaskHost/Polyfills/StringSyntaxAttribute.cs b/src/MSBuildTaskHost/Polyfills/StringSyntaxAttribute.cs deleted file mode 100644 index 2be24ae6499..00000000000 --- a/src/MSBuildTaskHost/Polyfills/StringSyntaxAttribute.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#if NET7_0_OR_GREATER - -using System.Runtime.CompilerServices; - -// This is a supporting forwarder for an internal polyfill API -[assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.StringSyntaxAttribute))] - -#else - -namespace System.Diagnostics.CodeAnalysis; - -[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] -internal sealed class StringSyntaxAttribute : Attribute -{ - /// Initializes the with the identifier of the syntax used. - /// The syntax identifier. - public StringSyntaxAttribute(string syntax) - { - Syntax = syntax; - Arguments = []; - } - - /// Initializes the with the identifier of the syntax used. - /// The syntax identifier. - /// Optional arguments associated with the specific syntax employed. - public StringSyntaxAttribute(string syntax, params object?[] arguments) - { - Syntax = syntax; - Arguments = arguments; - } - - /// Gets the identifier of the syntax used. - public string Syntax { get; } - - /// Optional arguments associated with the specific syntax employed. - public object?[] Arguments { get; } - - /// The syntax identifier for strings containing composite formats for string formatting. - public const string CompositeFormat = nameof(CompositeFormat); - - /// The syntax identifier for strings containing date format specifiers. - public const string DateOnlyFormat = nameof(DateOnlyFormat); - - /// The syntax identifier for strings containing date and time format specifiers. - public const string DateTimeFormat = nameof(DateTimeFormat); - - /// The syntax identifier for strings containing format specifiers. - public const string EnumFormat = nameof(EnumFormat); - - /// The syntax identifier for strings containing format specifiers. - public const string GuidFormat = nameof(GuidFormat); - - /// The syntax identifier for strings containing JavaScript Object Notation (JSON). - public const string Json = nameof(Json); - - /// The syntax identifier for strings containing numeric format specifiers. - public const string NumericFormat = nameof(NumericFormat); - - /// The syntax identifier for strings containing regular expressions. - public const string Regex = nameof(Regex); - - /// The syntax identifier for strings containing time format specifiers. - public const string TimeOnlyFormat = nameof(TimeOnlyFormat); - - /// The syntax identifier for strings containing format specifiers. - public const string TimeSpanFormat = nameof(TimeSpanFormat); - - /// The syntax identifier for strings containing URIs. - public const string Uri = nameof(Uri); - - /// The syntax identifier for strings containing XML. - public const string Xml = nameof(Xml); -} - -#endif From 66effaf2b29a16971bf1d3bb93d6f747ece40c7e Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 09:08:46 -0800 Subject: [PATCH 005/136] MSBuildTaskHost: Remove FEATURE_LEGACY_GETCURRENTDIRECTORY Since MSBuildTaskHost only targets .NET 3.5, it does not require conditional compilation for FEATURE_LEGACY_GETCURRENTDIRECTORY. It *always* includes the code path that provides an optimized GetCurrentDirectory on .NET Framework targets earlier than 4.6.2. --- src/Directory.BeforeCommon.targets | 2 -- .../Framework/NativeMethods.cs | 19 +++++++------------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets index 3e19b7e8ea0..a1662ac4a71 100644 --- a/src/Directory.BeforeCommon.targets +++ b/src/Directory.BeforeCommon.targets @@ -91,8 +91,6 @@ $(DefineConstants);FEATURE_HANDLEPROCESSCORRUPTEDSTATEEXCEPTIONS $(DefineConstants);FEATURE_HTTP_LISTENER $(DefineConstants);FEATURE_INSTALLED_MSBUILD - - $(DefineConstants);FEATURE_LEGACY_GETCURRENTDIRECTORY $(DefineConstants);FEATURE_LEGACY_GETFULLPATH $(DefineConstants);FEATURE_NAMED_PIPE_SECURITY_CONSTRUCTOR diff --git a/src/MSBuildTaskHost/Framework/NativeMethods.cs b/src/MSBuildTaskHost/Framework/NativeMethods.cs index e5858135cf0..0857a8de49d 100644 --- a/src/MSBuildTaskHost/Framework/NativeMethods.cs +++ b/src/MSBuildTaskHost/Framework/NativeMethods.cs @@ -1518,21 +1518,16 @@ internal static List> GetChildProcessIds(in } /// - /// Internal, optimized GetCurrentDirectory implementation that simply delegates to the native method + /// Internal, optimized GetCurrentDirectory implementation that simply delegates to the native method. /// - /// internal static unsafe string GetCurrentDirectory() { -#if FEATURE_LEGACY_GETCURRENTDIRECTORY - if (IsWindows) - { - int bufferSize = GetCurrentDirectoryWin32(0, null); - char* buffer = stackalloc char[bufferSize]; - int pathLength = GetCurrentDirectoryWin32(bufferSize, buffer); - return new string(buffer, startIndex: 0, length: pathLength); - } -#endif - return Directory.GetCurrentDirectory(); + int bufferSize = GetCurrentDirectoryWin32(nBufferLength: 0, lpBuffer: null); + + char* buffer = stackalloc char[bufferSize]; + int length = GetCurrentDirectoryWin32(bufferSize, buffer); + + return new string(buffer, startIndex: 0, length); } [SupportedOSPlatform("windows")] From acd2a60807626e279175b97eecdc12f5bb669b39 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 09:11:37 -0800 Subject: [PATCH 006/136] MSBuildTaskHost: Remove FEATURE_LEGACY_GETFULLPATH Since MSBuildTaskHost only targets .NET 3.5, it does not require conditional compilation for FEATURE_LEGACY_GETFULLPATH. It *always* includes the code path that provides an optimized GetFullPath on .NET Framework targets earlier than 4.6.2. --- src/Directory.BeforeCommon.targets | 2 -- src/MSBuildTaskHost/FileUtilities.cs | 35 +++++++++++----------------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets index a1662ac4a71..77da771a1b4 100644 --- a/src/Directory.BeforeCommon.targets +++ b/src/Directory.BeforeCommon.targets @@ -91,8 +91,6 @@ $(DefineConstants);FEATURE_HANDLEPROCESSCORRUPTEDSTATEEXCEPTIONS $(DefineConstants);FEATURE_HTTP_LISTENER $(DefineConstants);FEATURE_INSTALLED_MSBUILD - - $(DefineConstants);FEATURE_LEGACY_GETFULLPATH $(DefineConstants);FEATURE_NAMED_PIPE_SECURITY_CONSTRUCTOR $(DefineConstants);FEATURE_PERFORMANCE_COUNTERS $(DefineConstants);FEATURE_PIPE_SECURITY diff --git a/src/MSBuildTaskHost/FileUtilities.cs b/src/MSBuildTaskHost/FileUtilities.cs index 0ca435b0137..33af92040d7 100644 --- a/src/MSBuildTaskHost/FileUtilities.cs +++ b/src/MSBuildTaskHost/FileUtilities.cs @@ -463,36 +463,30 @@ internal static string NormalizePath(params string[] paths) private static string GetFullPath(string path) { -#if FEATURE_LEGACY_GETFULLPATH - if (NativeMethodsShared.IsWindows) - { - string uncheckedFullPath = NativeMethodsShared.GetFullPath(path); + string uncheckedFullPath = NativeMethodsShared.GetFullPath(path); - if (IsPathTooLong(uncheckedFullPath)) - { - string message = ResourceUtilities.FormatString(AssemblyResources.GetString("Shared.PathTooLong"), path, NativeMethodsShared.MaxPath); - throw new PathTooLongException(message); - } + if (IsPathTooLong(uncheckedFullPath)) + { + string message = ResourceUtilities.FormatString(AssemblyResources.GetString("Shared.PathTooLong"), path, NativeMethodsShared.MaxPath); + throw new PathTooLongException(message); + } - // We really don't care about extensions here, but Path.HasExtension provides a great way to - // invoke the CLR's invalid path checks (these are independent of path length) - Path.HasExtension(uncheckedFullPath); + // We really don't care about extensions here, but Path.HasExtension provides a great way to + // invoke the CLR's invalid path checks (these are independent of path length) + _ = Path.HasExtension(uncheckedFullPath); - // If we detect we are a UNC path then we need to use the regular get full path in order to do the correct checks for UNC formatting - // and security checks for strings like \\?\GlobalRoot - return IsUNCPath(uncheckedFullPath) ? Path.GetFullPath(uncheckedFullPath) : uncheckedFullPath; - } -#endif - return Path.GetFullPath(path); + // If we detect we are a UNC path then we need to use the regular get full path in order to do the correct checks for UNC formatting + // and security checks for strings like \\?\GlobalRoot + return IsUNCPath(uncheckedFullPath) ? Path.GetFullPath(uncheckedFullPath) : uncheckedFullPath; } -#if FEATURE_LEGACY_GETFULLPATH private static bool IsUNCPath(string path) { if (!NativeMethodsShared.IsWindows || !path.StartsWith(@"\\", StringComparison.Ordinal)) { return false; } + bool isUNC = true; for (int i = 2; i < path.Length - 1; i++) { @@ -521,7 +515,6 @@ From Path.cs in the CLR */ return isUNC || path.IndexOf(@"\\?\globalroot", StringComparison.OrdinalIgnoreCase) != -1; } -#endif // FEATURE_LEGACY_GETFULLPATH /// /// Normalizes all path separators (both forward and back slashes) to forward slashes. @@ -1595,7 +1588,7 @@ internal static void ReadExactly(this Stream stream, byte[] buffer, int offset, { throw new EndOfStreamException(); } - offset +=read; + offset += read; count -= read; } } From 423b462b5c6cc357251acc07b30fec5e25de6342 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 09:19:21 -0800 Subject: [PATCH 007/136] MSBuildTaskHost: Remove FEATURE_ASSEMBLY_LOCATION Since MSBuildTaskHost only targets .NET 3.5, System.Reflection.Assembly.Location is always available. --- src/Directory.BeforeCommon.targets | 1 - src/MSBuildTaskHost/FileUtilities.cs | 2 +- .../Framework/AssemblyUtilities.cs | 18 ------------------ src/MSBuildTaskHost/Framework/NativeMethods.cs | 18 ++---------------- 4 files changed, 3 insertions(+), 36 deletions(-) diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets index 77da771a1b4..c133f1797fe 100644 --- a/src/Directory.BeforeCommon.targets +++ b/src/Directory.BeforeCommon.targets @@ -77,7 +77,6 @@ $(DefineConstants);FEATURE_APPDOMAIN true $(DefineConstants);FEATURE_ASPNET_COMPILER - $(DefineConstants);FEATURE_ASSEMBLY_LOCATION $(DefineConstants);FEATURE_COMPILED_XSL $(DefineConstants);FEATURE_COMPILE_IN_TESTS $(DefineConstants);FEATURE_CONSTRAINED_EXECUTION diff --git a/src/MSBuildTaskHost/FileUtilities.cs b/src/MSBuildTaskHost/FileUtilities.cs index 33af92040d7..e503867f6f6 100644 --- a/src/MSBuildTaskHost/FileUtilities.cs +++ b/src/MSBuildTaskHost/FileUtilities.cs @@ -756,7 +756,7 @@ internal static bool HasExtension(string fileName, string[] allowedExtensions) /// /// Get the currently executing assembly path /// - internal static string ExecutingAssemblyPath => Path.GetFullPath(AssemblyUtilities.GetAssemblyLocation(typeof(FileUtilities).GetTypeInfo().Assembly)); + internal static string ExecutingAssemblyPath => Path.GetFullPath(typeof(FileUtilities).Assembly.Location); /// /// Determines the full path for the given file-spec. diff --git a/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs b/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs index 6b21b7d22eb..c6e0fc836ea 100644 --- a/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs +++ b/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs @@ -37,24 +37,6 @@ internal static class AssemblyUtilities public static Assembly EntryAssembly = GetEntryAssembly(); #endif - public static string GetAssemblyLocation(Assembly assembly) - { -#if FEATURE_ASSEMBLY_LOCATION - return assembly.Location; -#else - // Assembly.Location is only available in .netstandard1.5, but MSBuild needs to target 1.3. - // use reflection to access the property - Initialize(); - - if (s_assemblylocationProperty == null) - { - throw new NotSupportedException("Type Assembly does not have the Location property"); - } - - return (string)s_assemblylocationProperty.GetValue(assembly); -#endif - } - #if CLR2COMPATIBILITY /// /// Shim for the lack of in .NET 3.5. diff --git a/src/MSBuildTaskHost/Framework/NativeMethods.cs b/src/MSBuildTaskHost/Framework/NativeMethods.cs index 0857a8de49d..2162ea1b094 100644 --- a/src/MSBuildTaskHost/Framework/NativeMethods.cs +++ b/src/MSBuildTaskHost/Framework/NativeMethods.cs @@ -901,24 +901,10 @@ internal static bool OSUsesCaseSensitivePaths private static string s_frameworkCurrentPath; /// - /// Gets the currently running framework path + /// Gets the currently running framework path. /// internal static string FrameworkCurrentPath - { - get - { - if (s_frameworkCurrentPath == null) - { - var baseTypeLocation = AssemblyUtilities.GetAssemblyLocation(typeof(string).GetTypeInfo().Assembly); - - s_frameworkCurrentPath = - Path.GetDirectoryName(baseTypeLocation) - ?? string.Empty; - } - - return s_frameworkCurrentPath; - } - } + => s_frameworkCurrentPath ??= Path.GetDirectoryName(typeof(string).Assembly.Location) ?? string.Empty; /// /// Gets the base directory of all Mono frameworks From 1f14ea2603d329a8508557c56d511f757e5e9fb8 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 09:21:50 -0800 Subject: [PATCH 008/136] MSBuildTaskHost: Remove FEATURE_CULTUREINFO_GETCULTURES The FEATURE_CULTUREINFO_GETCULTURES code path is unused by MSBuildTaskHost. --- .../Framework/AssemblyUtilities.cs | 77 ------------------- 1 file changed, 77 deletions(-) diff --git a/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs b/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs index c6e0fc836ea..be1b3da23c9 100644 --- a/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs +++ b/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs @@ -5,11 +5,6 @@ using System.Globalization; using System.Reflection; -#if !FEATURE_CULTUREINFO_GETCULTURES -using System.Linq; -using Microsoft.Build.Framework; -#endif - #nullable disable namespace Microsoft.Build.Shared @@ -19,17 +14,6 @@ namespace Microsoft.Build.Shared /// internal static class AssemblyUtilities { -#if !FEATURE_CULTUREINFO_GETCULTURES - // True when the cached method info objects have been set. - private static bool s_initialized; - - // Cached method info - private static PropertyInfo s_assemblylocationProperty; - private static MethodInfo s_cultureInfoGetCultureMethod; - - private static Lazy s_validCultures = new Lazy(() => GetValidCultures(), true); -#endif - #if !CLR2COMPATIBILITY private static Lazy s_entryAssembly = new Lazy(() => GetEntryAssembly()); public static Assembly EntryAssembly => s_entryAssembly.Value; @@ -84,70 +68,9 @@ public static AssemblyName CloneIfPossible(this AssemblyName assemblyNameToClone } -#if !FEATURE_CULTUREINFO_GETCULTURES - public static bool CultureInfoHasGetCultures() - { - return s_cultureInfoGetCultureMethod != null; - } -#endif // !FEATURE_CULTUREINFO_GETCULTURES - - public static CultureInfo[] GetAllCultures() - { -#if FEATURE_CULTUREINFO_GETCULTURES - return CultureInfo.GetCultures(CultureTypes.AllCultures); -#else - Initialize(); - - if (!CultureInfoHasGetCultures()) - { - throw new NotSupportedException("CultureInfo does not have the method GetCultures"); - } - - return s_validCultures.Value; -#endif - } - -#if !FEATURE_CULTUREINFO_GETCULTURES - /// - /// Initialize static fields. Doesn't need to be thread safe. - /// - private static void Initialize() - { - if (s_initialized) - { - return; - } - - s_assemblylocationProperty = typeof(Assembly).GetProperty("Location", typeof(string)); - s_cultureInfoGetCultureMethod = typeof(CultureInfo).GetMethod("GetCultures"); - - s_initialized = true; - } -#endif // !FEATURE_CULTUREINFO_GETCULTURES - private static Assembly GetEntryAssembly() { return System.Reflection.Assembly.GetEntryAssembly(); } - -#if !FEATURE_CULTUREINFO_GETCULTURES - private static CultureInfo[] GetValidCultures() - { - var cultureTypesType = s_cultureInfoGetCultureMethod?.GetParameters().FirstOrDefault()?.ParameterType; - - FrameworkErrorUtilities.VerifyThrow(cultureTypesType?.Name == "CultureTypes" && - Enum.IsDefined(cultureTypesType, "AllCultures"), - "GetCulture is expected to accept CultureTypes.AllCultures"); - - var allCulturesEnumValue = Enum.Parse(cultureTypesType, "AllCultures", true); - - var cultures = s_cultureInfoGetCultureMethod.Invoke(null, [allCulturesEnumValue]) as CultureInfo[]; - - // CultureInfo.GetCultures should work if all reflection checks pass - FrameworkErrorUtilities.VerifyThrowInternalNull(cultures); - - return cultures; - } -#endif } } From a705a9331173d5581c1dfc3cec6616223178c0fa Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 09:31:49 -0800 Subject: [PATCH 009/136] MSBuildTaskHost: Remove FEATURE_APM The FEATURE_APM code path is always used by MSBuildTaskHost. .NET Framework 3.5 does support System.Threading.Tasks, so MSBuildTaskHost uses the older "asynchronous programming model (APM)". --- src/Directory.BeforeCommon.targets | 2 -- .../CommunicationsUtilities.cs | 22 ------------------- .../NodeEndpointOutOfProcBase.cs | 8 ------- 3 files changed, 32 deletions(-) diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets index c133f1797fe..2045c089e23 100644 --- a/src/Directory.BeforeCommon.targets +++ b/src/Directory.BeforeCommon.targets @@ -73,7 +73,6 @@ $(DefineConstants);FEATURE_APARTMENT_STATE - $(DefineConstants);FEATURE_APM $(DefineConstants);FEATURE_APPDOMAIN true $(DefineConstants);FEATURE_ASPNET_COMPILER @@ -82,7 +81,6 @@ $(DefineConstants);FEATURE_CONSTRAINED_EXECUTION $(DefineConstants);FEATURE_CODETASKFACTORY $(DefineConstants);FEATURE_CRYPTOGRAPHIC_FACTORY_ALGORITHM_NAMES - $(DefineConstants);FEATURE_CULTUREINFO_GETCULTURES $(DefineConstants);FEATURE_ENCODING_DEFAULT $(DefineConstants);FEATURE_ENVIRONMENT_SYSTEMDIRECTORY $(DefineConstants);FEATURE_FILE_TRACKER diff --git a/src/MSBuildTaskHost/CommunicationsUtilities.cs b/src/MSBuildTaskHost/CommunicationsUtilities.cs index 119464d22dd..47234555453 100644 --- a/src/MSBuildTaskHost/CommunicationsUtilities.cs +++ b/src/MSBuildTaskHost/CommunicationsUtilities.cs @@ -25,10 +25,6 @@ using System.Collections; using System.Collections.Frozen; using Microsoft.NET.StringTools; - -#endif -#if !FEATURE_APM -using System.Threading.Tasks; #endif #nullable disable @@ -926,24 +922,6 @@ out HandshakeResult result } #nullable disable -#if !FEATURE_APM - internal static async ValueTask ReadAsync(Stream stream, byte[] buffer, int bytesToRead) - { - int totalBytesRead = 0; - while (totalBytesRead < bytesToRead) - { - int bytesRead = await stream.ReadAsync(buffer.AsMemory(totalBytesRead, bytesToRead - totalBytesRead), CancellationToken.None); - if (bytesRead == 0) - { - return totalBytesRead; - } - totalBytesRead += bytesRead; - } - - return totalBytesRead; - } -#endif - /// /// Given the appropriate information, return the equivalent HandshakeOptions. /// diff --git a/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs b/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs index 038c7949dbb..ab971653490 100644 --- a/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs +++ b/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs @@ -389,15 +389,9 @@ private void PacketPumpProc() try { // Wait for a connection -#if FEATURE_APM IAsyncResult resultForConnection = localPipeServer.BeginWaitForConnection(null, null); CommunicationsUtilities.Trace("Waiting for connection {0} ms...", waitTimeRemaining); bool connected = resultForConnection.AsyncWaitHandle.WaitOne(waitTimeRemaining, false); -#else - Task connectionTask = localPipeServer.WaitForConnectionAsync(); - CommunicationsUtilities.Trace("Waiting for connection {0} ms...", waitTimeRemaining); - bool connected = connectionTask.Wait(waitTimeRemaining); -#endif if (!connected) { CommunicationsUtilities.Trace("Connection timed out waiting a host to contact us. Exiting comm thread."); @@ -406,9 +400,7 @@ private void PacketPumpProc() } CommunicationsUtilities.Trace("Parent started connecting. Reading handshake from parent"); -#if FEATURE_APM localPipeServer.EndWaitForConnection(resultForConnection); -#endif // The handshake protocol is a series of int exchanges. The host sends us a each component, and we // verify it. Afterwards, the host sends an "End of Handshake" signal, to which we respond in kind. From 8dd5bdedb1f1028977d3f1c282c8920249dade80 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 09:44:57 -0800 Subject: [PATCH 010/136] Remove FEATURE_* constants from .NET 3.5 builds unused by MSBuildTaskHost Many of the FEATURE_* conditional compilation constants defined for .NET 3.5 builds never appear in code compiled within MSBuildTaskHost. This change removes all of those for .NET 3.5 builds. --- src/Directory.BeforeCommon.targets | 35 ------------------------------ 1 file changed, 35 deletions(-) diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets index 2045c089e23..9863ac98e32 100644 --- a/src/Directory.BeforeCommon.targets +++ b/src/Directory.BeforeCommon.targets @@ -75,50 +75,15 @@ $(DefineConstants);FEATURE_APARTMENT_STATE $(DefineConstants);FEATURE_APPDOMAIN true - $(DefineConstants);FEATURE_ASPNET_COMPILER - $(DefineConstants);FEATURE_COMPILED_XSL - $(DefineConstants);FEATURE_COMPILE_IN_TESTS - $(DefineConstants);FEATURE_CONSTRAINED_EXECUTION - $(DefineConstants);FEATURE_CODETASKFACTORY - $(DefineConstants);FEATURE_CRYPTOGRAPHIC_FACTORY_ALGORITHM_NAMES - $(DefineConstants);FEATURE_ENCODING_DEFAULT - $(DefineConstants);FEATURE_ENVIRONMENT_SYSTEMDIRECTORY - $(DefineConstants);FEATURE_FILE_TRACKER - $(DefineConstants);FEATURE_GAC - $(DefineConstants);FEATURE_HANDLEPROCESSCORRUPTEDSTATEEXCEPTIONS - $(DefineConstants);FEATURE_HTTP_LISTENER - $(DefineConstants);FEATURE_INSTALLED_MSBUILD $(DefineConstants);FEATURE_NAMED_PIPE_SECURITY_CONSTRUCTOR - $(DefineConstants);FEATURE_PERFORMANCE_COUNTERS $(DefineConstants);FEATURE_PIPE_SECURITY - $(DefineConstants);FEATURE_PFX_SIGNING - $(DefineConstants);FEATURE_REFLECTION_EMIT_DEBUG_INFO - $(DefineConstants);FEATURE_REGISTRY_TOOLSETS - $(DefineConstants);FEATURE_REGISTRY_SDKS - $(DefineConstants);FEATURE_REGISTRYHIVE_DYNDATA - $(DefineConstants);FEATURE_RESGEN - $(DefineConstants);FEATURE_RESGENCACHE - $(DefineConstants);FEATURE_RESOURCE_EXPOSURE - $(DefineConstants);FEATURE_RESXREADER_LIVEDESERIALIZATION - $(DefineConstants);FEATURE_RUN_EXE_IN_TESTS $(DefineConstants);FEATURE_SECURITY_PERMISSIONS $(DefineConstants);FEATURE_SECURITY_PRINCIPAL_WINDOWS - $(DefineConstants);FEATURE_STRONG_NAMES - $(DefineConstants);FEATURE_SYSTEM_CONFIGURATION true - $(DefineConstants);FEATURE_TASK_GENERATERESOURCES $(DefineConstants);FEATURE_THREAD_ABORT - $(DefineConstants);FEATURE_THREAD_CULTURE - $(DefineConstants);FEATURE_MULTIPLE_TOOLSETS - $(DefineConstants);FEATURE_NODE_REUSE $(DefineConstants);FEATURE_NET35_TASKHOST - $(DefineConstants);FEATURE_XAML_TYPES - $(DefineConstants);FEATURE_XAMLTASKFACTORY true - $(DefineConstants);FEATURE_XML_SCHEMA_VALIDATION - $(DefineConstants);FEATURE_WIN32_REGISTRY $(DefineConstants);FEATURE_VISUALSTUDIOSETUP - $(DefineConstants);FEATURE_MSCOREE From de8dfaff17b5ac6a333ab9b3022b88f64341fa2d Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 09:49:20 -0800 Subject: [PATCH 011/136] MSBuildTaskHost: Remove FEATURE_PIPE_SECURITY and related constant The FEATURE_PIPE_SECURITY and FEATURE_NAMED_PIPE_SECURITY_CONSTRUCTOR code paths is always used by MSBuildTaskHost. --- src/Directory.BeforeCommon.targets | 2 -- .../NodeEndpointOutOfProcBase.cs | 28 ++++--------------- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets index 9863ac98e32..13be0996066 100644 --- a/src/Directory.BeforeCommon.targets +++ b/src/Directory.BeforeCommon.targets @@ -75,8 +75,6 @@ $(DefineConstants);FEATURE_APARTMENT_STATE $(DefineConstants);FEATURE_APPDOMAIN true - $(DefineConstants);FEATURE_NAMED_PIPE_SECURITY_CONSTRUCTOR - $(DefineConstants);FEATURE_PIPE_SECURITY $(DefineConstants);FEATURE_SECURITY_PERMISSIONS $(DefineConstants);FEATURE_SECURITY_PRINCIPAL_WINDOWS true diff --git a/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs b/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs index ab971653490..fc8cb0575a2 100644 --- a/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs +++ b/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs @@ -18,13 +18,10 @@ using System.IO; using System.Collections.Generic; -#if FEATURE_SECURITY_PERMISSIONS || FEATURE_PIPE_SECURITY +#if FEATURE_SECURITY_PERMISSIONS using System.Security.AccessControl; #endif -#if FEATURE_PIPE_SECURITY && FEATURE_NAMED_PIPE_SECURITY_CONSTRUCTOR using System.Security.Principal; - -#endif #if NET451_OR_GREATER || NETCOREAPP using System.Threading.Tasks; #endif @@ -137,7 +134,7 @@ internal abstract class NodeEndpointOutOfProcBase : INodeEndpoint nameof(HandshakeComponents.FileVersionPrivate)]; #endif -#endregion + #endregion #region INodeEndpoint Events @@ -236,7 +233,6 @@ internal void InternalConstruct(string pipeName = null, byte parentPacketVersion pipeName ??= NamedPipeUtil.GetPlatformSpecificPipeName(); -#if FEATURE_PIPE_SECURITY && FEATURE_NAMED_PIPE_SECURITY_CONSTRUCTOR SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner; PipeSecurity security = new PipeSecurity(); @@ -263,20 +259,6 @@ internal void InternalConstruct(string pipeName = null, byte parentPacketVersion PipeBufferSize, // Default output buffer security, HandleInheritability.None); -#else - _pipeServer = new NamedPipeServerStream( - pipeName, - PipeDirection.InOut, - 1, // Only allow one connection at a time. - PipeTransmissionMode.Byte, - PipeOptions.Asynchronous | PipeOptions.WriteThrough -#if FEATURE_PIPEOPTIONS_CURRENTUSERONLY - | PipeOptions.CurrentUserOnly -#endif - , - PipeBufferSize, // Default input buffer - PipeBufferSize); // Default output buffer -#endif } #endregion @@ -413,7 +395,7 @@ private void PacketPumpProc() int index = 0; foreach (var component in handshakeComponents.EnumerateComponents()) { - + if (!_pipeServer.TryReadIntForHandshake( byteToAccept: index == 0 ? (byte?)CommunicationsUtilities.handshakeVersion : null, /* this will disconnect a < 16.8 host; it expects leading 00 or F5 or 06. 0x00 is a wildcard */ #if NETCOREAPP2_1_OR_GREATER @@ -802,8 +784,8 @@ private void RunReadLoop( while (!exitLoop); } -#endregion + #endregion -#endregion + #endregion } } From 2ce4a7bb7efb93e3a2d4e8539ae3912994cb9dd4 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 09:50:56 -0800 Subject: [PATCH 012/136] MSBuildTaskHost: Remove FEATURE_SECURITY_PERMISSIONS The FEATURE_SECURITY_PERMISSIONS code paths are always available in MSBuildTaskHost. --- src/Directory.BeforeCommon.targets | 1 - src/MSBuildTaskHost/ExceptionHandling.cs | 4 +--- src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs | 5 +---- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets index 13be0996066..491c1b4470f 100644 --- a/src/Directory.BeforeCommon.targets +++ b/src/Directory.BeforeCommon.targets @@ -75,7 +75,6 @@ $(DefineConstants);FEATURE_APARTMENT_STATE $(DefineConstants);FEATURE_APPDOMAIN true - $(DefineConstants);FEATURE_SECURITY_PERMISSIONS $(DefineConstants);FEATURE_SECURITY_PRINCIPAL_WINDOWS true $(DefineConstants);FEATURE_THREAD_ABORT diff --git a/src/MSBuildTaskHost/ExceptionHandling.cs b/src/MSBuildTaskHost/ExceptionHandling.cs index 6ed43000558..3db090854ae 100644 --- a/src/MSBuildTaskHost/ExceptionHandling.cs +++ b/src/MSBuildTaskHost/ExceptionHandling.cs @@ -196,9 +196,7 @@ internal static bool IsIoRelatedException(Exception e) internal static bool IsXmlException(Exception e) { return e is XmlException -#if FEATURE_SECURITY_PERMISSIONS - || e is System.Security.XmlSyntaxException -#endif + || e is XmlSyntaxException || e is XmlSchemaException || e is UriFormatException; // XmlTextReader for example uses this under the covers } diff --git a/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs b/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs index fc8cb0575a2..6d2609f1768 100644 --- a/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs +++ b/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs @@ -18,9 +18,7 @@ using System.IO; using System.Collections.Generic; -#if FEATURE_SECURITY_PERMISSIONS using System.Security.AccessControl; -#endif using System.Security.Principal; #if NET451_OR_GREATER || NETCOREAPP using System.Threading.Tasks; @@ -443,7 +441,7 @@ private void PacketPumpProc() CommunicationsUtilities.Trace("Successfully connected to parent."); _pipeServer.WriteEndOfHandshakeSignal(); -#if FEATURE_SECURITY_PERMISSIONS + // We will only talk to a host that was started by the same user as us. Even though the pipe access is set to only allow this user, we want to ensure they // haven't attempted to change those permissions out from under us. This ensures that the only way they can truly gain access is to be impersonating the // user we were started by. @@ -457,7 +455,6 @@ private void PacketPumpProc() gotValidConnection = false; continue; } -#endif } } } From d1ae88edf9c39896e5098a4921cdf09de659ff4e Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 09:53:12 -0800 Subject: [PATCH 013/136] MSBuildTaskHost: Remove FEATURE_SECURITY_PRINCIPAL_WINDOWS The FEATURE_SECURITY_PRINCIPAL_WINDOWS code paths are always available in MSBuildTaskHost. --- src/Directory.BeforeCommon.targets | 1 - src/MSBuildTaskHost/CommunicationsUtilities.cs | 5 ----- 2 files changed, 6 deletions(-) diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets index 491c1b4470f..cb24b651fc6 100644 --- a/src/Directory.BeforeCommon.targets +++ b/src/Directory.BeforeCommon.targets @@ -75,7 +75,6 @@ $(DefineConstants);FEATURE_APARTMENT_STATE $(DefineConstants);FEATURE_APPDOMAIN true - $(DefineConstants);FEATURE_SECURITY_PRINCIPAL_WINDOWS true $(DefineConstants);FEATURE_THREAD_ABORT $(DefineConstants);FEATURE_NET35_TASKHOST diff --git a/src/MSBuildTaskHost/CommunicationsUtilities.cs b/src/MSBuildTaskHost/CommunicationsUtilities.cs index 47234555453..86d6c8384e5 100644 --- a/src/MSBuildTaskHost/CommunicationsUtilities.cs +++ b/src/MSBuildTaskHost/CommunicationsUtilities.cs @@ -8,10 +8,7 @@ using System.IO; using System.IO.Pipes; using System.Runtime.InteropServices; -#if FEATURE_SECURITY_PRINCIPAL_WINDOWS || RUNTIME_TYPE_NETCORE using System.Security.Principal; -#endif - using System.Reflection; using System.Security.Cryptography; using System.Text; @@ -1013,7 +1010,6 @@ internal static HandshakeOptions GetHandshakeOptions( context |= HandshakeOptions.LowPriority; } -#if FEATURE_SECURITY_PRINCIPAL_WINDOWS || RUNTIME_TYPE_NETCORE // If we are running in elevated privs, we will only accept a handshake from an elevated process as well. // Both the client and the host will calculate this separately, and the idea is that if they come out the same // then we can be sufficiently confident that the other side has the same elevation level as us. This is complementary @@ -1026,7 +1022,6 @@ internal static HandshakeOptions GetHandshakeOptions( { context |= HandshakeOptions.Administrator; } -#endif return context; } From b50526d6e47609e575e55d7f97c06e6fb676de4d Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 09:53:39 -0800 Subject: [PATCH 014/136] MSBuildTaskHost: Remove FEATURE_THREAD_ABORT The FEATURE_THREAD_ABORT code paths are always available in MSBuildTaskHost. --- src/Directory.BeforeCommon.targets | 1 - src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets index cb24b651fc6..3a441629b78 100644 --- a/src/Directory.BeforeCommon.targets +++ b/src/Directory.BeforeCommon.targets @@ -76,7 +76,6 @@ $(DefineConstants);FEATURE_APPDOMAIN true true - $(DefineConstants);FEATURE_THREAD_ABORT $(DefineConstants);FEATURE_NET35_TASKHOST true $(DefineConstants);FEATURE_VISUALSTUDIOSETUP diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs index aa7afd2efa4..5fb43a0f3f7 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs @@ -795,11 +795,9 @@ private void CancelTask() // It means we're already in the process of shutting down - Wait for the taskCompleteEvent to be set instead. if (_isTaskExecuting) { -#if FEATURE_THREAD_ABORT // The thread will be terminated crudely so our environment may be trashed but it's ok since we are // shutting down ASAP. _taskRunnerThread.Abort(); -#endif } } } From b3a33806a8c1d88001548ee318d09f5d884e4771 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 09:55:54 -0800 Subject: [PATCH 015/136] MSBuildTaskHost: Remove FEATURE_VISUALSTUDIOSETUP The FEATURE_VISUALSTUDIOSETUP code paths are never available in MSBuildTaskHost. This constant is always removed from .NET 3.5 builds. --- src/Directory.BeforeCommon.targets | 1 - .../Framework/VisualStudioLocationHelper.cs | 86 ------------------- 2 files changed, 87 deletions(-) diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets index 3a441629b78..abcf9231e8b 100644 --- a/src/Directory.BeforeCommon.targets +++ b/src/Directory.BeforeCommon.targets @@ -78,7 +78,6 @@ true $(DefineConstants);FEATURE_NET35_TASKHOST true - $(DefineConstants);FEATURE_VISUALSTUDIOSETUP diff --git a/src/MSBuildTaskHost/Framework/VisualStudioLocationHelper.cs b/src/MSBuildTaskHost/Framework/VisualStudioLocationHelper.cs index a747ea8bd1a..4a35360b32f 100644 --- a/src/MSBuildTaskHost/Framework/VisualStudioLocationHelper.cs +++ b/src/MSBuildTaskHost/Framework/VisualStudioLocationHelper.cs @@ -3,10 +3,6 @@ using System; using System.Collections.Generic; -#if FEATURE_VISUALSTUDIOSETUP -using System.Runtime.InteropServices; -using Microsoft.VisualStudio.Setup.Configuration; -#endif #nullable disable @@ -19,10 +15,6 @@ namespace Microsoft.Build.Shared /// internal class VisualStudioLocationHelper { -#if FEATURE_VISUALSTUDIOSETUP - private const int REGDB_E_CLASSNOTREG = unchecked((int)0x80040154); -#endif // FEATURE_VISUALSTUDIOSETUP - /// /// Query the Visual Studio setup API to get instances of Visual Studio installed /// on the machine. Will not include anything before Visual Studio "15". @@ -31,86 +23,8 @@ internal class VisualStudioLocationHelper internal static IList GetInstances() { var validInstances = new List(); - -#if FEATURE_VISUALSTUDIOSETUP - try - { - // This code is not obvious. See the sample (link above) for reference. - var query = (ISetupConfiguration2)GetQuery(); - var e = query.EnumAllInstances(); - - int fetched; - var instances = new ISetupInstance[1]; - do - { - // Call e.Next to query for the next instance (single item or nothing returned). - e.Next(1, instances, out fetched); - if (fetched <= 0) - { - continue; - } - - var instance = instances[0]; - var state = ((ISetupInstance2)instance).GetState(); - Version version; - - try - { - version = new Version(instance.GetInstallationVersion()); - } - catch (FormatException) - { - continue; - } - - // If the install was complete and a valid version, consider it. - if (state == InstanceState.Complete) - { - validInstances.Add(new VisualStudioInstance( - instance.GetDisplayName(), - instance.GetInstallationPath(), - version)); - } - } while (fetched > 0); - } - catch (COMException) - { } - catch (DllNotFoundException) - { - // This is OK, VS "15" or greater likely not installed. - } -#endif return validInstances; } - -#if FEATURE_VISUALSTUDIOSETUP - private static ISetupConfiguration GetQuery() - { - try - { - // Try to CoCreate the class object. - return new SetupConfiguration(); - } - catch (COMException ex) when (ex.ErrorCode == REGDB_E_CLASSNOTREG) - { - // Try to get the class object using app-local call. - ISetupConfiguration query; - var result = GetSetupConfiguration(out query, IntPtr.Zero); - - if (result < 0) - { - throw new COMException("Failed to get query", result); - } - - return query; - } - } - - [DllImport("Microsoft.VisualStudio.Setup.Configuration.Native.dll", ExactSpelling = true, PreserveSig = true)] - private static extern int GetSetupConfiguration( - [MarshalAs(UnmanagedType.Interface), Out] out ISetupConfiguration configuration, - IntPtr reserved); -#endif } /// From 7744be1e181f0ed5cee46d4b99aae74d13c5602b Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 10:21:53 -0800 Subject: [PATCH 016/136] Remove Feature* properties from .NET 3.5 builds The FeatureAppDomain, FeatureSystemConfiguration, and FeatureXamlTypes properties are not used in .NET 3.5 builds. NOTE: It seems that FeatureAppDomain is the only one of these properties that are used anywhere. However, it is used in a target in Microsoft.Build, which is not built for .NET 3.5. --- src/Directory.BeforeCommon.targets | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets index abcf9231e8b..569fe9551f9 100644 --- a/src/Directory.BeforeCommon.targets +++ b/src/Directory.BeforeCommon.targets @@ -74,10 +74,7 @@ $(DefineConstants);FEATURE_APARTMENT_STATE $(DefineConstants);FEATURE_APPDOMAIN - true - true $(DefineConstants);FEATURE_NET35_TASKHOST - true From d61cf7c07b1da9271e4a395d6fc852c2a3a36f23 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 10:25:33 -0800 Subject: [PATCH 017/136] MSBuildTaskHost: Remove FEATURE_REPORTFILEACCESSES The FEATURE_REPORTFILEACCESSES code paths are not available in MSBuildTaskHost. This constant was never included in .NET 3.5 builds. --- .../MSBuild/OutOfProcTaskHostNode.cs | 34 ++----------------- src/MSBuildTaskHost/TaskHostTaskComplete.cs | 29 ---------------- 2 files changed, 2 insertions(+), 61 deletions(-) diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs index 5fb43a0f3f7..928c49bd661 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs @@ -171,13 +171,6 @@ internal class OutOfProcTaskHostNode : private RegisteredTaskObjectCacheBase _registeredTaskObjectCache; #endif -#if FEATURE_REPORTFILEACCESSES - /// - /// The file accesses reported by the most recently completed task. - /// - private List _fileAccessData = new List(); -#endif - /// /// Constructor. /// @@ -544,17 +537,6 @@ public override bool IsTaskInputLoggingEnabled return _taskHost._currentConfiguration.IsTaskInputLoggingEnabled; } } - -#if FEATURE_REPORTFILEACCESSES - /// - /// Reports a file access from a task. - /// - /// The file access to report. - public void ReportFileAccess(FileAccessData fileAccessData) - { - _taskHost._fileAccessData.Add(fileAccessData); - } -#endif } public EngineServices EngineServices { get; } @@ -988,12 +970,7 @@ private void RunTask(object state) lock (_taskCompleteLock) { - _taskCompletePacket = new TaskHostTaskComplete( - taskResult, -#if FEATURE_REPORTFILEACCESSES - _fileAccessData, -#endif - currentEnvironment); + _taskCompletePacket = new TaskHostTaskComplete(taskResult, currentEnvironment); } #if FEATURE_APPDOMAIN @@ -1014,18 +991,11 @@ private void RunTask(object state) // Create a minimal taskCompletePacket to carry the exception so that the TaskHostTask does not hang while waiting _taskCompletePacket = new TaskHostTaskComplete( new OutOfProcTaskHostTaskResult(TaskCompleteType.CrashedAfterExecution, e), -#if FEATURE_REPORTFILEACCESSES - _fileAccessData, -#endif - null); + buildProcessEnvironment: null); } } finally { -#if FEATURE_REPORTFILEACCESSES - _fileAccessData = new List(); -#endif - // Call CleanupTask to unload any domains and other necessary cleanup in the taskWrapper _taskWrapper.CleanupTask(); diff --git a/src/MSBuildTaskHost/TaskHostTaskComplete.cs b/src/MSBuildTaskHost/TaskHostTaskComplete.cs index 6bded722522..57ca8ed7e83 100644 --- a/src/MSBuildTaskHost/TaskHostTaskComplete.cs +++ b/src/MSBuildTaskHost/TaskHostTaskComplete.cs @@ -4,9 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -#if !CLR2COMPATIBILITY && FEATURE_REPORTFILEACCESSES -using Microsoft.Build.Experimental.FileAccess; -#endif using Microsoft.Build.Shared; #nullable disable @@ -52,10 +49,6 @@ internal enum TaskCompleteType /// internal class TaskHostTaskComplete : INodePacket { -#if FEATURE_REPORTFILEACCESSES - private List _fileAccessData; -#endif - /// /// Result of the task's execution. /// @@ -100,9 +93,6 @@ internal class TaskHostTaskComplete : INodePacket #pragma warning restore CS1572 // XML comment has a param tag, but there is no parameter by that name public TaskHostTaskComplete( OutOfProcTaskHostTaskResult result, -#if FEATURE_REPORTFILEACCESSES - List fileAccessData, -#endif IDictionary buildProcessEnvironment) { ErrorUtilities.VerifyThrowInternalNull(result); @@ -111,9 +101,6 @@ public TaskHostTaskComplete( _taskException = result.TaskException; _taskExceptionMessage = result.ExceptionMessage; _taskExceptionMessageArgs = result.ExceptionMessageArgs; -#if FEATURE_REPORTFILEACCESSES - _fileAccessData = fileAccessData; -#endif if (result.FinalParameterValues != null) { @@ -220,17 +207,6 @@ public NodePacketType Type get { return NodePacketType.TaskHostTaskComplete; } } -#if FEATURE_REPORTFILEACCESSES - /// - /// Gets the file accesses reported by the task. - /// - public List FileAccessData - { - [DebuggerStepThrough] - get => _fileAccessData; - } -#endif - /// /// Translates the packet to/from binary form. /// @@ -243,13 +219,8 @@ public void Translate(ITranslator translator) translator.Translate(ref _taskExceptionMessageArgs); translator.TranslateDictionary(ref _taskOutputParameters, StringComparer.OrdinalIgnoreCase, TaskParameter.FactoryForDeserialization); translator.TranslateDictionary(ref _buildProcessEnvironment, StringComparer.OrdinalIgnoreCase); -#if FEATURE_REPORTFILEACCESSES - translator.Translate(ref _fileAccessData, - (ITranslator translator, ref FileAccessData data) => ((ITranslatable)data).Translate(translator)); -#else bool hasFileAccessData = false; translator.Translate(ref hasFileAccessData); -#endif } /// From aeb3e746066fa954ffa9afd3dfc1d767923b54a4 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 10:32:31 -0800 Subject: [PATCH 018/136] MSBuildTaskHost: Remove AppContext support from BuildEnvironmentHelper BuildEnvironmentHelper includes a check for AppContext.BaseDirectory that is always returns null on .NET 3.5 and the MSBuildTaskHost. This change removes that check and related code. --- src/MSBuildTaskHost/BuildEnvironmentHelper.cs | 40 +------------------ 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs index 3a0c945eb7d..c34ecbd2830 100644 --- a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs +++ b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs @@ -33,11 +33,6 @@ internal sealed class BuildEnvironmentHelper /// private static readonly string[] s_msBuildProcess = { "MSBUILD", "MSBUILDTASKHOST" }; - /// - /// Name of MSBuild executable files. - /// - private static readonly string[] s_msBuildExeNames = { "MSBuild.exe", "MSBuild.dll" }; - /// /// Gets the cached Build Environment instance. /// @@ -83,8 +78,7 @@ private static BuildEnvironment Initialize() TryFromMSBuildProcess, TryFromMSBuildAssembly, TryFromDevConsole, - TryFromSetupApi, - TryFromAppContextBaseDirectory + TryFromSetupApi }; foreach (var location in possibleLocations) @@ -317,25 +311,6 @@ private static BuildEnvironment TryFromSetupApi() visualStudioPath: instances[0].Path); } - private static BuildEnvironment TryFromAppContextBaseDirectory() - { - // Assemblies compiled against anything older than .NET 4.0 won't have a System.AppContext - // Try the base directory that the assembly resolver uses to probe for assemblies. - // Under certain scenarios the assemblies are loaded from spurious locations like the NuGet package cache - // but the toolset files are copied to the app's directory via "contentFiles". - - var appContextBaseDirectory = s_getAppContextBaseDirectory(); - if (string.IsNullOrEmpty(appContextBaseDirectory)) - { - return null; - } - - // Look for possible MSBuild exe names in the AppContextBaseDirectory - return s_msBuildExeNames - .Select((name) => TryFromStandaloneMSBuildExe(Path.Combine(appContextBaseDirectory, name))) - .FirstOrDefault(env => env != null); - } - private static BuildEnvironment TryFromStandaloneMSBuildExe(string msBuildExePath) { if (!string.IsNullOrEmpty(msBuildExePath) && FileSystems.Default.FileExists(msBuildExePath)) @@ -445,15 +420,6 @@ private static string GetExecutingAssemblyPath() return FileUtilities.ExecutingAssemblyPath; } - private static string GetAppContextBaseDirectory() - { -#if !CLR2COMPATIBILITY // Assemblies compiled against anything older than .NET 4.0 won't have a System.AppContext - return AppContext.BaseDirectory; -#else - return null; -#endif - } - private static string GetEnvironmentVariable(string variable) { return Environment.GetEnvironmentVariable(variable); @@ -463,14 +429,13 @@ private static string GetEnvironmentVariable(string variable) /// Resets the current singleton instance (for testing). /// internal static void ResetInstance_ForUnitTestsOnly(Func getProcessFromRunningProcess = null, - Func getExecutingAssemblyPath = null, Func getAppContextBaseDirectory = null, + Func getExecutingAssemblyPath = null, Func> getVisualStudioInstances = null, Func getEnvironmentVariable = null, Func runningTests = null) { s_getProcessFromRunningProcess = getProcessFromRunningProcess ?? GetProcessFromRunningProcess; s_getExecutingAssemblyPath = getExecutingAssemblyPath ?? GetExecutingAssemblyPath; - s_getAppContextBaseDirectory = getAppContextBaseDirectory ?? GetAppContextBaseDirectory; s_getVisualStudioInstances = getVisualStudioInstances ?? VisualStudioLocationHelper.GetInstances; s_getEnvironmentVariable = getEnvironmentVariable ?? GetEnvironmentVariable; @@ -492,7 +457,6 @@ internal static void ResetInstance_ForUnitTestsOnly(BuildEnvironment buildEnviro private static Func s_getProcessFromRunningProcess = GetProcessFromRunningProcess; private static Func s_getExecutingAssemblyPath = GetExecutingAssemblyPath; - private static Func s_getAppContextBaseDirectory = GetAppContextBaseDirectory; private static Func> s_getVisualStudioInstances = VisualStudioLocationHelper.GetInstances; private static Func s_getEnvironmentVariable = GetEnvironmentVariable; private static Func s_runningTests = CheckIfRunningTests; From 08b7d0c53da2b3f264c5e63f9132d4f8befb7282 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 10:36:32 -0800 Subject: [PATCH 019/136] MSBuildTaskHost: Remove VS Setup API from BuildEnviromentHelper VisualStudioLocationHelper.GetInstances() always returns an empty list on .NET 3.5. So, BuildEnvironmentHelper.TryFromSetupApi (the only caller) can be removed from MSBuildTaskHost along with all of VisualStuiodLocationHelper. --- src/MSBuildTaskHost/BuildEnvironmentHelper.cs | 39 +------------ .../Framework/VisualStudioLocationHelper.cs | 57 ------------------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - 3 files changed, 1 insertion(+), 96 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/VisualStudioLocationHelper.cs diff --git a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs index c34ecbd2830..2ae96273001 100644 --- a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs +++ b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs @@ -77,8 +77,7 @@ private static BuildEnvironment Initialize() TryFromVisualStudioProcess, TryFromMSBuildProcess, TryFromMSBuildAssembly, - TryFromDevConsole, - TryFromSetupApi + TryFromDevConsole }; foreach (var location in possibleLocations) @@ -278,39 +277,6 @@ private static BuildEnvironment TryFromDevConsole() visualStudioPath: vsInstallDir); } - private static BuildEnvironment TryFromSetupApi() - { - if (s_runningTests()) - { - // If running unit tests, then don't try to get the build environment from MSBuild installed on the machine - // (we should be using the locally built MSBuild instead) - return null; - } - - Version v = new Version(CurrentVisualStudioVersion); - var instances = s_getVisualStudioInstances() - .Where(i => i.Version.Major == v.Major && FileSystems.Default.DirectoryExists(i.Path)) - .ToList(); - - if (instances.Count == 0) - { - return null; - } - - if (instances.Count > 1) - { - // TODO: Warn user somehow. We may have picked the wrong one. - } - - return new BuildEnvironment( - BuildEnvironmentMode.VisualStudio, - GetMSBuildExeFromVsRoot(instances[0].Path), - runningTests: false, - runningInMSBuildExe: false, - runningInVisualStudio: false, - visualStudioPath: instances[0].Path); - } - private static BuildEnvironment TryFromStandaloneMSBuildExe(string msBuildExePath) { if (!string.IsNullOrEmpty(msBuildExePath) && FileSystems.Default.FileExists(msBuildExePath)) @@ -430,13 +396,11 @@ private static string GetEnvironmentVariable(string variable) /// internal static void ResetInstance_ForUnitTestsOnly(Func getProcessFromRunningProcess = null, Func getExecutingAssemblyPath = null, - Func> getVisualStudioInstances = null, Func getEnvironmentVariable = null, Func runningTests = null) { s_getProcessFromRunningProcess = getProcessFromRunningProcess ?? GetProcessFromRunningProcess; s_getExecutingAssemblyPath = getExecutingAssemblyPath ?? GetExecutingAssemblyPath; - s_getVisualStudioInstances = getVisualStudioInstances ?? VisualStudioLocationHelper.GetInstances; s_getEnvironmentVariable = getEnvironmentVariable ?? GetEnvironmentVariable; // Tests which specifically test the BuildEnvironmentHelper need it to be able to act as if it is not running tests @@ -457,7 +421,6 @@ internal static void ResetInstance_ForUnitTestsOnly(BuildEnvironment buildEnviro private static Func s_getProcessFromRunningProcess = GetProcessFromRunningProcess; private static Func s_getExecutingAssemblyPath = GetExecutingAssemblyPath; - private static Func> s_getVisualStudioInstances = VisualStudioLocationHelper.GetInstances; private static Func s_getEnvironmentVariable = GetEnvironmentVariable; private static Func s_runningTests = CheckIfRunningTests; diff --git a/src/MSBuildTaskHost/Framework/VisualStudioLocationHelper.cs b/src/MSBuildTaskHost/Framework/VisualStudioLocationHelper.cs deleted file mode 100644 index 4a35360b32f..00000000000 --- a/src/MSBuildTaskHost/Framework/VisualStudioLocationHelper.cs +++ /dev/null @@ -1,57 +0,0 @@ -// 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; - -#nullable disable - -namespace Microsoft.Build.Shared -{ - /// - /// Helper class to wrap the Microsoft.VisualStudio.Setup.Configuration.Interop API to query - /// Visual Studio setup for instances installed on the machine. - /// Code derived from sample: https://code.msdn.microsoft.com/Visual-Studio-Setup-0cedd331 - /// - internal class VisualStudioLocationHelper - { - /// - /// Query the Visual Studio setup API to get instances of Visual Studio installed - /// on the machine. Will not include anything before Visual Studio "15". - /// - /// Enumerable list of Visual Studio instances - internal static IList GetInstances() - { - var validInstances = new List(); - return validInstances; - } - } - - /// - /// Wrapper class to represent an installed instance of Visual Studio. - /// - internal class VisualStudioInstance - { - /// - /// Version of the Visual Studio Instance - /// - internal Version Version { get; } - - /// - /// Path to the Visual Studio installation - /// - internal string Path { get; } - - /// - /// Full name of the Visual Studio instance with SKU name - /// - internal string Name { get; } - - internal VisualStudioInstance(string name, string path, Version version) - { - Name = name; - Path = path; - Version = version; - } - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 6e64c260ded..69a627be8fb 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -94,7 +94,6 @@ - From 228ec3790bb5e1ce07525d3a30ef3bc02be8686f Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 10:40:05 -0800 Subject: [PATCH 020/136] MSBuildTaskHost: Remove RUNTIME_TYPE_NETCORE code paths RUNTIME_TYPE_NETCORE code paths aren't ever compiled in .NET 3.5 builds. --- src/MSBuildTaskHost/BuildEnvironmentHelper.cs | 18 ------------------ src/MSBuildTaskHost/CommunicationsUtilities.cs | 7 ++----- .../Framework/AssemblyUtilities.cs | 6 ------ src/MSBuildTaskHost/Framework/NativeMethods.cs | 13 +------------ 4 files changed, 3 insertions(+), 41 deletions(-) diff --git a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs index 2ae96273001..6610257dcb6 100644 --- a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs +++ b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs @@ -366,19 +366,7 @@ private static bool IsProcessInList(string processName, string[] processList) private static string GetProcessFromRunningProcess() { -#if RUNTIME_TYPE_NETCORE - // The EntryAssembly property can return null when a managed assembly has been loaded from - // an unmanaged application (for example, using custom CLR hosting). - if (AssemblyUtilities.EntryAssembly == null) - { - return EnvironmentUtilities.ProcessPath; - } - - return AssemblyUtilities.GetAssemblyLocation(AssemblyUtilities.EntryAssembly); -#else - return EnvironmentUtilities.ProcessPath; -#endif } private static string GetExecutingAssemblyPath() @@ -539,13 +527,7 @@ NativeMethodsShared.ProcessorArchitectures.X64 or NativeMethodsShared.ProcessorA MSBuildToolsDirectory32 = MSBuildToolsDirectoryRoot; MSBuildToolsDirectory64 = existsCheck(potentialAmd64FromX86) ? Path.Combine(MSBuildToolsDirectoryRoot, "amd64") : CurrentMSBuildToolsDirectory; -#if RUNTIME_TYPE_NETCORE - // Fall back to "current" for any architecture since .NET SDK doesn't - // support cross-arch task invocations. - MSBuildToolsDirectoryArm64 = existsCheck(potentialARM64FromX86) ? Path.Combine(MSBuildToolsDirectoryRoot, "arm64") : CurrentMSBuildToolsDirectory; -#else MSBuildToolsDirectoryArm64 = existsCheck(potentialARM64FromX86) ? Path.Combine(MSBuildToolsDirectoryRoot, "arm64") : null; -#endif } MSBuildExtensionsPath = mode == BuildEnvironmentMode.VisualStudio diff --git a/src/MSBuildTaskHost/CommunicationsUtilities.cs b/src/MSBuildTaskHost/CommunicationsUtilities.cs index 86d6c8384e5..c410fba1d8d 100644 --- a/src/MSBuildTaskHost/CommunicationsUtilities.cs +++ b/src/MSBuildTaskHost/CommunicationsUtilities.cs @@ -1014,14 +1014,11 @@ internal static HandshakeOptions GetHandshakeOptions( // Both the client and the host will calculate this separately, and the idea is that if they come out the same // then we can be sufficiently confident that the other side has the same elevation level as us. This is complementary // to the username check which is also done on connection. - if ( -#if RUNTIME_TYPE_NETCORE - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && -#endif - new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) + if (new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) { context |= HandshakeOptions.Administrator; } + return context; } diff --git a/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs b/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs index be1b3da23c9..b6cddae6f13 100644 --- a/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs +++ b/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs @@ -51,21 +51,15 @@ public static AssemblyName CloneIfPossible(this AssemblyName assemblyNameToClone name.Flags = assemblyNameToClone.Flags; name.ProcessorArchitecture = assemblyNameToClone.ProcessorArchitecture; -#if !RUNTIME_TYPE_NETCORE name.CultureInfo = assemblyNameToClone.CultureInfo; name.HashAlgorithm = assemblyNameToClone.HashAlgorithm; name.VersionCompatibility = assemblyNameToClone.VersionCompatibility; name.CodeBase = assemblyNameToClone.CodeBase; name.KeyPair = assemblyNameToClone.KeyPair; name.VersionCompatibility = assemblyNameToClone.VersionCompatibility; -#else - // Setting the culture name creates a new CultureInfo, leading to many allocations. Only set CultureName when the CultureInfo member is not available. - name.CultureName = assemblyNameToClone.CultureName; -#endif return name; #endif - } private static Assembly GetEntryAssembly() diff --git a/src/MSBuildTaskHost/Framework/NativeMethods.cs b/src/MSBuildTaskHost/Framework/NativeMethods.cs index 2162ea1b094..725f0aab6b3 100644 --- a/src/MSBuildTaskHost/Framework/NativeMethods.cs +++ b/src/MSBuildTaskHost/Framework/NativeMethods.cs @@ -838,18 +838,7 @@ internal static string OSName /// /// Framework named as presented to users (for example in version info). /// - internal static string FrameworkName - { - get - { -#if RUNTIME_TYPE_NETCORE - const string frameworkName = ".NET"; -#else - const string frameworkName = ".NET Framework"; -#endif - return frameworkName; - } - } + internal static string FrameworkName => ".NET Framework"; /// /// OS name that can be used for the msbuildExtensionsPathSearchPaths element From 39ed041b4aa4f40e25af08fb49d458e40a032f73 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 10:43:01 -0800 Subject: [PATCH 021/136] MSBuildTaskHost: Remove a few unused types CopyOnWriteDictionary, ReadOnlyEmptyCollection, and ReadOnlyEmptyDictionary are never used in MSBuildTaskHost and can be safely removed. --- src/MSBuildTaskHost/CopyOnWriteDictionary.cs | 413 ------------------ src/MSBuildTaskHost/MSBuildTaskHost.csproj | 3 - .../ReadOnlyEmptyCollection.cs | 145 ------ .../ReadOnlyEmptyDictionary.cs | 329 -------------- 4 files changed, 890 deletions(-) delete mode 100644 src/MSBuildTaskHost/CopyOnWriteDictionary.cs delete mode 100644 src/MSBuildTaskHost/ReadOnlyEmptyCollection.cs delete mode 100644 src/MSBuildTaskHost/ReadOnlyEmptyDictionary.cs diff --git a/src/MSBuildTaskHost/CopyOnWriteDictionary.cs b/src/MSBuildTaskHost/CopyOnWriteDictionary.cs deleted file mode 100644 index b0948ed17a2..00000000000 --- a/src/MSBuildTaskHost/CopyOnWriteDictionary.cs +++ /dev/null @@ -1,413 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Runtime.Serialization; - -namespace Microsoft.Build.Collections -{ - /// - /// A dictionary that has copy-on-write semantics. - /// KEYS AND VALUES MUST BE IMMUTABLE OR COPY-ON-WRITE FOR THIS TO WORK. - /// - /// The value type. - /// - /// Thread safety: for all users, this class is as thread safe as the underlying Dictionary implementation, that is, - /// safe for concurrent readers or one writer from EACH user. It achieves this by locking itself and cloning before - /// any write, if it is being shared - i.e., stopping sharing before any writes occur. - /// - /// - /// This class must be serializable as it is used for metadata passed to tasks, which may - /// be run in a separate appdomain. - /// - [Serializable] - internal class CopyOnWriteDictionary : IDictionary, IDictionary, ISerializable - { -#if !NET35 // MSBuildNameIgnoreCaseComparer not compiled into MSBuildTaskHost but also allocations not interesting there. - /// - /// Empty dictionary with a , - /// used as the basis of new dictionaries with that comparer to avoid - /// allocating new comparers objects. - /// - private static readonly ImmutableDictionary NameComparerDictionaryPrototype = ImmutableDictionary.Create(MSBuildNameIgnoreCaseComparer.Default); - - /// - /// Empty dictionary with , - /// used as the basis of new dictionaries with that comparer to avoid - /// allocating new comparers objects. - /// - private static readonly ImmutableDictionary OrdinalIgnoreCaseComparerDictionaryPrototype = ImmutableDictionary.Create(StringComparer.OrdinalIgnoreCase); -#endif - - - /// - /// The backing dictionary. - /// Lazily created. - /// - private ImmutableDictionary _backing; - - /// - /// Constructor. Consider supplying a comparer instead. - /// - internal CopyOnWriteDictionary() - { - _backing = ImmutableDictionary.Empty; - } - - /// - /// Constructor taking a specified comparer for the keys - /// - internal CopyOnWriteDictionary(IEqualityComparer? keyComparer) - { - _backing = GetInitialDictionary(keyComparer); - } - - /// - /// Serialization constructor, for crossing appdomain boundaries - /// - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "context", Justification = "Not needed")] - protected CopyOnWriteDictionary(SerializationInfo info, StreamingContext context) - { - object v = info.GetValue(nameof(_backing), typeof(KeyValuePair[]))!; - - object comparer = info.GetValue(nameof(Comparer), typeof(IEqualityComparer))!; - - var b = GetInitialDictionary((IEqualityComparer?)comparer); - - _backing = b.AddRange((KeyValuePair[])v); - } - - private static ImmutableDictionary GetInitialDictionary(IEqualityComparer? keyComparer) - { -#if NET35 - return ImmutableDictionary.Create(keyComparer); -#else - return keyComparer is MSBuildNameIgnoreCaseComparer - ? NameComparerDictionaryPrototype - : keyComparer == StringComparer.OrdinalIgnoreCase - ? OrdinalIgnoreCaseComparerDictionaryPrototype - : ImmutableDictionary.Create(keyComparer); -#endif - } - - /// - /// Cloning constructor. Defers the actual clone. - /// - private CopyOnWriteDictionary(CopyOnWriteDictionary that) - { - _backing = that._backing; - } - - public CopyOnWriteDictionary(IDictionary dictionary) - { - _backing = dictionary.GetType() == typeof(ImmutableDictionary) - ? (ImmutableDictionary)dictionary - : dictionary.ToImmutableDictionary(); - } - - /// - /// Returns the collection of keys in the dictionary. - /// - public ICollection Keys => ((IDictionary)_backing).Keys; - - /// - /// Returns the collection of values in the dictionary. - /// - public ICollection Values => ((IDictionary)_backing).Values; - - /// - /// Returns the number of items in the collection. - /// - public int Count => _backing.Count; - - /// - /// Returns true if the collection is read-only. - /// - public bool IsReadOnly => ((IDictionary)_backing).IsReadOnly; - - /// - /// IDictionary implementation - /// - bool IDictionary.IsFixedSize => false; - - /// - /// IDictionary implementation - /// - bool IDictionary.IsReadOnly => IsReadOnly; - - /// - /// IDictionary implementation - /// - ICollection IDictionary.Keys => (ICollection)Keys; - - /// - /// IDictionary implementation - /// - ICollection IDictionary.Values => (ICollection)Values; - - /// - /// IDictionary implementation - /// - int ICollection.Count => Count; - - /// - /// IDictionary implementation - /// - bool ICollection.IsSynchronized => false; - - /// - /// IDictionary implementation - /// - object ICollection.SyncRoot => this; - - /// - /// Comparer used for keys - /// - internal IEqualityComparer Comparer - { - get => _backing.KeyComparer; - private set => _backing = _backing.WithComparers(keyComparer: value); - } - - /// - /// The backing copy-on-write dictionary, safe to reuse. - /// - internal ImmutableDictionary BackingDictionary => _backing; - - /// - /// Accesses the value for the specified key. - /// - public V this[string key] - { - get => _backing[key]; - - set - { - _backing = _backing.SetItem(key, value); - } - } - - /// - /// IDictionary implementation - /// - object? IDictionary.this[object key] - { - get - { - TryGetValue((string)key, out V? val); - return val; - } -#nullable disable - set => this[(string)key] = (V)value; -#nullable enable - } - - /// - /// Adds a value to the dictionary. - /// - public void Add(string key, V value) - { - _backing = _backing.SetItem(key, value); - } - - /// - /// Adds several value to the dictionary. - /// - public void SetItems(IEnumerable> items) - { - _backing = _backing.SetItems(items); - } - - public IEnumerable> Where(Func, bool> predicate) - { - return _backing.Where(predicate); - } - /// - /// Returns true if the dictionary contains the specified key. - /// - public bool ContainsKey(string key) - { - return _backing.ContainsKey(key); - } - - /// - /// Removes the entry for the specified key from the dictionary. - /// - public bool Remove(string key) - { - ImmutableDictionary initial = _backing; - - _backing = _backing.Remove(key); - - return initial != _backing; // whether the removal occured - } - -#nullable disable - /// - /// Attempts to find the value for the specified key in the dictionary. - /// - public bool TryGetValue(string key, out V value) - { - return _backing.TryGetValue(key, out value); - } -#nullable restore - - /// - /// Adds an item to the collection. - /// - public void Add(KeyValuePair item) - { - _backing = _backing.SetItem(item.Key, item.Value); - } - - /// - /// Clears the collection. - /// - public void Clear() - { - _backing = _backing.Clear(); - } - - /// - /// Returns true ff the collection contains the specified item. - /// - public bool Contains(KeyValuePair item) - { - return _backing.Contains(item); - } - - /// - /// Copies all of the elements of the collection to the specified array. - /// - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - ((IDictionary)_backing).CopyTo(array, arrayIndex); - } - - /// - /// Remove an item from the dictionary. - /// - public bool Remove(KeyValuePair item) - { - ImmutableDictionary initial = _backing; - - _backing = _backing.Remove(item.Key); - - return initial != _backing; // whether the removal occured - } - -#if NET472_OR_GREATER || NETCOREAPP - /// - /// Implementation of generic IEnumerable.GetEnumerator() - /// - public ImmutableDictionary.Enumerator GetEnumerator() - { - return _backing.GetEnumerator(); - } - - IEnumerator> IEnumerable>.GetEnumerator() - { - ImmutableDictionary.Enumerator enumerator = _backing.GetEnumerator(); - return _backing.GetEnumerator(); - } -#else - public IEnumerator> GetEnumerator() - { - return _backing.GetEnumerator(); - } -#endif - - /// - /// Implementation of IEnumerable.GetEnumerator() - /// - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable>)this).GetEnumerator(); - } - -#nullable disable - /// - /// IDictionary implementation. - /// - void IDictionary.Add(object key, object value) - { - Add((string)key, (V)value); - } -#nullable enable - - /// - /// IDictionary implementation. - /// - void IDictionary.Clear() - { - Clear(); - } - - /// - /// IDictionary implementation. - /// - bool IDictionary.Contains(object key) - { - return ContainsKey((string)key); - } - - /// - /// IDictionary implementation. - /// - IDictionaryEnumerator IDictionary.GetEnumerator() - { - return ((IDictionary)_backing).GetEnumerator(); - } - - /// - /// IDictionary implementation. - /// - void IDictionary.Remove(object key) - { - Remove((string)key); - } - - /// - /// IDictionary implementation. - /// - void ICollection.CopyTo(Array array, int index) - { - int i = 0; - foreach (KeyValuePair entry in this) - { - array.SetValue(new DictionaryEntry(entry.Key, entry.Value), index + i); - i++; - } - } - - /// - /// Clone, with the actual clone deferred - /// - internal CopyOnWriteDictionary Clone() - { - return new CopyOnWriteDictionary(this); - } - - /// - /// Returns true if these dictionaries have the same backing. - /// - internal bool HasSameBacking(CopyOnWriteDictionary other) - { - return ReferenceEquals(other._backing, _backing); - } - - public void GetObjectData(SerializationInfo info, StreamingContext context) - { - ImmutableDictionary snapshot = _backing; - KeyValuePair[] array = snapshot.ToArray(); - - info.AddValue(nameof(_backing), array); - info.AddValue(nameof(Comparer), Comparer); - } - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 69a627be8fb..1aecb136088 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -48,7 +48,6 @@ - @@ -120,8 +119,6 @@ - - diff --git a/src/MSBuildTaskHost/ReadOnlyEmptyCollection.cs b/src/MSBuildTaskHost/ReadOnlyEmptyCollection.cs deleted file mode 100644 index 128480dca68..00000000000 --- a/src/MSBuildTaskHost/ReadOnlyEmptyCollection.cs +++ /dev/null @@ -1,145 +0,0 @@ -// 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; -using System.Collections.Generic; -using Microsoft.Build.Shared; - -#nullable disable - -namespace Microsoft.Build.Collections -{ - /// - /// A read-only wrapper over an empty collection. - /// - /// - /// Thus this is an omission from the BCL. - /// - /// Type of element in the collection - internal class ReadOnlyEmptyCollection : ICollection, ICollection - { - /// - /// Backing live collection - /// - private static ReadOnlyEmptyCollection s_instance; - - /// - /// Private default constructor as this is a singleton - /// - private ReadOnlyEmptyCollection() - { - } - - /// - /// Get the instance - /// - public static ReadOnlyEmptyCollection Instance - { - get - { - if (s_instance == null) - { - s_instance = new ReadOnlyEmptyCollection(); - } - - return s_instance; - } - } - - /// - /// Pass through for underlying collection - /// - public int Count - { - get { return 0; } - } - - /// - /// Returns true. - /// - public bool IsReadOnly - { - get { return true; } - } - - /// - /// Whether collection is synchronized - /// - bool ICollection.IsSynchronized - { - get { return false; } - } - - /// - /// Sync root - /// - object ICollection.SyncRoot - { - get { return this; } - } - - /// - /// Prohibited on read only collection: throws - /// - public void Add(T item) - { - ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); - } - - /// - /// Prohibited on read only collection: throws - /// - public void Clear() - { - ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); - } - - /// - /// Pass through for underlying collection - /// - public bool Contains(T item) - { - return false; - } - - /// - /// Pass through for underlying collection - /// - public void CopyTo(T[] array, int arrayIndex) - { - } - - /// - /// Prohibited on read only collection: throws - /// - public bool Remove(T item) - { - ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); - return false; - } - - /// - /// Get an enumerator over an empty collection - /// - public IEnumerator GetEnumerator() - { - yield break; - } - - /// - /// Get an enumerator over an empty collection - /// - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - /// - /// ICollection version of CopyTo - /// - void ICollection.CopyTo(Array array, int index) - { - } - } -} diff --git a/src/MSBuildTaskHost/ReadOnlyEmptyDictionary.cs b/src/MSBuildTaskHost/ReadOnlyEmptyDictionary.cs deleted file mode 100644 index 46b1b2738e9..00000000000 --- a/src/MSBuildTaskHost/ReadOnlyEmptyDictionary.cs +++ /dev/null @@ -1,329 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Build.Shared; - -#nullable disable - -namespace Microsoft.Build.Collections -{ - /// - /// A special singleton enumerable that enumerates a read-only empty dictionary - /// - /// Key - /// Value - internal class ReadOnlyEmptyDictionary : IDictionary, IReadOnlyDictionary, IDictionary - { - /// - /// The single instance - /// - private static readonly Dictionary s_backing = new Dictionary(); - - /// - /// The single instance - /// - private static ReadOnlyEmptyDictionary s_instance; - - /// - /// Private default constructor as this is a singleton - /// - private ReadOnlyEmptyDictionary() - { - } - - /// - /// Get the instance - /// - public static ReadOnlyEmptyDictionary Instance - { - get - { - if (s_instance == null) - { - s_instance = new ReadOnlyEmptyDictionary(); - } - - return s_instance; - } - } - - /// - /// Empty returns zero - /// - public int Count - { - get { return 0; } - } - - /// - /// Returns true - /// - public bool IsReadOnly - { - get { return true; } - } - - /// - /// Gets empty collection - /// - public ICollection Keys => -#if CLR2COMPATIBILITY - new K[0]; -#else - Array.Empty(); -#endif - - /// - /// Gets empty collection - /// - public ICollection Values => -#if CLR2COMPATIBILITY - new V[0]; -#else - Array.Empty(); -#endif - - /// - /// Is it fixed size - /// - public bool IsFixedSize - { - get { return true; } - } - - /// - /// Not synchronized - /// - public bool IsSynchronized - { - get { return false; } - } - - /// - /// No sync root - /// - public object SyncRoot - { - get { return null; } - } - - /// - /// Keys - /// - ICollection IDictionary.Keys - { - get { return (ICollection)((IDictionary)this).Keys; } - } - - /// - /// Values - /// - ICollection IDictionary.Values - { - get { return (ICollection)((IDictionary)this).Values; } - } - - /// - /// Keys - /// - IEnumerable IReadOnlyDictionary.Keys - { - get { return Keys; } - } - - /// - /// Values - /// - IEnumerable IReadOnlyDictionary.Values - { - get { return Values; } - } - - /// - /// Indexer - /// - public object this[object key] - { - get - { - return ((IDictionary)this)[(K)key]; - } - - set - { - ((IDictionary)this)[(K)key] = (V)value; - } - } - - /// - /// Get returns null as read-only - /// Set is prohibited and throws. - /// - public V this[K key] - { - get - { - // Trigger KeyNotFoundException - return new Dictionary()[key]; - } - - set - { - ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); - } - } - - /// - /// Pass through for underlying collection - /// - public void Add(K key, V value) - { - ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); - } - - /// - /// Empty returns false - /// - public bool ContainsKey(K key) - { - return false; - } - - /// - /// Prohibited on read only collection: throws - /// - public bool Remove(K key) - { - ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); - return false; - } - - /// - /// Empty returns false - /// - public bool TryGetValue(K key, out V value) - { - value = default(V); - return false; - } - - /// - /// Prohibited on read only collection: throws - /// - public void Add(KeyValuePair item) - { - ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); - } - - /// - /// Prohibited on read only collection: throws - /// - public void Clear() - { - ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); - } - - /// - /// Empty returns false - /// - public bool Contains(KeyValuePair item) - { - return false; - } - - /// - /// Empty does nothing - /// - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - } - - /// - /// Prohibited on read only collection: throws - /// - public bool Remove(KeyValuePair item) - { - ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection"); - return false; - } - - /// - /// Get empty enumerator - /// - public IEnumerator> GetEnumerator() - { - return Enumerable.Empty>().GetEnumerator(); - } - - /// - /// Get empty enumerator - /// - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - /// - /// Add - /// - public void Add(object key, object value) - { - ((IDictionary)this).Add((K)key, (V)value); - } - - /// - /// Contains - /// - public bool Contains(object key) - { - return ((IDictionary)this).ContainsKey((K)key); - } - - /// - /// Enumerator - /// - IDictionaryEnumerator IDictionary.GetEnumerator() - { - return ((IDictionary)s_backing).GetEnumerator(); - } - - /// - /// Remove - /// - public void Remove(object key) - { - ((IDictionary)this).Remove((K)key); - } - - /// - /// CopyTo - /// - public void CopyTo(System.Array array, int index) - { - // Nothing to do - } - } -} - -#if NET35 -namespace System.Collections.Generic -{ - public interface IReadOnlyCollection : IEnumerable - { - int Count { get; } - } - - public interface IReadOnlyDictionary : IReadOnlyCollection> - { - TValue this[TKey key] { get; } - IEnumerable Keys { get; } - IEnumerable Values { get; } - bool ContainsKey(TKey key); - bool TryGetValue(TKey key, out TValue value); - } -} -#endif From ecc03a31f60dc6aadd96d94368f2d15f74d23124 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 10:46:44 -0800 Subject: [PATCH 022/136] MSBuildTaskHost: Remove NET35 disabled code blocks --- src/MSBuildTaskHost/AssemblyLoadInfo.cs | 9 +- src/MSBuildTaskHost/FileUtilitiesRegex.cs | 3 - src/MSBuildTaskHost/LoadedType.cs | 113 ------------------ .../OutOfProcTaskAppDomainWrapperBase.cs | 20 ---- .../MSBuild/OutOfProcTaskHostNode.cs | 3 - src/MSBuildTaskHost/TaskHostConfiguration.cs | 25 ---- .../TaskParameterTypeVerifier.cs | 2 - 7 files changed, 1 insertion(+), 174 deletions(-) diff --git a/src/MSBuildTaskHost/AssemblyLoadInfo.cs b/src/MSBuildTaskHost/AssemblyLoadInfo.cs index 4ced73e1360..a4aeee86a93 100644 --- a/src/MSBuildTaskHost/AssemblyLoadInfo.cs +++ b/src/MSBuildTaskHost/AssemblyLoadInfo.cs @@ -226,14 +226,7 @@ internal override string AssemblyLocation /// Gets whether this assembly is an inline task assembly. /// Detects inline tasks by checking if the file path ends with the inline task suffix. /// - internal override bool IsInlineTask - { -#if !NET35 - get { return _assemblyFile?.EndsWith(TaskFactoryUtilities.InlineTaskSuffix, StringComparison.OrdinalIgnoreCase) == true; } -#else - get { return false; } -#endif - } + internal override bool IsInlineTask => false; } } } diff --git a/src/MSBuildTaskHost/FileUtilitiesRegex.cs b/src/MSBuildTaskHost/FileUtilitiesRegex.cs index c35f1f9ed5f..b5ecc5a1f68 100644 --- a/src/MSBuildTaskHost/FileUtilitiesRegex.cs +++ b/src/MSBuildTaskHost/FileUtilitiesRegex.cs @@ -156,9 +156,6 @@ internal static int StartsWithUncPatternMatchLength(string pattern) /// /// Input to check for UNC pattern minimum requirements. /// true if the UNC pattern is a minimum length of 5 and the first two characters are be a slash, false otherwise. -#if !NET35 - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif internal static bool MeetsUncPatternMinimumRequirements(string pattern) { return pattern.Length >= 5 && diff --git a/src/MSBuildTaskHost/LoadedType.cs b/src/MSBuildTaskHost/LoadedType.cs index 18f59bd164f..40f777459da 100644 --- a/src/MSBuildTaskHost/LoadedType.cs +++ b/src/MSBuildTaskHost/LoadedType.cs @@ -55,118 +55,9 @@ internal LoadedType( : loadedAssembly.Location; LoadedAssembly = loadedAssembly; - -#if !NET35 - // This block is reflection only loaded type implementation. Net35 does not support it, and fall backs to former implementation in #else - // Property `Properties` set in this block aren't used by TaskHosts. Properties below are only used on the NodeProvider side to get information about the - // properties and reflect over them without needing them to be fully loaded, so it also isn't need for TaskHosts. - - // MetadataLoadContext-loaded Type objects don't support testing for inherited attributes, so we manually walk the BaseType chain. - Type? t = type; - while (t is not null) - { - try - { - if (TypeUtilities.HasAttribute(t)) - { - HasLoadInSeparateAppDomainAttribute = true; - } - - if (TypeUtilities.HasAttribute(t)) - { - HasSTAThreadAttribute = true; - } - - if (t.IsMarshalByRef) - { - IsMarshalByRef = true; - } - } - catch when (loadedViaMetadataLoadContext) - { - // when assembly is loaded via metadata load context we can ignore exception because there is no expectation to have it in proc. - // BUT we should throw for in-proc case and handle it on higher level. - } - - t = t.BaseType; - } - - PropertyInfo[] props = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); - Properties = new ReflectableTaskPropertyInfo[props.Length]; - if (loadedViaMetadataLoadContext) - { - PropertyAssemblyQualifiedNames = new string[props.Length]; - } - - for (int i = 0; i < props.Length; i++) - { - bool outputAttribute = false; - bool requiredAttribute = false; - foreach (CustomAttributeData attr in CustomAttributeData.GetCustomAttributes(props[i])) - { - try - { - if (attr.AttributeType?.Name.Equals(nameof(OutputAttribute)) == true) - { - outputAttribute = true; - } - else if (attr.AttributeType?.Name.Equals(nameof(RequiredAttribute)) == true) - { - requiredAttribute = true; - } - } - catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) - { - // Skip attributes that can't be loaded - continue; - } - } - - // Check whether it's assignable to ITaskItem or ITaskItem[]. Simplify to just checking for ITaskItem. - Type? pt = null; - try - { - pt = props[i].PropertyType; - if (pt.IsArray) - { - pt = pt.GetElementType(); - } - } - catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) - { - // Skip properties that can't be loaded - continue; - } - - bool isAssignableToITask = false; - try - { - isAssignableToITask = pt != null && iTaskItemType.IsAssignableFrom(pt); - } - catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) - { - // Can't determine assignability, default to false - } - - Properties[i] = new ReflectableTaskPropertyInfo(props[i], outputAttribute, requiredAttribute, isAssignableToITask); - if (loadedViaMetadataLoadContext && PropertyAssemblyQualifiedNames != null) - { - try - { - PropertyAssemblyQualifiedNames[i] = Properties[i]?.PropertyType?.AssemblyQualifiedName ?? string.Empty; - } - catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) - { - PropertyAssemblyQualifiedNames[i] = string.Empty; - } - } - } -#else - // For v3.5 fallback to old full type approach, as oppose to reflection only HasLoadInSeparateAppDomainAttribute = Type.GetTypeInfo().IsDefined(typeof(LoadInSeparateAppDomainAttribute), true /* inherited */); HasSTAThreadAttribute = Type.GetTypeInfo().IsDefined(typeof(RunInSTAAttribute), true /* inherited */); IsMarshalByRef = Type.IsMarshalByRef; -#endif } #endregion @@ -236,10 +127,6 @@ private bool CheckForHardcodedSTARequirement() /// internal Assembly LoadedAssembly { get; private set; } -#if !NET35 - internal ReflectableTaskPropertyInfo[] Properties { get; private set; } -#endif - /// /// Assembly-qualified names for properties. Only has a value if this type was loaded using MetadataLoadContext. /// diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs index ae77fb43e03..c4c05be13a4 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs @@ -11,9 +11,6 @@ using Microsoft.Build.BackEnd; using Microsoft.Build.Framework; using Microsoft.Build.Shared; -#if !NET35 -using Microsoft.Build.Execution; -#endif #nullable disable @@ -58,10 +55,6 @@ internal class OutOfProcTaskAppDomainWrapperBase /// private string taskName; -#if !NET35 - private HostServices _hostServices; -#endif - /// /// This is the actual user task whose instance we will create and invoke Execute /// @@ -109,9 +102,6 @@ internal OutOfProcTaskHostTaskResult ExecuteTask( string projectFile, #if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, -#endif -#if !NET35 - HostServices hostServices, #endif IDictionary taskParams) { @@ -120,9 +110,6 @@ internal OutOfProcTaskHostTaskResult ExecuteTask( #if FEATURE_APPDOMAIN _taskAppDomain = null; -#endif -#if !NET35 - _hostServices = hostServices; #endif wrappedTask = null; @@ -348,13 +335,6 @@ private OutOfProcTaskHostTaskResult InstantiateAndExecuteTask( ); #pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter -#if !NET35 - if (projectFile != null && _hostServices != null) - { - wrappedTask.HostObject = _hostServices.GetHostObject(projectFile, targetName, taskName); - } -#endif - wrappedTask.BuildEngine = oopTaskHostNode; } catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs index 928c49bd661..ad39adacd46 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs @@ -942,9 +942,6 @@ private void RunTask(object state) taskConfiguration.ProjectFile, #if FEATURE_APPDOMAIN taskConfiguration.AppDomainSetup, -#endif -#if !NET35 - taskConfiguration.HostServices, #endif taskParams); } diff --git a/src/MSBuildTaskHost/TaskHostConfiguration.cs b/src/MSBuildTaskHost/TaskHostConfiguration.cs index ddf1d1c1a5d..8b2ecc5fedc 100644 --- a/src/MSBuildTaskHost/TaskHostConfiguration.cs +++ b/src/MSBuildTaskHost/TaskHostConfiguration.cs @@ -95,10 +95,6 @@ internal class TaskHostConfiguration : INodePacket /// private string _projectFile; -#if !NET35 - private HostServices _hostServices; -#endif - /// /// The set of parameters to apply to the task prior to execution. /// @@ -167,9 +163,6 @@ public TaskHostConfiguration( IDictionary buildProcessEnvironment, CultureInfo culture, CultureInfo uiCulture, -#if !NET35 - HostServices hostServices, -#endif #if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, #endif @@ -206,9 +199,6 @@ public TaskHostConfiguration( _culture = culture; _uiCulture = uiCulture; -#if !NET35 - _hostServices = hostServices; -#endif #if FEATURE_APPDOMAIN _appDomainSetup = appDomainSetup; #endif @@ -308,18 +298,6 @@ public AppDomainSetup AppDomainSetup } #endif -#if !NET35 - /// - /// The HostServices to be used by the task host. - /// - public HostServices HostServices - { - [DebuggerStepThrough] - get - { return _hostServices; } - } -#endif - /// /// Line number where the instance of this task is defined. /// @@ -511,9 +489,6 @@ public void Translate(ITranslator translator) { translator.Translate(ref _targetName); translator.Translate(ref _projectFile); -#if !NET35 - translator.Translate(ref _hostServices); -#endif } translator.Translate(ref _isTaskInputLoggingEnabled); diff --git a/src/MSBuildTaskHost/TaskParameterTypeVerifier.cs b/src/MSBuildTaskHost/TaskParameterTypeVerifier.cs index 2b7d6160d84..9e0bdce6333 100644 --- a/src/MSBuildTaskHost/TaskParameterTypeVerifier.cs +++ b/src/MSBuildTaskHost/TaskParameterTypeVerifier.cs @@ -4,9 +4,7 @@ using System; using System.Reflection; using Microsoft.Build.Framework; -#if NET35 using Microsoft.Build.Shared; -#endif #nullable disable From dd9c8a1f7559f184ff6416b9036578722972afb3 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 10:57:38 -0800 Subject: [PATCH 023/136] MSBuildTaskHost: Remove NET* disabled code blocks Since MSBuildTaskHost only builds for .NET 3.5, there are many code blocks specific to other .NET versions that can be removed. NOTE: Disabled code blocks were intentionally NOT removed from polyfill types. --- src/MSBuildTaskHost/AssemblyNameExtension.cs | 10 -- src/MSBuildTaskHost/BufferedReadStream.cs | 60 ---------- .../CommunicationsUtilities.cs | 93 +++------------ src/MSBuildTaskHost/Constants.cs | 8 -- src/MSBuildTaskHost/EnvironmentUtilities.cs | 16 --- src/MSBuildTaskHost/FileUtilities.cs | 28 +---- src/MSBuildTaskHost/FileUtilitiesRegex.cs | 4 - .../Framework/BinaryTranslator.cs | 4 - .../BuildException/BuildExceptionBase.cs | 7 -- .../Framework/InternalErrorException.cs | 3 - .../Framework/InterningReadTranslator.cs | 5 - .../Framework/InterningWriteTranslator.cs | 30 ++--- .../MSBuildNameIgnoreCaseComparer.cs | 4 - .../Framework/NativeMethods.cs | 21 ---- src/MSBuildTaskHost/GlobalUsings.cs | 4 - .../NodeEndpointOutOfProcBase.cs | 108 +----------------- src/MSBuildTaskHost/XMakeAttributes.cs | 4 - 17 files changed, 24 insertions(+), 385 deletions(-) diff --git a/src/MSBuildTaskHost/AssemblyNameExtension.cs b/src/MSBuildTaskHost/AssemblyNameExtension.cs index 9f4b12b918e..689932bdc9d 100644 --- a/src/MSBuildTaskHost/AssemblyNameExtension.cs +++ b/src/MSBuildTaskHost/AssemblyNameExtension.cs @@ -577,11 +577,6 @@ private static int CompareBaseNamesStringWise(string asString1, string asString2 baseLenThat = asString2.Length; } -#if NET - ReadOnlySpan nameThis = asString1.AsSpan(0, baseLenThis); - ReadOnlySpan nameThat = asString2.AsSpan(0, baseLenThat); - return nameThis.CompareTo(nameThat, StringComparison.OrdinalIgnoreCase); -#else // If the lengths are the same then we can compare without copying. if (baseLenThis == baseLenThat) { @@ -592,7 +587,6 @@ private static int CompareBaseNamesStringWise(string asString1, string asString2 string nameThis = asString1.Substring(0, baseLenThis); string nameThat = asString2.Substring(0, baseLenThat); return string.Compare(nameThis, nameThat, StringComparison.OrdinalIgnoreCase); -#endif } /// @@ -784,9 +778,6 @@ internal bool ComparePublicKeyToken(AssemblyNameExtension that) /// internal static bool ComparePublicKeyTokens(byte[] aPKT, byte[] bPKT) { -#if NET - return aPKT.AsSpan().SequenceEqual(bPKT.AsSpan()); -#else // Some assemblies (real case was interop assembly) may have null PKTs. aPKT ??= []; bPKT ??= []; @@ -805,7 +796,6 @@ internal static bool ComparePublicKeyTokens(byte[] aPKT, byte[] bPKT) } return true; -#endif } /// diff --git a/src/MSBuildTaskHost/BufferedReadStream.cs b/src/MSBuildTaskHost/BufferedReadStream.cs index 55bba5986f8..ee43cfc2b41 100644 --- a/src/MSBuildTaskHost/BufferedReadStream.cs +++ b/src/MSBuildTaskHost/BufferedReadStream.cs @@ -6,10 +6,6 @@ using System.IO.Pipes; using System.Threading; -#if NET451_OR_GREATER || NETCOREAPP -using System.Threading.Tasks; -#endif - #nullable disable namespace Microsoft.Build.BackEnd @@ -126,62 +122,6 @@ public override int Read(byte[] buffer, int offset, int count) } } -#if NET451_OR_GREATER || NETCOREAPP - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - if (count > BUFFER_SIZE) - { - // Trying to read more data than the buffer can hold - int alreadyCopied = CopyToBuffer(buffer, offset); - -#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' - int innerReadCount = await _innerStream.ReadAsync(buffer, offset + alreadyCopied, count - alreadyCopied, cancellationToken); -#pragma warning restore CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' - return innerReadCount + alreadyCopied; - } - else if (count <= _currentlyBufferedByteCount) - { - // Enough data buffered to satisfy read request - Array.Copy(_buffer, _currentIndexInBuffer, buffer, offset, count); - _currentIndexInBuffer += count; - _currentlyBufferedByteCount -= count; - return count; - } - else - { - // Need to read more data - int alreadyCopied = CopyToBuffer(buffer, offset); - -#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' - int innerReadCount = await _innerStream.ReadAsync(_buffer, 0, BUFFER_SIZE, cancellationToken); -#pragma warning restore CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' - _currentIndexInBuffer = 0; - _currentlyBufferedByteCount = innerReadCount; - - int remainingCopyCount = alreadyCopied + innerReadCount >= count ? count - alreadyCopied : innerReadCount; - Array.Copy(_buffer, 0, buffer, offset + alreadyCopied, remainingCopyCount); - _currentIndexInBuffer += remainingCopyCount; - _currentlyBufferedByteCount -= remainingCopyCount; - - return alreadyCopied + remainingCopyCount; - } - - int CopyToBuffer(byte[] buffer, int offset) - { - int alreadyCopied = 0; - if (_currentlyBufferedByteCount > 0) - { - Array.Copy(_buffer, _currentIndexInBuffer, buffer, offset, _currentlyBufferedByteCount); - alreadyCopied = _currentlyBufferedByteCount; - _currentIndexInBuffer = 0; - _currentlyBufferedByteCount = 0; - } - - return alreadyCopied; - } - } -#endif - public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); diff --git a/src/MSBuildTaskHost/CommunicationsUtilities.cs b/src/MSBuildTaskHost/CommunicationsUtilities.cs index c410fba1d8d..f90f1ecc709 100644 --- a/src/MSBuildTaskHost/CommunicationsUtilities.cs +++ b/src/MSBuildTaskHost/CommunicationsUtilities.cs @@ -272,15 +272,9 @@ protected Handshake(HandshakeOptions nodeType, bool includeSessionId, string pre } private string GetToolsDirectory(bool isNetTaskHost, string predefinedToolsDirectory) => -#if NETFRAMEWORK - isNetTaskHost - - // For .NET TaskHost assembly directory we set the expectation for the child dotnet process to connect to. + isNetTaskHost // For .NET TaskHost assembly directory we set the expectation for the child dotnet process to connect to. ? predefinedToolsDirectory : BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot; -#else - BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot; -#endif private static HandshakeComponents CreateNetTaskHostComponents(int options, int salt, int sessionId) => new( options, @@ -353,13 +347,8 @@ public string ComputeHash() { var input = GetKey(); byte[] utf8 = Encoding.UTF8.GetBytes(input); -#if NET - Span bytes = stackalloc byte[SHA256.HashSizeInBytes]; - SHA256.HashData(utf8, bytes); -#else using var sha = SHA256.Create(); var bytes = sha.ComputeHash(utf8); -#endif _computedHash = Convert.ToBase64String(bytes) .Replace("/", "_") .Replace("=", string.Empty); @@ -450,7 +439,6 @@ internal static int NodeConnectionTimeout [System.Runtime.Versioning.SupportedOSPlatform("windows")] internal static extern unsafe bool FreeEnvironmentStrings(char* pStrings); -#if NETFRAMEWORK /// /// Set environment variable P/Invoke. /// @@ -472,7 +460,6 @@ internal static void SetEnvironmentVariable(string name, string value) throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()); } } -#endif #if !CLR2COMPATIBILITY /// @@ -624,14 +611,6 @@ private static FrozenDictionary GetEnvironmentVariablesWindows() } } -#if NET - /// - /// Sets an environment variable using . - /// - internal static void SetEnvironmentVariable(string name, string value) - => Environment.SetEnvironmentVariable(name, value); -#endif - #if !CLR2COMPATIBILITY /// /// Returns key value pairs of environment variables in a read-only dictionary @@ -754,17 +733,11 @@ internal static void WriteIntForHandshake(this PipeStream stream, int value) internal static bool TryReadEndOfHandshakeSignal( this PipeStream stream, bool isProvider, -#if NETCOREAPP2_1_OR_GREATER - int timeout, -#endif out HandshakeResult result) { // Accept only the first byte of the EndOfHandshakeSignal if (stream.TryReadIntForHandshake( byteToAccept: null, -#if NETCOREAPP2_1_OR_GREATER - timeout, -#endif out HandshakeResult innerResult)) { byte negotiatedPacketVersion = 1; @@ -782,9 +755,6 @@ internal static bool TryReadEndOfHandshakeSignal( // We detected packet version marker, now let's read actual PacketVersion if (!stream.TryReadIntForHandshake( byteToAccept: null, -#if NETCOREAPP2_1_OR_GREATER - timeout, -#endif out HandshakeResult versionResult)) { result = versionResult; @@ -797,9 +767,6 @@ internal static bool TryReadEndOfHandshakeSignal( if (!stream.TryReadIntForHandshake( byteToAccept: null, -#if NETCOREAPP2_1_OR_GREATER - timeout, -#endif out innerResult)) { result = innerResult; @@ -841,61 +808,29 @@ private static HandshakeResult CreateVersionMismatchResult(bool isProvider, int internal static bool TryReadIntForHandshake( this PipeStream stream, byte? byteToAccept, -#if NETCOREAPP2_1_OR_GREATER - int timeout, -#endif out HandshakeResult result ) #pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter { byte[] bytes = new byte[4]; + int bytesRead = stream.Read(bytes, 0, bytes.Length); -#if NETCOREAPP2_1_OR_GREATER - if (!NativeMethodsShared.IsWindows) + // Abort for connection attempts from ancient MSBuild.exes + if (byteToAccept != null && bytesRead > 0 && byteToAccept != bytes[0]) { - // Enforce a minimum timeout because the timeout passed to Connect() just before - // calling this method does not apply on UNIX domain socket-based - // implementations of PipeStream. - // https://github.com/dotnet/corefx/issues/28791 - timeout = Math.Max(timeout, 50); - - // A legacy MSBuild.exe won't try to connect to MSBuild running - // in a dotnet host process, so we can read the bytes simply. - var readTask = stream.ReadAsync(bytes, 0, bytes.Length); - - // Manual timeout here because the timeout passed to Connect() just before - // calling this method does not apply on UNIX domain socket-based - // implementations of PipeStream. - // https://github.com/dotnet/corefx/issues/28791 - if (!readTask.Wait(timeout)) - { - result = HandshakeResult.Failure(HandshakeStatus.Timeout, String.Format(CultureInfo.InvariantCulture, "Did not receive return handshake in {0}ms", timeout)); - return false; - } - readTask.GetAwaiter().GetResult(); + stream.WriteIntForHandshake(0x0F0F0F0F); + stream.WriteIntForHandshake(0x0F0F0F0F); + result = HandshakeResult.Failure(HandshakeStatus.OldMSBuild, String.Format(CultureInfo.InvariantCulture, "Client: rejected old host. Received byte {0} instead of {1}.", bytes[0], byteToAccept)); + return false; } - else -#endif - { - int bytesRead = stream.Read(bytes, 0, bytes.Length); - // Abort for connection attempts from ancient MSBuild.exes - if (byteToAccept != null && bytesRead > 0 && byteToAccept != bytes[0]) - { - stream.WriteIntForHandshake(0x0F0F0F0F); - stream.WriteIntForHandshake(0x0F0F0F0F); - result = HandshakeResult.Failure(HandshakeStatus.OldMSBuild, String.Format(CultureInfo.InvariantCulture, "Client: rejected old host. Received byte {0} instead of {1}.", bytes[0], byteToAccept)); - return false; - } - - if (bytesRead != bytes.Length) - { - // We've unexpectly reached end of stream. - // We are now in a bad state, disconnect on our end - result = HandshakeResult.Failure(HandshakeStatus.UnexpectedEndOfStream, String.Format(CultureInfo.InvariantCulture, "Unexpected end of stream while reading for handshake")); + if (bytesRead != bytes.Length) + { + // We've unexpectly reached end of stream. + // We are now in a bad state, disconnect on our end + result = HandshakeResult.Failure(HandshakeStatus.UnexpectedEndOfStream, String.Format(CultureInfo.InvariantCulture, "Unexpected end of stream while reading for handshake")); - return false; - } + return false; } try diff --git a/src/MSBuildTaskHost/Constants.cs b/src/MSBuildTaskHost/Constants.cs index 0e5afa5b465..7a365f065c8 100644 --- a/src/MSBuildTaskHost/Constants.cs +++ b/src/MSBuildTaskHost/Constants.cs @@ -2,9 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -#if NET -using System.Buffers; -#endif using System.IO; #nullable disable @@ -134,12 +131,7 @@ internal static class MSBuildConstants internal static readonly string[] EnvironmentNewLine = [Environment.NewLine]; internal static readonly char[] PipeChar = ['|']; internal static readonly char[] PathSeparatorChar = [Path.PathSeparator]; - -#if NET - internal static readonly SearchValues InvalidPathChars = SearchValues.Create(Path.GetInvalidPathChars()); -#else internal static readonly char[] InvalidPathChars = Path.GetInvalidPathChars(); -#endif } internal static class PropertyNames diff --git a/src/MSBuildTaskHost/EnvironmentUtilities.cs b/src/MSBuildTaskHost/EnvironmentUtilities.cs index b64e792b53d..8b011bdca01 100644 --- a/src/MSBuildTaskHost/EnvironmentUtilities.cs +++ b/src/MSBuildTaskHost/EnvironmentUtilities.cs @@ -12,17 +12,9 @@ namespace Microsoft.Build.Shared { internal static partial class EnvironmentUtilities { -#if NET472_OR_GREATER || NETCOREAPP - public static bool Is64BitProcess => Marshal.SizeOf() == 8; - public static bool Is64BitOperatingSystem => - Environment.Is64BitOperatingSystem; -#endif - -#if !NETCOREAPP private static volatile int s_processId; private static volatile string? s_processPath; -#endif private static volatile string? s_processName; /// Gets the unique identifier for the current process. @@ -30,9 +22,6 @@ public static int CurrentProcessId { get { -#if NETCOREAPP - return Environment.ProcessId; -#else // copied from Environment.ProcessId int processId = s_processId; if (processId == 0) @@ -45,7 +34,6 @@ public static int CurrentProcessId } return processId; -#endif } } @@ -60,9 +48,6 @@ public static string? ProcessPath { get { -#if NETCOREAPP - return Environment.ProcessPath; -#else // copied from Environment.ProcessPath string? processPath = s_processPath; if (processPath == null) @@ -76,7 +61,6 @@ public static string? ProcessPath } return (processPath?.Length != 0) ? processPath : null; -#endif } } diff --git a/src/MSBuildTaskHost/FileUtilities.cs b/src/MSBuildTaskHost/FileUtilities.cs index e503867f6f6..d0afcda6c90 100644 --- a/src/MSBuildTaskHost/FileUtilities.cs +++ b/src/MSBuildTaskHost/FileUtilities.cs @@ -7,9 +7,6 @@ #else using Microsoft.Build.Shared.Concurrent; #endif -#if NET -using System.Buffers; -#endif using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -111,11 +108,7 @@ public static bool GetIsFileSystemCaseSensitive() /// Copied from https://github.com/dotnet/corefx/blob/056715ff70e14712419d82d51c8c50c54b9ea795/src/Common/src/System/IO/PathInternal.Windows.cs#L61 /// MSBuild should support the union of invalid path chars across the supported OSes, so builds can have the same behaviour crossplatform: https://github.com/dotnet/msbuild/issues/781#issuecomment-243942514 /// -#if NET - internal static readonly SearchValues InvalidPathChars = SearchValues.Create( -#else internal static readonly char[] InvalidPathChars = ( -#endif [ '|', '\0', (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, @@ -137,11 +130,7 @@ public static bool GetIsFileSystemCaseSensitive() (char)31, ':', '*', '?', '\\', '/' ]; -#if NET - internal static readonly SearchValues InvalidFileNameChars = SearchValues.Create(InvalidFileNameCharsArray); -#else internal static char[] InvalidFileNameChars => InvalidFileNameCharsArray; -#endif internal static readonly string DirectorySeparatorString = Path.DirectorySeparatorChar.ToString(); @@ -269,11 +258,7 @@ internal static string EnsureTrailingNoLeadingSlash(string path, int start) return FrameworkFileUtilities.FixFilePath(start < stop && FrameworkFileUtilities.IsSlash(path[stop - 1]) ? path.Substring(start) : -#if NET - string.Concat(path.AsSpan(start), new(in Path.DirectorySeparatorChar))); -#else path.Substring(start) + Path.DirectorySeparatorChar); -#endif } /// @@ -316,11 +301,7 @@ internal static string EnsureQuotes(string path, bool isSingleQuote = true) // Special case: convert the quotes. if (path.Length > 1 && path[0] == convertQuote && path[path.Length - 1] == convertQuote) { -#if NET - path = $"{targetQuote}{path.AsSpan(1, path.Length - 2)}{targetQuote}"; -#else path = $"{targetQuote}{path.Substring(1, path.Length - 2)}{targetQuote}"; -#endif } // Enclose the path in a set of the 'target' quote unless the string is already quoted with the 'target' quotes. else if (path.Length == 1 || path[0] != targetQuote || path[path.Length - 1] != targetQuote) @@ -859,19 +840,12 @@ internal static bool PathIsInvalid(string path) // Path.GetFileName does not react well to malformed filenames. // For example, Path.GetFileName("a/b/foo:bar") returns bar instead of foo:bar // It also throws exceptions on illegal path characters -#if NET - if (!path.AsSpan().ContainsAny(InvalidPathChars)) - { - int lastDirectorySeparator = path.LastIndexOfAny(FrameworkFileUtilities.Slashes); - return path.AsSpan(lastDirectorySeparator >= 0 ? lastDirectorySeparator + 1 : 0).ContainsAny(InvalidFileNameChars); - } -#else if (path.IndexOfAny(InvalidPathChars) < 0) { int lastDirectorySeparator = path.LastIndexOfAny(FrameworkFileUtilities.Slashes); return path.IndexOfAny(InvalidFileNameChars, lastDirectorySeparator >= 0 ? lastDirectorySeparator + 1 : 0) >= 0; } -#endif + return true; } diff --git a/src/MSBuildTaskHost/FileUtilitiesRegex.cs b/src/MSBuildTaskHost/FileUtilitiesRegex.cs index b5ecc5a1f68..ce0bf2c8d0f 100644 --- a/src/MSBuildTaskHost/FileUtilitiesRegex.cs +++ b/src/MSBuildTaskHost/FileUtilitiesRegex.cs @@ -52,11 +52,7 @@ internal static bool StartsWithDrivePattern(string pattern) // first character must be a letter, // second character must be a ":" return pattern.Length >= 2 && -#if NET - char.IsAsciiLetter(pattern[0]) && -#else ((pattern[0] >= 'A' && pattern[0] <= 'Z') || (pattern[0] >= 'a' && pattern[0] <= 'z')) && -#endif pattern[1] == ':'; } diff --git a/src/MSBuildTaskHost/Framework/BinaryTranslator.cs b/src/MSBuildTaskHost/Framework/BinaryTranslator.cs index f25c79df899..42643b4b805 100644 --- a/src/MSBuildTaskHost/Framework/BinaryTranslator.cs +++ b/src/MSBuildTaskHost/Framework/BinaryTranslator.cs @@ -336,11 +336,7 @@ public void Translate(ref HashSet set) } int count = _reader.ReadInt32(); -#if NET472_OR_GREATER || NET9_0_OR_GREATER - set = new HashSet(count); -#else set = new HashSet(); -#endif for (int i = 0; i < count; i++) { diff --git a/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionBase.cs b/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionBase.cs index 869b74070b6..0c32be246fc 100644 --- a/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionBase.cs +++ b/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionBase.cs @@ -31,9 +31,6 @@ private protected BuildExceptionBase( { } // This is needed to allow opting back in to BinaryFormatter serialization -#if NET8_0_OR_GREATER - [Obsolete(DiagnosticId = "SYSLIB0051")] -#endif private protected BuildExceptionBase(SerializationInfo info, StreamingContext context) : base(info, context) { } @@ -86,11 +83,7 @@ internal static void WriteExceptionToTranslator(ITranslator translator, Exceptio writer.WriteOptionalString(exception.Source); writer.WriteOptionalString(exception.HelpLink); // HResult is completely protected up till net4.5 -#if NET || NET45_OR_GREATER - int? hresult = exception.HResult; -#else int? hresult = null; -#endif writer.WriteOptionalInt32(hresult); IDictionary? customKeyedSerializedData = (exception as BuildExceptionBase)?.FlushCustomState(); diff --git a/src/MSBuildTaskHost/Framework/InternalErrorException.cs b/src/MSBuildTaskHost/Framework/InternalErrorException.cs index 8b1096c5eef..f85d7a7d57b 100644 --- a/src/MSBuildTaskHost/Framework/InternalErrorException.cs +++ b/src/MSBuildTaskHost/Framework/InternalErrorException.cs @@ -75,9 +75,6 @@ private InternalErrorException(string message, Exception innerException, bool ca /// Private constructor used for (de)serialization. The constructor is private as this class is sealed /// If we ever add new members to this class, we'll need to update this. /// -#if NET8_0_OR_GREATER - [Obsolete(DiagnosticId = "SYSLIB0051")] -#endif private InternalErrorException(SerializationInfo info, StreamingContext context) : base(info, context) { diff --git a/src/MSBuildTaskHost/Framework/InterningReadTranslator.cs b/src/MSBuildTaskHost/Framework/InterningReadTranslator.cs index 76a3d35b8f0..8536e9c6430 100644 --- a/src/MSBuildTaskHost/Framework/InterningReadTranslator.cs +++ b/src/MSBuildTaskHost/Framework/InterningReadTranslator.cs @@ -73,12 +73,7 @@ public void Translate(ITranslator translator) { // Only deserialize the intern header since the caller will be reading directly from the stream. _translator.Translate(ref _strings); -#if NET - _pathIdsToString.Clear(); - _pathIdsToString.EnsureCapacity(_strings.Count); -#else _pathIdsToString = new(_strings.Count); -#endif } } } diff --git a/src/MSBuildTaskHost/Framework/InterningWriteTranslator.cs b/src/MSBuildTaskHost/Framework/InterningWriteTranslator.cs index ec31eb92eb4..f8daf6306c8 100644 --- a/src/MSBuildTaskHost/Framework/InterningWriteTranslator.cs +++ b/src/MSBuildTaskHost/Framework/InterningWriteTranslator.cs @@ -64,29 +64,13 @@ internal InterningWriteTranslator() /// An estimate of the number of unique strings to be interned. internal void Setup(IEqualityComparer comparer, int initialCapacity) { -#if NET - if (_stringToIds.Comparer == comparer) - { - // Clear before setting capacity, since dictionaries will rehash every entry. - _strings.Clear(); - _stringToIds.Clear(); - _stringToPathIds.Clear(); - _strings.EnsureCapacity(initialCapacity); - _stringToIds.EnsureCapacity(initialCapacity); - _stringToPathIds.EnsureCapacity(initialCapacity); - } - else - { -#endif - // If the interner is in a reused translator, the comparer might not match between packets. - // Just throw away the old collections in this case. - _strings.Clear(); - _strings.Capacity = initialCapacity; - _stringToIds = new Dictionary(initialCapacity, comparer); - _stringToPathIds = new Dictionary(initialCapacity, comparer); -#if NET - } -#endif + // If the interner is in a reused translator, the comparer might not match between packets. + // Just throw away the old collections in this case. + _strings.Clear(); + _strings.Capacity = initialCapacity; + _stringToIds = new Dictionary(initialCapacity, comparer); + _stringToPathIds = new Dictionary(initialCapacity, comparer); + _packetStream.Position = 0; _packetStream.SetLength(0); diff --git a/src/MSBuildTaskHost/Framework/MSBuildNameIgnoreCaseComparer.cs b/src/MSBuildTaskHost/Framework/MSBuildNameIgnoreCaseComparer.cs index ef837fcced6..21fc4f862af 100644 --- a/src/MSBuildTaskHost/Framework/MSBuildNameIgnoreCaseComparer.cs +++ b/src/MSBuildTaskHost/Framework/MSBuildNameIgnoreCaseComparer.cs @@ -64,9 +64,6 @@ public bool Equals(string compareToString, string constrainedString, int start, return false; } -#if NET - return compareToString.AsSpan().Equals(constrainedString.AsSpan(start, lengthToCompare), StringComparison.OrdinalIgnoreCase); -#else if (lengthToCompare != compareToString.Length) { return false; @@ -107,7 +104,6 @@ public bool Equals(string compareToString, string constrainedString, int start, } return true; -#endif } /// diff --git a/src/MSBuildTaskHost/Framework/NativeMethods.cs b/src/MSBuildTaskHost/Framework/NativeMethods.cs index 725f0aab6b3..bb2e1763681 100644 --- a/src/MSBuildTaskHost/Framework/NativeMethods.cs +++ b/src/MSBuildTaskHost/Framework/NativeMethods.cs @@ -463,27 +463,6 @@ public SystemInformationData() else { ProcessorArchitectures processorArchitecture = ProcessorArchitectures.Unknown; - -#if NET || NETSTANDARD1_1_OR_GREATER - // Get the architecture from the runtime. - processorArchitecture = RuntimeInformation.OSArchitecture switch - { - Architecture.Arm => ProcessorArchitectures.ARM, - Architecture.Arm64 => ProcessorArchitectures.ARM64, - Architecture.X64 => ProcessorArchitectures.X64, - Architecture.X86 => ProcessorArchitectures.X86, -#if NET - Architecture.Wasm => ProcessorArchitectures.WASM, - Architecture.S390x => ProcessorArchitectures.S390X, - Architecture.LoongArch64 => ProcessorArchitectures.LOONGARCH64, - Architecture.Armv6 => ProcessorArchitectures.ARMV6, - Architecture.Ppc64le => ProcessorArchitectures.PPC64LE, -#endif - _ => ProcessorArchitectures.Unknown, - }; - -#endif - ProcessorArchitectureTypeNative = ProcessorArchitectureType = processorArchitecture; } } diff --git a/src/MSBuildTaskHost/GlobalUsings.cs b/src/MSBuildTaskHost/GlobalUsings.cs index a252c3a096b..81647980ca2 100644 --- a/src/MSBuildTaskHost/GlobalUsings.cs +++ b/src/MSBuildTaskHost/GlobalUsings.cs @@ -1,8 +1,4 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#if NET -global using LockType = System.Threading.Lock; -#else global using LockType = System.Object; -#endif \ No newline at end of file diff --git a/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs b/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs index 6d2609f1768..82c723243ec 100644 --- a/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs +++ b/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs @@ -2,9 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -#if NET -using System.Collections.Frozen; -#endif using System.Diagnostics.CodeAnalysis; #if CLR2COMPATIBILITY using Microsoft.Build.Shared.Concurrent; @@ -20,9 +17,6 @@ using System.Security.AccessControl; using System.Security.Principal; -#if NET451_OR_GREATER || NETCOREAPP -using System.Threading.Tasks; -#endif #nullable disable @@ -36,13 +30,6 @@ internal abstract class NodeEndpointOutOfProcBase : INodeEndpoint { #region Private Data -#if NETCOREAPP2_1_OR_GREATER - /// - /// The amount of time to wait for the client to connect to the host. - /// - private const int ClientConnectTimeout = 60000; -#endif // NETCOREAPP2_1 - /// /// The size of the buffers to use for named pipes /// @@ -121,17 +108,6 @@ internal abstract class NodeEndpointOutOfProcBase : INodeEndpoint /// private byte _parentPacketVersion; -#if NET - /// - /// The set of property names from handshake responsible for node version. - /// - private readonly FrozenSet _versionHandshakeGroup = [ - nameof(HandshakeComponents.FileVersionMajor), - nameof(HandshakeComponents.FileVersionMinor), - nameof(HandshakeComponents.FileVersionBuild), - nameof(HandshakeComponents.FileVersionPrivate)]; -#endif - #endregion #region INodeEndpoint Events @@ -396,9 +372,6 @@ private void PacketPumpProc() if (!_pipeServer.TryReadIntForHandshake( byteToAccept: index == 0 ? (byte?)CommunicationsUtilities.handshakeVersion : null, /* this will disconnect a < 16.8 host; it expects leading 00 or F5 or 06. 0x00 is a wildcard */ -#if NETCOREAPP2_1_OR_GREATER - ClientConnectTimeout, /* wait a long time for the handshake from this side */ -#endif out HandshakeResult result)) { CommunicationsUtilities.Trace($"Handshake failed with error: {result.ErrorMessage}"); @@ -422,13 +395,7 @@ private void PacketPumpProc() if (gotValidConnection) { // To ensure that our handshake and theirs have the same number of bytes, receive and send a magic number indicating EOS. - - if ( -#if NETCOREAPP2_1_OR_GREATER - _pipeServer.TryReadEndOfHandshakeSignal(false, ClientConnectTimeout, out HandshakeResult _)) /* wait a long time for the handshake from this side */ -#else - _pipeServer.TryReadEndOfHandshakeSignal(false, out HandshakeResult _)) -#endif + if (_pipeServer.TryReadEndOfHandshakeSignal(false, out HandshakeResult _)) { // Send supported PacketVersion after EndOfHandshakeSignal // Based on this parent node decides how to communicate with the child. @@ -510,13 +477,7 @@ private void PacketPumpProc() { if (localPipeServer.IsConnected) { -#if NET // OperatingSystem.IsWindows() is new in .NET 5.0 - if (OperatingSystem.IsWindows()) -#endif - { - localPipeServer.WaitForPipeDrain(); - } - + localPipeServer.WaitForPipeDrain(); localPipeServer.Disconnect(); } } @@ -536,27 +497,6 @@ private bool IsHandshakePartValid(KeyValuePair component, int hands return true; } -#if NET - // Check if this is a valid NET task host exception - bool isAllowedMismatch = false; - - if (component.Key == nameof(HandshakeComponents.Options)) - { - // NET Task host allows MSBuild.exe to connect to it even if they have bitness mismatch. - // 0x00FFFFFF is the handshake version included in component, the rest is the node type. - isAllowedMismatch = IsAllowedBitnessMismatch(component.Value, handshakePart); - } - else - { - isAllowedMismatch = _versionHandshakeGroup.Contains(component.Key) && component.Value == Handshake.NetTaskHostHandshakeVersion; - } - - if (isAllowedMismatch) - { - CommunicationsUtilities.Trace("Handshake for NET Host. Child host {0} for {1}.", handshakePart, component.Key); - return true; - } -#endif CommunicationsUtilities.Trace( "Handshake failed. Received {0} from host for {1} but expected {2}. Probably the host is a different MSBuild build.", handshakePart, @@ -566,26 +506,6 @@ private bool IsHandshakePartValid(KeyValuePair component, int hands return false; } -#if NET - /// - /// NET Task host allows MSBuild.exe to connect to it even if they have bitness mismatch. - /// 0x00FFFFFF is the handshake version included in component, the rest is the node type. - /// - private bool IsAllowedBitnessMismatch(int expectedOptions, int receivedOptions) - { - var expectedNodeType = (HandshakeOptions)(expectedOptions & 0x00FFFFFF); - var receivedNodeType = (HandshakeOptions)(receivedOptions & 0x00FFFFFF); - - // not X64 or Arm64 means we are running on x86 - bool receivedIsX86 = !Handshake.IsHandshakeOptionEnabled(receivedNodeType, HandshakeOptions.X64) && - !Handshake.IsHandshakeOptionEnabled(receivedNodeType, HandshakeOptions.Arm64); - - bool expectedIsX64 = Handshake.IsHandshakeOptionEnabled(expectedNodeType, HandshakeOptions.X64); - - return receivedIsX86 && expectedIsX64; - } -#endif - private void RunReadLoop( BufferedReadStream localReadPipe, NamedPipeServerStream localWritePipe, @@ -600,23 +520,13 @@ private void RunReadLoop( CommunicationsUtilities.Trace("Entering read loop."); byte[] headerByte = new byte[5]; ITranslator writeTranslator = null; -#if NET451_OR_GREATER - Task readTask = localReadPipe.ReadAsync(headerByte, 0, headerByte.Length, CancellationToken.None); -#elif NETCOREAPP - Task readTask = localReadPipe.ReadAsync(headerByte.AsMemory(), CancellationToken.None).AsTask(); -#else IAsyncResult result = localReadPipe.BeginRead(headerByte, 0, headerByte.Length, null, null); -#endif // Ordering is important. We want packetAvailable to supercede terminate otherwise we will not properly wait for all // packets to be sent by other threads which are shutting down, such as the logging thread. WaitHandle[] handles = new WaitHandle[] { -#if NET451_OR_GREATER || NETCOREAPP - ((IAsyncResult)readTask).AsyncWaitHandle, -#else result.AsyncWaitHandle, -#endif localPacketAvailable, localTerminatePacketPump, }; @@ -632,11 +542,7 @@ private void RunReadLoop( int bytesRead = 0; try { -#if NET451_OR_GREATER || NETCOREAPP - bytesRead = readTask.ConfigureAwait(false).GetAwaiter().GetResult(); -#else bytesRead = localReadPipe.EndRead(result); -#endif } catch (Exception e) { @@ -705,19 +611,9 @@ private void RunReadLoop( break; } -#if NET451_OR_GREATER - readTask = localReadPipe.ReadAsync(headerByte, 0, headerByte.Length, CancellationToken.None); -#elif NETCOREAPP - readTask = localReadPipe.ReadAsync(headerByte.AsMemory(), CancellationToken.None).AsTask(); -#else result = localReadPipe.BeginRead(headerByte, 0, headerByte.Length, null, null); -#endif -#if NET451_OR_GREATER || NETCOREAPP - handles[0] = ((IAsyncResult)readTask).AsyncWaitHandle; -#else handles[0] = result.AsyncWaitHandle; -#endif } break; diff --git a/src/MSBuildTaskHost/XMakeAttributes.cs b/src/MSBuildTaskHost/XMakeAttributes.cs index 47e15477b90..213b5889c81 100644 --- a/src/MSBuildTaskHost/XMakeAttributes.cs +++ b/src/MSBuildTaskHost/XMakeAttributes.cs @@ -451,11 +451,7 @@ internal static string GetCurrentMSBuildArchitecture() /// internal static string GetCurrentMSBuildRuntime() { -#if NET40_OR_GREATER - return MSBuildRuntimeValues.clr4; -#else return MSBuildRuntimeValues.net; -#endif } /// From 3a4dc4010b2d69fbc688b8c4b803f77ec4e27d81 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 11:00:57 -0800 Subject: [PATCH 024/136] MSBuildTaskHost: Remove BUILDINGAPPXTASKS code blocks BUILDINGAPPXTASKS is not relevant when building MSBuildTaskHost, so code compiled with BUILDINGAPPXTASKS can be removed. --- src/MSBuildTaskHost/ErrorUtilities.cs | 7 ------- src/MSBuildTaskHost/ExceptionHandling.cs | 11 ----------- src/MSBuildTaskHost/ResourceUtilities.cs | 14 -------------- 3 files changed, 32 deletions(-) diff --git a/src/MSBuildTaskHost/ErrorUtilities.cs b/src/MSBuildTaskHost/ErrorUtilities.cs index ed4a38b1650..ad95740b58a 100644 --- a/src/MSBuildTaskHost/ErrorUtilities.cs +++ b/src/MSBuildTaskHost/ErrorUtilities.cs @@ -11,11 +11,7 @@ using System.Threading; using Microsoft.Build.Framework; -#if BUILDINGAPPXTASKS -namespace Microsoft.Build.AppxPackage.Shared -#else namespace Microsoft.Build.Shared -#endif { /// /// This class contains methods that are useful for error checking and validation. @@ -39,8 +35,6 @@ public static void DebugTraceMessage(string category, string formatstring, param } } -#if !BUILDINGAPPXTASKS - internal static void VerifyThrowInternalError([DoesNotReturnIf(false)] bool condition, string message, params object?[]? args) { if (!condition) @@ -646,6 +640,5 @@ internal static void VerifyCollectionCopyToArguments( arrayParameterName); } } -#endif } } diff --git a/src/MSBuildTaskHost/ExceptionHandling.cs b/src/MSBuildTaskHost/ExceptionHandling.cs index 3db090854ae..a173f86f932 100644 --- a/src/MSBuildTaskHost/ExceptionHandling.cs +++ b/src/MSBuildTaskHost/ExceptionHandling.cs @@ -3,9 +3,6 @@ #nullable disable -#if BUILDINGAPPXTASKS -namespace Microsoft.Build.AppxPackage.Shared -#else using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -26,7 +23,6 @@ namespace Microsoft.Build.AppxPackage.Shared using Microsoft.Build.Framework; namespace Microsoft.Build.Shared -#endif { /// /// Utility methods for classifying and handling exceptions. @@ -105,12 +101,10 @@ internal static string DebugDumpPath /// internal static string DumpFilePath => s_dumpFileName; -#if !BUILDINGAPPXTASKS /// /// The filename that exceptions will be dumped to /// private static string s_dumpFileName; -#endif /// /// If the given exception is "ignorable under some circumstances" return false. /// Otherwise it's "really bad", and return true. @@ -128,9 +122,7 @@ internal static bool IsCriticalException(Exception e) #if !TASKHOST || e is CriticalTaskException #endif -#if !BUILDINGAPPXTASKS || e is InternalErrorException -#endif ) { // Ideally we would include NullReferenceException, because it should only ever be thrown by CLR (use ArgumentNullException for arguments) @@ -233,8 +225,6 @@ internal static LineAndColumn GetXmlLineAndColumn(Exception e) }; } -#if !BUILDINGAPPXTASKS - /// /// If the given exception is file IO related or Xml related return false. /// Otherwise, return true. @@ -424,7 +414,6 @@ internal static string ReadAnyExceptionFromFile(DateTime fromTimeUtc) return builder.ToString(); } -#endif /// Line and column pair. internal struct LineAndColumn diff --git a/src/MSBuildTaskHost/ResourceUtilities.cs b/src/MSBuildTaskHost/ResourceUtilities.cs index 9d1ccedb342..241831873a9 100644 --- a/src/MSBuildTaskHost/ResourceUtilities.cs +++ b/src/MSBuildTaskHost/ResourceUtilities.cs @@ -2,18 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -#if !BUILDINGAPPXTASKS using System.Resources; using System.Diagnostics; -#endif using System.Diagnostics.CodeAnalysis; using System.Globalization; -#if BUILDINGAPPXTASKS -namespace Microsoft.Build.AppxPackage.Shared -#else namespace Microsoft.Build.Shared -#endif { /// /// This class contains utility methods for dealing with resources. @@ -35,10 +29,6 @@ internal static class ResourceUtilities [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "Microsoft.Build.Shared.ResourceUtilities.#ExtractMessageCode(System.Boolean,System.String,System.String&)", Justification = "Unavoidable complexity")] internal static string ExtractMessageCode(bool msbuildCodeOnly, string message, out string? code) { -#if !BUILDINGAPPXTASKS - ErrorUtilities.VerifyThrowInternalNull(message); -#endif - code = null; int i = 0; @@ -47,7 +37,6 @@ internal static string ExtractMessageCode(bool msbuildCodeOnly, string message, i++; } -#if !BUILDINGAPPXTASKS if (msbuildCodeOnly) { if ( @@ -69,7 +58,6 @@ internal static string ExtractMessageCode(bool msbuildCodeOnly, string message, i += 8; } else -#endif { int j = i; for (; j < message.Length; j++) @@ -134,7 +122,6 @@ internal static string ExtractMessageCode(bool msbuildCodeOnly, string message, private static string GetHelpKeyword(string resourceName) => "MSBuild." + resourceName; -#if !BUILDINGAPPXTASKS /// /// Retrieves the contents of the named resource string. /// @@ -475,7 +462,6 @@ internal static void VerifyResourceStringExists(string resourceName) #endif ErrorUtilities.ThrowInternalError(e.Message); } -#endif } } } From d6e9d35e06d901ed207392fe9994636e7afecf55 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 11:13:37 -0800 Subject: [PATCH 025/136] MSBuildTaskHost: Remove CLR2COMPATIBILITY disabled code blocks MSBuildTaskHost is always compiled with CLR2COMPATIBILITY, so code blocks that aren't compiled with that conditional compilation constant can be removed. --- src/MSBuildTaskHost/CollectionHelpers.cs | 30 --- .../CommunicationsUtilities.cs | 132 +--------- src/MSBuildTaskHost/ErrorUtilities.cs | 33 --- src/MSBuildTaskHost/ExceptionHandling.cs | 47 +--- src/MSBuildTaskHost/FileSystem/FileSystems.cs | 11 - src/MSBuildTaskHost/FileUtilities.cs | 221 ----------------- .../Framework/AssemblyUtilities.cs | 33 --- .../Framework/BinaryTranslator.cs | 57 ----- src/MSBuildTaskHost/Framework/ITranslator.cs | 19 -- .../Framework/NativeMethods.cs | 230 +----------------- .../Framework/StringBuilderCache.cs | 12 - src/MSBuildTaskHost/InterningBinaryReader.cs | 18 -- src/MSBuildTaskHost/LogMessagePacketBase.cs | 15 -- .../OutOfProcTaskAppDomainWrapperBase.cs | 4 - .../MSBuild/OutOfProcTaskHostNode.cs | 168 +------------ src/MSBuildTaskHost/Modifiers.cs | 15 -- src/MSBuildTaskHost/NamedPipeUtil.cs | 10 - .../NodeEndpointOutOfProcBase.cs | 8 - .../TaskEngineAssemblyResolver.cs | 5 - src/MSBuildTaskHost/TaskHostConfiguration.cs | 12 - src/MSBuildTaskHost/XMakeAttributes.cs | 25 -- 21 files changed, 13 insertions(+), 1092 deletions(-) diff --git a/src/MSBuildTaskHost/CollectionHelpers.cs b/src/MSBuildTaskHost/CollectionHelpers.cs index 17265682d2a..f949db5a683 100644 --- a/src/MSBuildTaskHost/CollectionHelpers.cs +++ b/src/MSBuildTaskHost/CollectionHelpers.cs @@ -48,35 +48,5 @@ internal static bool ContainsValueAndIsEqual(this Dictionary dic return false; } - -#if !CLR2COMPATIBILITY - internal static bool SetEquivalent(IEnumerable a, IEnumerable b) - { - return a.ToHashSet().SetEquals(b); - } - - internal static bool DictionaryEquals(IReadOnlyDictionary a, IReadOnlyDictionary b) - { - if (a.Count != b.Count) - { - return false; - } - - foreach (var aKvp in a) - { - if (!b.TryGetValue(aKvp.Key, out var bValue)) - { - return false; - } - - if (!Equals(aKvp.Value, bValue)) - { - return false; - } - } - - return true; - } -#endif } } diff --git a/src/MSBuildTaskHost/CommunicationsUtilities.cs b/src/MSBuildTaskHost/CommunicationsUtilities.cs index f90f1ecc709..003a4c657e4 100644 --- a/src/MSBuildTaskHost/CommunicationsUtilities.cs +++ b/src/MSBuildTaskHost/CommunicationsUtilities.cs @@ -17,13 +17,6 @@ using Microsoft.Build.Shared; using Microsoft.Build.BackEnd; -#if !CLR2COMPATIBILITY -using Microsoft.Build.Shared.Debugging; -using System.Collections; -using System.Collections.Frozen; -using Microsoft.NET.StringTools; -#endif - #nullable disable namespace Microsoft.Build.Internal @@ -397,14 +390,6 @@ internal static class CommunicationsUtilities /// private static long s_lastLoggedTicks = DateTime.UtcNow.Ticks; -#if !CLR2COMPATIBILITY - /// - /// A set of environment variables cached from the last time we called GetEnvironmentVariables. - /// Used to avoid allocations if the environment has not changed. - /// - private static EnvironmentState s_environmentState; -#endif - /// /// Delegate to debug the communication utilities. /// @@ -461,15 +446,6 @@ internal static void SetEnvironmentVariable(string name, string value) } } -#if !CLR2COMPATIBILITY - /// - /// A container to atomically swap a cached set of environment variables and the block string used to create it. - /// The environment block property will only be set on Windows, since on Unix we need to directly call - /// Environment.GetEnvironmentVariables(). - /// - private sealed record class EnvironmentState(FrozenDictionary EnvironmentVariables, ReadOnlyMemory EnvironmentBlock = default); -#endif - /// /// Returns key value pairs of environment variables in a new dictionary /// with a case-insensitive key comparer. @@ -477,19 +453,8 @@ private sealed record class EnvironmentState(FrozenDictionary En /// /// Copied from the BCL implementation to eliminate some expensive security asserts on .NET Framework. /// -#if CLR2COMPATIBILITY internal static Dictionary GetEnvironmentVariables() { -#else - [System.Runtime.Versioning.SupportedOSPlatform("windows")] - private static FrozenDictionary GetEnvironmentVariablesWindows() - { - // The DebugUtils static constructor can set the MSBUILDDEBUGPATH environment variable to propagate the debug path to out of proc nodes. - // Need to ensure that constructor is called before this method returns in order to capture its env var write. - // Otherwise the env var is not captured and thus gets deleted when RequiestBuilder resets the environment based on the cached results of this method. - ErrorUtilities.VerifyThrowInternalNull(DebugUtils.ProcessInfoString, nameof(DebugUtils.DebugPath)); -#endif - unsafe { char* pEnvironmentBlock = null; @@ -510,17 +475,6 @@ private static FrozenDictionary GetEnvironmentVariablesWindows() } long stringBlockLength = pEnvironmentBlockEnd - pEnvironmentBlock; -#if !CLR2COMPATIBILITY - // Avoid allocating any objects if the environment still matches the last state. - // We speed this up by comparing the full block instead of individual key-value pairs. - ReadOnlySpan stringBlock = new(pEnvironmentBlock, (int)stringBlockLength); - EnvironmentState lastState = s_environmentState; - if (lastState?.EnvironmentBlock.Span.SequenceEqual(stringBlock) == true) - { - return lastState.EnvironmentVariables; - } -#endif - Dictionary table = new(200, StringComparer.OrdinalIgnoreCase); // Razzle has 150 environment variables // Copy strings out, parsing into pairs and inserting into the table. @@ -564,11 +518,7 @@ private static FrozenDictionary GetEnvironmentVariablesWindows() continue; } -#if !CLR2COMPATIBILITY - string key = Strings.WeakIntern(new ReadOnlySpan(pEnvironmentBlock + startKey, i - startKey)); -#else string key = new string(pEnvironmentBlock, startKey, i - startKey); -#endif i++; @@ -581,25 +531,13 @@ private static FrozenDictionary GetEnvironmentVariablesWindows() i++; } -#if !CLR2COMPATIBILITY - string value = Strings.WeakIntern(new ReadOnlySpan(pEnvironmentBlock + startValue, i - startValue)); -#else string value = new string(pEnvironmentBlock, startValue, i - startValue); -#endif // skip over 0 handled by for loop's i++ table[key] = value; } -#if !CLR2COMPATIBILITY - // Update with the current state. - EnvironmentState currentState = - new(table.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase), stringBlock.ToArray()); - s_environmentState = currentState; - return currentState.EnvironmentVariables; -#else return table; -#endif } finally { @@ -611,69 +549,6 @@ private static FrozenDictionary GetEnvironmentVariablesWindows() } } -#if !CLR2COMPATIBILITY - /// - /// Returns key value pairs of environment variables in a read-only dictionary - /// with a case-insensitive key comparer. - /// - /// If the environment variables have not changed since the last time - /// this method was called, the same dictionary instance will be returned. - /// - internal static FrozenDictionary GetEnvironmentVariables() - { - // Always call the native method on Windows, as we'll be able to avoid the internal - // string and Hashtable allocations caused by Environment.GetEnvironmentVariables(). - if (NativeMethodsShared.IsWindows) - { - return GetEnvironmentVariablesWindows(); - } - - IDictionary vars = Environment.GetEnvironmentVariables(); - - // Directly use the enumerator since Current will box DictionaryEntry. - IDictionaryEnumerator enumerator = vars.GetEnumerator(); - - // If every key-value pair matches the last state, return a cached dictionary. - FrozenDictionary lastEnvironmentVariables = s_environmentState?.EnvironmentVariables; - if (vars.Count == lastEnvironmentVariables?.Count) - { - bool sameState = true; - - while (enumerator.MoveNext() && sameState) - { - DictionaryEntry entry = enumerator.Entry; - if (!lastEnvironmentVariables.TryGetValue((string)entry.Key, out string value) - || !string.Equals((string)entry.Value, value, StringComparison.Ordinal)) - { - sameState = false; - } - } - - if (sameState) - { - return lastEnvironmentVariables; - } - } - - // Otherwise, allocate and update with the current state. - Dictionary table = new(vars.Count, EnvironmentVariableComparer); - - enumerator.Reset(); - while (enumerator.MoveNext()) - { - DictionaryEntry entry = enumerator.Entry; - string key = Strings.WeakIntern((string)entry.Key); - string value = Strings.WeakIntern((string)entry.Value); - table[key] = value; - } - - EnvironmentState newState = new(table.ToFrozenDictionary(EnvironmentVariableComparer)); - s_environmentState = newState; - - return newState.EnvironmentVariables; - } -#endif - /// /// Updates the environment to match the provided dictionary. /// @@ -1069,12 +944,7 @@ private static void TraceCore(int nodeId, string message) { lock (s_traceLock) { - s_debugDumpPath ??= -#if CLR2COMPATIBILITY - Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); -#else - DebugUtils.DebugPath; -#endif + s_debugDumpPath ??= Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); if (String.IsNullOrEmpty(s_debugDumpPath)) { diff --git a/src/MSBuildTaskHost/ErrorUtilities.cs b/src/MSBuildTaskHost/ErrorUtilities.cs index ad95740b58a..dc1540ca745 100644 --- a/src/MSBuildTaskHost/ErrorUtilities.cs +++ b/src/MSBuildTaskHost/ErrorUtilities.cs @@ -126,12 +126,6 @@ internal static void VerifyThrowInternalNull([NotNull] object? parameter, [Calle /// The object that should already have been used as a lock. internal static void VerifyThrowInternalLockHeld(object locker) { -#if !CLR2COMPATIBILITY - if (!Monitor.IsEntered(locker)) - { - ThrowInternalError("Lock should already have been taken"); - } -#endif } /// @@ -515,33 +509,6 @@ internal static void VerifyThrowArgumentLength([NotNull] string? parameter, [Cal } } -#if !CLR2COMPATIBILITY - /// - /// Throws an ArgumentNullException if the given collection is null - /// and ArgumentException if it has zero length. - /// - internal static void VerifyThrowArgumentLength([NotNull] IReadOnlyCollection parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) - { - VerifyThrowArgumentNull(parameter, parameterName); - - if (parameter.Count == 0) - { - ThrowArgumentLength(parameterName); - } - } - - /// - /// Throws an ArgumentException if the given collection is not null but of zero length. - /// - internal static void VerifyThrowArgumentLengthIfNotNull([MaybeNull] IReadOnlyCollection? parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) - { - if (parameter?.Count == 0) - { - ThrowArgumentLength(parameterName); - } - } -#endif - [DoesNotReturn] private static void ThrowArgumentLength(string? parameterName) { diff --git a/src/MSBuildTaskHost/ExceptionHandling.cs b/src/MSBuildTaskHost/ExceptionHandling.cs index a173f86f932..e825f1de9f5 100644 --- a/src/MSBuildTaskHost/ExceptionHandling.cs +++ b/src/MSBuildTaskHost/ExceptionHandling.cs @@ -17,9 +17,6 @@ using Microsoft.Build.Shared.FileSystem; using System.Xml.Schema; using System.Runtime.Serialization; -#if !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS -using Microsoft.Build.Shared.Debugging; -#endif using Microsoft.Build.Framework; namespace Microsoft.Build.Shared @@ -37,32 +34,7 @@ internal static class ExceptionHandling /// private static string GetDebugDumpPath() { - string debugPath = - - /* Unmerged change from project 'Microsoft.Build.Engine.OM.UnitTests (net7.0)' - Before: - // Cannot access change wave logic from these assemblies (https://github.com/dotnet/msbuild/issues/6707) - After: - // Cannot access change wave logic from these assemblies (https://github.com/dotnet/msbuild/issues/6707) - */ - /* Unmerged change from project 'Microsoft.Build.Engine.OM.UnitTests (net472)' - Before: - // Cannot access change wave logic from these assemblies (https://github.com/dotnet/msbuild/issues/6707) - After: - // Cannot access change wave logic from these assemblies (https://github.com/dotnet/msbuild/issues/6707) - */ - /* Unmerged change from project 'MSBuildTaskHost' - Before: - // Cannot access change wave logic from these assemblies (https://github.com/dotnet/msbuild/issues/6707) - After: - // Cannot access change wave logic from these assemblies (https://github.com/dotnet/msbuild/issues/6707) - */ - // Cannot access change wave logic from these assemblies (https://github.com/dotnet/msbuild/issues/6707) -#if CLR2COMPATIBILITY || MICROSOFT_BUILD_ENGINE_OM_UNITTESTS - Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); -#else - DebugUtils.DebugPath; -#endif + string debugPath = Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); return !string.IsNullOrEmpty(debugPath) ? debugPath @@ -122,8 +94,7 @@ internal static bool IsCriticalException(Exception e) #if !TASKHOST || e is CriticalTaskException #endif - || e is InternalErrorException - ) + || e is InternalErrorException) { // Ideally we would include NullReferenceException, because it should only ever be thrown by CLR (use ArgumentNullException for arguments) // but we should handle it if tasks and loggers throw it. @@ -132,20 +103,6 @@ internal static bool IsCriticalException(Exception e) return true; } -#if !CLR2COMPATIBILITY - // Check if any critical exceptions - var aggregateException = e as AggregateException; - - if (aggregateException != null) - { - // If the aggregate exception contains a critical exception it is considered a critical exception - if (aggregateException.InnerExceptions.Any(innerException => IsCriticalException(innerException))) - { - return true; - } - } -#endif - return false; } diff --git a/src/MSBuildTaskHost/FileSystem/FileSystems.cs b/src/MSBuildTaskHost/FileSystem/FileSystems.cs index c876af6bc77..d1dc29c6a6f 100644 --- a/src/MSBuildTaskHost/FileSystem/FileSystems.cs +++ b/src/MSBuildTaskHost/FileSystem/FileSystems.cs @@ -14,18 +14,7 @@ internal static class FileSystems private static IFileSystem GetFileSystem() { -#if CLR2COMPATIBILITY return MSBuildTaskHostFileSystem.Singleton(); -#else - if (NativeMethodsShared.IsWindows) - { - return MSBuildOnWindowsFileSystem.Singleton(); - } - else - { - return ManagedFileSystem.Singleton(); - } -#endif } } } diff --git a/src/MSBuildTaskHost/FileUtilities.cs b/src/MSBuildTaskHost/FileUtilities.cs index d0afcda6c90..b683368acb2 100644 --- a/src/MSBuildTaskHost/FileUtilities.cs +++ b/src/MSBuildTaskHost/FileUtilities.cs @@ -2,11 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -#if !CLR2COMPATIBILITY -using System.Collections.Concurrent; -#else using Microsoft.Build.Shared.Concurrent; -#endif using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -45,26 +41,10 @@ internal static partial class FileUtilities /// internal static string cacheDirectory = null; -#if !CLR2COMPATIBILITY - /// - /// AsyncLocal working directory for use during property/item expansion in multithreaded mode. - /// Set by MultiThreadedTaskEnvironmentDriver when building projects. null in multiprocess mode. - /// Using AsyncLocal ensures the value flows to child threads/tasks spawned during execution of tasks. - /// - private static readonly AsyncLocal s_currentThreadWorkingDirectory = new(); - internal static string CurrentThreadWorkingDirectory - { - get => s_currentThreadWorkingDirectory.Value; - set => s_currentThreadWorkingDirectory.Value = value; - } -#else // net35 taskhost does not support AsyncLocal, and the scenario is not relevant there. internal static string CurrentThreadWorkingDirectory = null; -#endif -#if CLR2COMPATIBILITY internal static string TempFileDirectory => Path.GetTempPath(); -#endif /// /// FOR UNIT TESTS ONLY @@ -352,18 +332,7 @@ internal static String GetDirectoryNameOfFullPath(String fullPath) internal static string TruncatePathToTrailingSegments(string path, int trailingSegmentsToKeep) { -#if !CLR2COMPATIBILITY - ErrorUtilities.VerifyThrowInternalLength(path, nameof(path)); - ErrorUtilities.VerifyThrow(trailingSegmentsToKeep >= 0, "trailing segments must be positive"); - - var segments = path.Split(FrameworkFileUtilities.Slashes, StringSplitOptions.RemoveEmptyEntries); - - var headingSegmentsToRemove = Math.Max(0, segments.Length - trailingSegmentsToKeep); - - return string.Join(DirectorySeparatorString, segments.Skip(headingSegmentsToRemove)); -#else return path; -#endif } internal static bool ContainsRelativePathSegments(string path) @@ -392,9 +361,6 @@ internal static bool ContainsRelativePathSegments(string path) return false; } -#if !CLR2COMPATIBILITY - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif private static bool RelativePathBoundsAreValid(string path, int leftIndex, int rightIndex) { var leftBound = leftIndex - 1 >= 0 @@ -408,9 +374,6 @@ private static bool RelativePathBoundsAreValid(string path, int leftIndex, int r return IsValidRelativePathBound(leftBound) && IsValidRelativePathBound(rightBound); } -#if !CLR2COMPATIBILITY - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif private static bool IsValidRelativePathBound(char? c) { return c == null || IsAnySlash(c.Value); @@ -435,13 +398,6 @@ internal static string NormalizePath(string directory, string file) return NormalizePath(Path.Combine(directory, file)); } -#if !CLR2COMPATIBILITY - internal static string NormalizePath(params string[] paths) - { - return NormalizePath(Path.Combine(paths)); - } -#endif - private static string GetFullPath(string path) { string uncheckedFullPath = NativeMethodsShared.GetFullPath(path); @@ -509,146 +465,8 @@ internal static string NormalizePathSeparatorsToForwardSlash(string path) return string.IsNullOrEmpty(path) ? path : path.Replace('\\', '/'); } -#if !CLR2COMPATIBILITY - /// - /// If on Unix, convert backslashes to slashes for strings that resemble paths. - /// The heuristic is if something resembles paths (contains slashes) check if the - /// first segment exists and is a directory. - /// Use a native shared method to massage file path. If the file is adjusted, - /// that qualifies is as a path. - /// - /// @baseDirectory is just passed to LooksLikeUnixFilePath, to help with the check - /// - internal static string MaybeAdjustFilePath(string value, string baseDirectory = "") - { - var comparisonType = StringComparison.Ordinal; - - // Don't bother with arrays or properties or network paths, or those that - // have no slashes. - if (NativeMethodsShared.IsWindows || string.IsNullOrEmpty(value) - || value.StartsWith("$(", comparisonType) || value.StartsWith("@(", comparisonType) - || value.StartsWith("\\\\", comparisonType)) - { - return value; - } - - // For Unix-like systems, we may want to convert backslashes to slashes - Span newValue = ConvertToUnixSlashes(value.ToCharArray()); - - // Find the part of the name we want to check, that is remove quotes, if present - bool shouldAdjust = newValue.IndexOf('/') != -1 && LooksLikeUnixFilePath(RemoveQuotes(newValue), baseDirectory); - return shouldAdjust ? newValue.ToString() : value; - } - - /// - /// If on Unix, convert backslashes to slashes for strings that resemble paths. - /// This overload takes and returns ReadOnlyMemory of characters. - /// - internal static ReadOnlyMemory MaybeAdjustFilePath(ReadOnlyMemory value, string baseDirectory = "") - { - if (NativeMethodsShared.IsWindows || value.IsEmpty) - { - return value; - } - - // Don't bother with arrays or properties or network paths. - if (value.Length >= 2) - { - var span = value.Span; - - // The condition is equivalent to span.StartsWith("$(") || span.StartsWith("@(") || span.StartsWith("\\\\") - if ((span[1] == '(' && (span[0] == '$' || span[0] == '@')) || - (span[1] == '\\' && span[0] == '\\')) - { - return value; - } - } - - // For Unix-like systems, we may want to convert backslashes to slashes - Span newValue = ConvertToUnixSlashes(value.ToArray()); - - // Find the part of the name we want to check, that is remove quotes, if present - bool shouldAdjust = newValue.IndexOf('/') != -1 && LooksLikeUnixFilePath(RemoveQuotes(newValue), baseDirectory); - return shouldAdjust ? newValue.ToString().AsMemory() : value; - } - - private static Span ConvertToUnixSlashes(Span path) - { - return path.IndexOf('\\') == -1 ? path : CollapseSlashes(path); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Span CollapseSlashes(Span str) - { - int sliceLength = 0; - - // Performs Regex.Replace(str, @"[\\/]+", "/") - for (int i = 0; i < str.Length; i++) - { - bool isCurSlash = IsAnySlash(str[i]); - bool isPrevSlash = i > 0 && IsAnySlash(str[i - 1]); - - if (!isCurSlash || !isPrevSlash) - { - str[sliceLength] = str[i] == '\\' ? '/' : str[i]; - sliceLength++; - } - } - - return str.Slice(0, sliceLength); - } - - private static Span RemoveQuotes(Span path) - { - int endId = path.Length - 1; - char singleQuote = '\''; - char doubleQuote = '\"'; - - bool hasQuotes = path.Length > 2 - && ((path[0] == singleQuote && path[endId] == singleQuote) - || (path[0] == doubleQuote && path[endId] == doubleQuote)); - - return hasQuotes ? path.Slice(1, endId - 1) : path; - } -#endif - -#if !CLR2COMPATIBILITY - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif internal static bool IsAnySlash(char c) => c == '/' || c == '\\'; -#if !CLR2COMPATIBILITY - /// - /// If on Unix, check if the string looks like a file path. - /// The heuristic is if something resembles paths (contains slashes) check if the - /// first segment exists and is a directory. - /// - /// If @baseDirectory is not null, then look for the first segment exists under - /// that - /// - internal static bool LooksLikeUnixFilePath(string value, string baseDirectory = "") - => LooksLikeUnixFilePath(value.AsSpan(), baseDirectory); - - internal static bool LooksLikeUnixFilePath(ReadOnlySpan value, string baseDirectory = "") - { - if (NativeMethodsShared.IsWindows) - { - return false; - } - - // The first slash will either be at the beginning of the string or after the first directory name - int directoryLength = value.Slice(1).IndexOf('/') + 1; - bool shouldCheckDirectory = directoryLength != 0; - - // Check for actual files or directories under / that get missed by the above logic - bool shouldCheckFileOrDirectory = !shouldCheckDirectory && value.Length > 0 && value[0] == '/'; - ReadOnlySpan directory = value.Slice(0, directoryLength); - - return (shouldCheckDirectory && DefaultFileSystem.DirectoryExists(Path.Combine(baseDirectory, directory.ToString()))) - || (shouldCheckFileOrDirectory && DefaultFileSystem.FileOrDirectoryExists(value.ToString())); - } -#endif - /// /// Extracts the directory from the given file-spec. /// @@ -674,35 +492,6 @@ internal static string GetDirectory(string fileSpec) return directory; } -#if !CLR2COMPATIBILITY - /// - /// Deletes all subdirectories within the specified directory without throwing exceptions. - /// This method enumerates all subdirectories in the given directory and attempts to delete - /// each one recursively. If any IO-related exceptions occur during enumeration or deletion, - /// they are silently ignored. - /// - /// The directory whose subdirectories should be deleted. - /// - /// This method is useful for cleanup operations where partial failure is acceptable. - /// It will not delete the root directory itself, only its subdirectories. - /// IO exceptions during directory enumeration or deletion are caught and ignored. - /// - internal static void DeleteSubdirectoriesNoThrow(string directory) - { - try - { - foreach (string dir in FileSystems.Default.EnumerateDirectories(directory)) - { - DeleteDirectoryNoThrow(dir, recursive: true, retryCount: 1); - } - } - catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) - { - // If we can't enumerate the directories, ignore. Other cases should be handled by DeleteDirectoryNoThrow. - } - } -#endif - /// /// Determines whether the given assembly file name has one of the listed extensions. /// @@ -1518,16 +1307,6 @@ private static bool PathsEqualNonAscii(string strA, string strB, int i, int leng return false; } -#if !CLR2COMPATIBILITY - /// - /// Clears the file existence cache. - /// - internal static void ClearFileExistenceCache() - { - FileExistenceCache.Clear(); - } -#endif - internal static void ReadFromStream(this Stream stream, byte[] content, int startIndex, int length) { stream.ReadExactly(content, startIndex, length); diff --git a/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs b/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs index b6cddae6f13..64937accaf3 100644 --- a/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs +++ b/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs @@ -14,14 +14,8 @@ namespace Microsoft.Build.Shared /// internal static class AssemblyUtilities { -#if !CLR2COMPATIBILITY - private static Lazy s_entryAssembly = new Lazy(() => GetEntryAssembly()); - public static Assembly EntryAssembly => s_entryAssembly.Value; -#else public static Assembly EntryAssembly = GetEntryAssembly(); -#endif -#if CLR2COMPATIBILITY /// /// Shim for the lack of in .NET 3.5. /// @@ -29,37 +23,10 @@ public static Type GetTypeInfo(this Type t) { return t; } -#endif public static AssemblyName CloneIfPossible(this AssemblyName assemblyNameToClone) { -#if CLR2COMPATIBILITY return (AssemblyName)assemblyNameToClone.Clone(); -#else - - // NOTE: In large projects, this is called a lot. Avoid calling AssemblyName.Clone - // because it clones the Version property (which is immutable) and the PublicKey property - // and the PublicKeyToken property. - // - // While the array themselves are mutable - throughout MSBuild they are only ever - // read from. - AssemblyName name = new AssemblyName(); - name.Name = assemblyNameToClone.Name; - name.SetPublicKey(assemblyNameToClone.GetPublicKey()); - name.SetPublicKeyToken(assemblyNameToClone.GetPublicKeyToken()); - name.Version = assemblyNameToClone.Version; - name.Flags = assemblyNameToClone.Flags; - name.ProcessorArchitecture = assemblyNameToClone.ProcessorArchitecture; - - name.CultureInfo = assemblyNameToClone.CultureInfo; - name.HashAlgorithm = assemblyNameToClone.HashAlgorithm; - name.VersionCompatibility = assemblyNameToClone.VersionCompatibility; - name.CodeBase = assemblyNameToClone.CodeBase; - name.KeyPair = assemblyNameToClone.KeyPair; - name.VersionCompatibility = assemblyNameToClone.VersionCompatibility; - - return name; -#endif } private static Assembly GetEntryAssembly() diff --git a/src/MSBuildTaskHost/Framework/BinaryTranslator.cs b/src/MSBuildTaskHost/Framework/BinaryTranslator.cs index 42643b4b805..326dd7ccd77 100644 --- a/src/MSBuildTaskHost/Framework/BinaryTranslator.cs +++ b/src/MSBuildTaskHost/Framework/BinaryTranslator.cs @@ -471,32 +471,6 @@ public void Translate(ref TimeSpan value) value = new System.TimeSpan(ticks); } - // MSBuildTaskHost is based on CLR 3.5, which does not have the 6-parameter constructor for BuildEventContext. - // However, it also does not ever need to translate BuildEventContexts, so it should be perfectly safe to - // compile this method out of that assembly. -#if !CLR2COMPATIBILITY - - /// - /// Translates a BuildEventContext - /// - /// - /// This method exists only because there is no serialization method built into the BuildEventContext - /// class, and it lives in Framework and we don't want to add a public method to it. - /// - /// The context to be translated. - public void Translate(ref BuildEventContext value) - { - value = new BuildEventContext( - _reader.ReadInt32(), - _reader.ReadInt32(), - _reader.ReadInt32(), - _reader.ReadInt32(), - _reader.ReadInt32(), - _reader.ReadInt32(), - _reader.ReadInt32()); - } -#endif - /// /// Translates a CultureInfo /// @@ -505,17 +479,12 @@ public void TranslateCulture(ref CultureInfo value) { string cultureName = _reader.ReadString(); -#if CLR2COMPATIBILITY // It may be that some culture codes are accepted on later .net framework versions // but not on the older 3.5 or 2.0. Fallbacks are required in this case to prevent // exceptions value = LoadCultureWithFallback(cultureName); -#else - value = new CultureInfo(cultureName); -#endif } -#if CLR2COMPATIBILITY private static CultureInfo LoadCultureWithFallback(string cultureName) { CultureInfo cultureInfo; @@ -536,7 +505,6 @@ private static bool TryLoadCulture(string cultureName, out CultureInfo cultureIn return false; } } -#endif /// /// Translates an enumeration. @@ -1333,31 +1301,6 @@ public void Translate(ref TimeSpan value) _writer.Write(value.Ticks); } - // MSBuildTaskHost is based on CLR 3.5, which does not have the 6-parameter constructor for BuildEventContext. - // However, it also does not ever need to translate BuildEventContexts, so it should be perfectly safe to - // compile this method out of that assembly. -#if !CLR2COMPATIBILITY - - /// - /// Translates a BuildEventContext - /// - /// - /// This method exists only because there is no serialization method built into the BuildEventContext - /// class, and it lives in Framework and we don't want to add a public method to it. - /// - /// The context to be translated. - public void Translate(ref BuildEventContext value) - { - _writer.Write(value.SubmissionId); - _writer.Write(value.NodeId); - _writer.Write(value.EvaluationId); - _writer.Write(value.ProjectInstanceId); - _writer.Write(value.ProjectContextId); - _writer.Write(value.TargetId); - _writer.Write(value.TaskId); - } -#endif - /// /// Translates a CultureInfo /// diff --git a/src/MSBuildTaskHost/Framework/ITranslator.cs b/src/MSBuildTaskHost/Framework/ITranslator.cs index 71059a49272..2172a34693d 100644 --- a/src/MSBuildTaskHost/Framework/ITranslator.cs +++ b/src/MSBuildTaskHost/Framework/ITranslator.cs @@ -263,25 +263,6 @@ BinaryWriter Writer /// The value to be translated. void Translate(ref TimeSpan value); - // MSBuildTaskHost is based on CLR 3.5, which does not have the 6-parameter constructor for BuildEventContext, - // which is what current implementations of this method use. However, it also does not ever need to translate - // BuildEventContexts, so it should be perfectly safe to compile this method out of that assembly. I am compiling - // the method out of the interface as well, instead of just making the method empty, so that if we ever do need - // to translate BuildEventContexts from the CLR 3.5 task host, it will become immediately obvious, rather than - // failing or misbehaving silently. -#if !CLR2COMPATIBILITY - - /// - /// Translates a BuildEventContext - /// - /// - /// This method exists only because there is no serialization method built into the BuildEventContext - /// class, and it lives in Framework and we don't want to add a public method to it. - /// - /// The context to be translated. - void Translate(ref BuildEventContext value); -#endif - /// /// Translates an enumeration. /// diff --git a/src/MSBuildTaskHost/Framework/NativeMethods.cs b/src/MSBuildTaskHost/Framework/NativeMethods.cs index bb2e1763681..8129a9c19a9 100644 --- a/src/MSBuildTaskHost/Framework/NativeMethods.cs +++ b/src/MSBuildTaskHost/Framework/NativeMethods.cs @@ -14,10 +14,6 @@ using Microsoft.Win32; using Microsoft.Win32.SafeHandles; -#if !CLR2COMPATIBILITY -using Microsoft.Build.Framework.Logging; -#endif - using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; #nullable disable @@ -291,11 +287,7 @@ internal class MemoryStatus /// public MemoryStatus() { -#if CLR2COMPATIBILITY _length = (uint)Marshal.SizeOf(typeof(MemoryStatus)); -#else - _length = (uint)Marshal.SizeOf(); -#endif } /// @@ -396,11 +388,7 @@ internal class SecurityAttributes { public SecurityAttributes() { -#if (CLR2COMPATIBILITY) _nLength = (uint)Marshal.SizeOf(typeof(SecurityAttributes)); -#else - _nLength = (uint)Marshal.SizeOf(); -#endif } private uint _nLength; @@ -741,11 +729,7 @@ internal static bool IsUnixLike [SupportedOSPlatformGuard("linux")] internal static bool IsLinux { -#if CLR2COMPATIBILITY get { return false; } -#else - get { return RuntimeInformation.IsOSPlatform(OSPlatform.Linux); } -#endif } /// @@ -753,56 +737,24 @@ internal static bool IsLinux /// internal static bool IsBSD { -#if CLR2COMPATIBILITY get { return false; } -#else - get - { - return RuntimeInformation.IsOSPlatform(OSPlatform.Create("FREEBSD")) || - RuntimeInformation.IsOSPlatform(OSPlatform.Create("NETBSD")) || - RuntimeInformation.IsOSPlatform(OSPlatform.Create("OPENBSD")); - } -#endif } -#if !CLR2COMPATIBILITY - private static bool? _isWindows; -#endif /// /// Gets a flag indicating if we are running under some version of Windows /// [SupportedOSPlatformGuard("windows")] internal static bool IsWindows { -#if CLR2COMPATIBILITY get { return true; } -#else - get - { - _isWindows ??= RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - return _isWindows.Value; - } -#endif } -#if !CLR2COMPATIBILITY - private static bool? _isOSX; -#endif - /// /// Gets a flag indicating if we are running under Mac OSX /// internal static bool IsOSX { -#if CLR2COMPATIBILITY get { return false; } -#else - get - { - _isOSX ??= RuntimeInformation.IsOSPlatform(OSPlatform.OSX); - return _isOSX.Value; - } -#endif } /// @@ -833,31 +785,6 @@ internal static bool OSUsesCaseSensitivePaths get { return IsLinux; } } -#if !CLR2COMPATIBILITY - /// - /// Determines whether the file system is case sensitive by creating a test file. - /// Copied from FileUtilities.GetIsFileSystemCaseSensitive() in Shared. - /// FIXME: shared code should be consolidated to Framework https://github.com/dotnet/msbuild/issues/6984 - /// - private static readonly Lazy s_isFileSystemCaseSensitive = new(() => - { - try - { - string pathWithUpperCase = Path.Combine(Path.GetTempPath(), $"INTCASESENSITIVETEST{Guid.NewGuid():N}"); - using (new FileStream(pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose)) - { - return !File.Exists(pathWithUpperCase.ToLowerInvariant()); - } - } - catch - { - return OSUsesCaseSensitivePaths; - } - }); - - internal static bool IsFileSystemCaseSensitive => s_isFileSystemCaseSensitive.Value; -#endif - /// /// The base directory for all framework paths in Mono /// @@ -1131,32 +1058,7 @@ internal static bool MakeSymbolicLink(string newFileName, string existingFileNam /// internal static DateTime GetLastWriteFileUtcTime(string fullPath) { -#if !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS - if (Traits.Instance.EscapeHatches.AlwaysDoImmutableFilesUpToDateCheck) - { - return LastWriteFileUtcTime(fullPath); - } - - bool isNonModifiable = FileClassifier.Shared.IsNonModifiable(fullPath); - if (isNonModifiable) - { - if (ImmutableFilesTimestampCache.Shared.TryGetValue(fullPath, out DateTime modifiedAt)) - { - return modifiedAt; - } - } - - DateTime modifiedTime = LastWriteFileUtcTime(fullPath); - - if (isNonModifiable && modifiedTime != DateTime.MinValue) - { - ImmutableFilesTimestampCache.Shared.TryAdd(fullPath, modifiedTime); - } - - return modifiedTime; -#else return LastWriteFileUtcTime(fullPath); -#endif DateTime LastWriteFileUtcTime(string path) { @@ -1359,56 +1261,18 @@ internal static void KillTree(int processIdToKill) internal static int GetParentProcessId(int processId) { int ParentID = 0; -#if !CLR2COMPATIBILITY - if (IsUnixLike) + using SafeProcessHandle hProcess = OpenProcess(eDesiredAccess.PROCESS_QUERY_INFORMATION, false, processId); { - string line = null; - - try + if (!hProcess.IsInvalid) { - // /proc//stat returns a bunch of space separated fields. Get that string - - // TODO: this was - // using (var r = FileUtilities.OpenRead("/proc/" + processId + "/stat")) - // and could be again when FileUtilities moves to Framework - - using var fileStream = new FileStream($"/proc/{processId}/stat", FileMode.Open, System.IO.FileAccess.Read); - using StreamReader r = new(fileStream); + // UNDONE: NtQueryInformationProcess will fail if we are not elevated and other process is. Advice is to change to use ToolHelp32 API's + // For now just return zero and worst case we will not kill some children. + PROCESS_BASIC_INFORMATION pbi = new PROCESS_BASIC_INFORMATION(); + int pSize = 0; - line = r.ReadLine(); - } - catch // Ignore errors since the process may have terminated - { - } - - if (!string.IsNullOrWhiteSpace(line)) - { - // One of the fields is the process name. It may contain any characters, but since it's - // in parenthesis, we can finds its end by looking for the last parenthesis. After that, - // there comes a space, then the second fields separated by a space is the parent id. - string[] statFields = line.Substring(line.LastIndexOf(')')).Split(MSBuildConstants.SpaceChar, 4); - if (statFields.Length >= 3) + if (0 == NtQueryInformationProcess(hProcess, PROCESSINFOCLASS.ProcessBasicInformation, ref pbi, pbi.Size, ref pSize)) { - ParentID = Int32.Parse(statFields[2]); - } - } - } - else -#endif - { - using SafeProcessHandle hProcess = OpenProcess(eDesiredAccess.PROCESS_QUERY_INFORMATION, false, processId); - { - if (!hProcess.IsInvalid) - { - // UNDONE: NtQueryInformationProcess will fail if we are not elevated and other process is. Advice is to change to use ToolHelp32 API's - // For now just return zero and worst case we will not kill some children. - PROCESS_BASIC_INFORMATION pbi = new PROCESS_BASIC_INFORMATION(); - int pSize = 0; - - if (0 == NtQueryInformationProcess(hProcess, PROCESSINFOCLASS.ProcessBasicInformation, ref pbi, pbi.Size, ref pSize)) - { - ParentID = (int)pbi.InheritedFromUniqueProcessId; - } + ParentID = (int)pbi.InheritedFromUniqueProcessId; } } } @@ -1528,7 +1392,6 @@ private static unsafe int GetFullPathWin32(string target, int bufferLength, char /// True only if the contents of and the first characters in are identical. private static unsafe bool AreStringsEqual(char* buffer, int len, string s) { -#if CLR2COMPATIBILITY if (len != s.Length) { return false; @@ -1543,9 +1406,6 @@ private static unsafe bool AreStringsEqual(char* buffer, int len, string s) } return true; -#else - return s.AsSpan().SequenceEqual(new ReadOnlySpan(buffer, len)); -#endif } internal static void VerifyThrowWin32Result(int result) @@ -1558,80 +1418,6 @@ internal static void VerifyThrowWin32Result(int result) } } -#if !CLR2COMPATIBILITY - internal static (bool acceptAnsiColorCodes, bool outputIsScreen, uint? originalConsoleMode) QueryIsScreenAndTryEnableAnsiColorCodes(StreamHandleType handleType = StreamHandleType.StdOut) - { - if (Console.IsOutputRedirected) - { - // There's no ANSI terminal support if console output is redirected. - return (acceptAnsiColorCodes: false, outputIsScreen: false, originalConsoleMode: null); - } - - if (Console.BufferHeight == 0 || Console.BufferWidth == 0) - { - // The current console doesn't have a valid buffer size, which means it is not a real console. let's default to not using TL - // in those scenarios. - return (acceptAnsiColorCodes: false, outputIsScreen: false, originalConsoleMode: null); - } - - bool acceptAnsiColorCodes = false; - bool outputIsScreen = false; - uint? originalConsoleMode = null; - if (IsWindows) - { - try - { - IntPtr outputStream = GetStdHandle((int)handleType); - if (GetConsoleMode(outputStream, out uint consoleMode)) - { - if ((consoleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == ENABLE_VIRTUAL_TERMINAL_PROCESSING) - { - // Console is already in required state. - acceptAnsiColorCodes = true; - } - else - { - originalConsoleMode = consoleMode; - consoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; - if (SetConsoleMode(outputStream, consoleMode) && GetConsoleMode(outputStream, out consoleMode)) - { - // We only know if vt100 is supported if the previous call actually set the new flag, older - // systems ignore the setting. - acceptAnsiColorCodes = (consoleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == ENABLE_VIRTUAL_TERMINAL_PROCESSING; - } - } - - uint fileType = GetFileType(outputStream); - // The std out is a char type (LPT or Console). - outputIsScreen = fileType == FILE_TYPE_CHAR; - acceptAnsiColorCodes &= outputIsScreen; - } - } - catch - { - // In the unlikely case that the above fails we just ignore and continue. - } - } - else - { - // On posix OSes detect whether the terminal supports VT100 from the value of the TERM environment variable. - acceptAnsiColorCodes = AnsiDetector.IsAnsiSupported(Environment.GetEnvironmentVariable("TERM")); - // It wasn't redirected as tested above so we assume output is screen/console - outputIsScreen = true; - } - return (acceptAnsiColorCodes, outputIsScreen, originalConsoleMode); - } - - internal static void RestoreConsoleMode(uint? originalConsoleMode, StreamHandleType handleType = StreamHandleType.StdOut) - { - if (IsWindows && originalConsoleMode is not null) - { - IntPtr stdOut = GetStdHandle((int)handleType); - _ = SetConsoleMode(stdOut, originalConsoleMode.Value); - } - } -#endif // !CLR2COMPATIBILITY - #endregion #region PInvoke diff --git a/src/MSBuildTaskHost/Framework/StringBuilderCache.cs b/src/MSBuildTaskHost/Framework/StringBuilderCache.cs index 5fd67790e9d..68c849ac118 100644 --- a/src/MSBuildTaskHost/Framework/StringBuilderCache.cs +++ b/src/MSBuildTaskHost/Framework/StringBuilderCache.cs @@ -4,9 +4,6 @@ using System; using System.Diagnostics; using System.Text; -#if DEBUG && !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS -using Microsoft.Build.Eventing; -#endif #nullable disable @@ -50,18 +47,12 @@ public static StringBuilder Acquire(int capacity = 16 /*StringBuilder.DefaultCap if (capacity <= sb.Capacity) { sb.Length = 0; // Equivalent of sb.Clear() that works on .Net 3.5 -#if DEBUG && !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS - MSBuildEventSource.Log.ReusableStringBuilderFactoryStart(hash: sb.GetHashCode(), newCapacity: capacity, oldCapacity: sb.Capacity, type: "sbc-hit"); -#endif return sb; } } } StringBuilder stringBuilder = new StringBuilder(capacity); -#if DEBUG && !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS - MSBuildEventSource.Log.ReusableStringBuilderFactoryStart(hash: stringBuilder.GetHashCode(), newCapacity: capacity, oldCapacity: stringBuilder.Capacity, type: "sbc-miss"); -#endif return stringBuilder; } @@ -92,9 +83,6 @@ public static void Release(StringBuilder sb) Debug.Assert(StringBuilderCache.t_cachedInstance == null, "Unexpected replacing of other StringBuilder."); StringBuilderCache.t_cachedInstance = sb; } -#if DEBUG && !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS - MSBuildEventSource.Log.ReusableStringBuilderFactoryStop(hash: sb.GetHashCode(), returningCapacity: sb.Capacity, returningLength: sb.Length, type: sb.Capacity <= MAX_BUILDER_SIZE ? "sbc-return" : "sbc-discard"); -#endif } /// diff --git a/src/MSBuildTaskHost/InterningBinaryReader.cs b/src/MSBuildTaskHost/InterningBinaryReader.cs index 307cc68bdc9..1aca5dff8a4 100644 --- a/src/MSBuildTaskHost/InterningBinaryReader.cs +++ b/src/MSBuildTaskHost/InterningBinaryReader.cs @@ -7,10 +7,6 @@ using System.Diagnostics; using System.Threading; -#if !CLR2COMPATIBILITY -using System.Buffers; -#endif - using ErrorUtilities = Microsoft.Build.Shared.ErrorUtilities; using Microsoft.NET.StringTools; @@ -152,12 +148,8 @@ public override String ReadString() charsRead = _decoder.GetChars(rawBuffer, rawPosition, n, charBuffer, 0); return Strings.WeakIntern(charBuffer.AsSpan(0, charsRead)); } -#if !CLR2COMPATIBILITY - resultBuffer ??= ArrayPool.Shared.Rent(stringLength); // Actual string length in chars may be smaller. -#else // Since NET35 is only used in rare TaskHost processes, we decided to leave it as-is. resultBuffer ??= new char[stringLength]; // Actual string length in chars may be smaller. -#endif charsRead += _decoder.GetChars(rawBuffer, rawPosition, n, resultBuffer, charsRead); currPos += n; @@ -173,16 +165,6 @@ public override String ReadString() Debug.Assert(false, e.ToString()); throw; } -#if !CLR2COMPATIBILITY - finally - { - // resultBuffer shall always be either Rented or null - if (resultBuffer != null) - { - ArrayPool.Shared.Return(resultBuffer); - } - } -#endif } /// diff --git a/src/MSBuildTaskHost/LogMessagePacketBase.cs b/src/MSBuildTaskHost/LogMessagePacketBase.cs index 33e7c619c97..5ce14f6e420 100644 --- a/src/MSBuildTaskHost/LogMessagePacketBase.cs +++ b/src/MSBuildTaskHost/LogMessagePacketBase.cs @@ -506,11 +506,7 @@ private static Delegate CreateDelegateRobust(Type type, Object firstArgument, Me { try { -#if CLR2COMPATIBILITY delegateMethod = Delegate.CreateDelegate(type, firstArgument, methodInfo); -#else - delegateMethod = methodInfo.CreateDelegate(type, firstArgument); -#endif } catch (FileLoadException) when (i < 5) { @@ -899,11 +895,6 @@ private void WriteResponseFileUsedEventToStream(ResponseFileUsedEventArgs respon string filePath = responseFileUsedEventArgs.ResponseFilePath; translator.Translate(ref filePath); - -#if !CLR2COMPATIBILITY - DateTime timestamp = responseFileUsedEventArgs.RawTimestamp; - translator.Translate(ref timestamp); -#endif } #endregion @@ -1059,12 +1050,6 @@ private ResponseFileUsedEventArgs ReadResponseFileUsedEventFromStream(ITranslato translator.Translate(ref responseFilePath); ResponseFileUsedEventArgs buildEvent = new ResponseFileUsedEventArgs(responseFilePath); -#if !CLR2COMPATIBILITY - DateTime timestamp = default; - translator.Translate(ref timestamp); - buildEvent.RawTimestamp = timestamp; -#endif - return buildEvent; } diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs index c4c05be13a4..d2933c31cf3 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs @@ -272,11 +272,7 @@ private OutOfProcTaskHostTaskResult InstantiateAndExecuteTaskInSTAThread( } finally { -#if CLR2COMPATIBILITY taskRunnerFinished.Close(); -#else - taskRunnerFinished.Dispose(); -#endif taskRunnerFinished = null; } diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs index ad39adacd46..32d90754047 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs @@ -11,9 +11,6 @@ using Microsoft.Build.BackEnd; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -#if !CLR2COMPATIBILITY -using Microsoft.Build.Experimental.FileAccess; -#endif using Microsoft.Build.Internal; using Microsoft.Build.Shared; #if FEATURE_APPDOMAIN @@ -31,12 +28,7 @@ internal class OutOfProcTaskHostNode : #if FEATURE_APPDOMAIN MarshalByRefObject, #endif - INodePacketFactory, INodePacketHandler, -#if CLR2COMPATIBILITY - IBuildEngine3 -#else - IBuildEngine10 -#endif + INodePacketFactory, INodePacketHandler, IBuildEngine3 { /// /// Keeps a record of all environment variables that, on startup of the task host, have a different @@ -164,13 +156,6 @@ internal class OutOfProcTaskHostNode : /// private bool _nodeReuse; -#if !CLR2COMPATIBILITY - /// - /// The task object cache. - /// - private RegisteredTaskObjectCacheBase _registeredTaskObjectCache; -#endif - /// /// Constructor. /// @@ -196,10 +181,6 @@ public OutOfProcTaskHostNode() thisINodePacketFactory.RegisterPacketHandler(NodePacketType.TaskHostConfiguration, TaskHostConfiguration.FactoryForDeserialization, this); thisINodePacketFactory.RegisterPacketHandler(NodePacketType.TaskHostTaskCancelled, TaskHostTaskCancelled.FactoryForDeserialization, this); thisINodePacketFactory.RegisterPacketHandler(NodePacketType.NodeBuildComplete, NodeBuildComplete.FactoryForDeserialization, this); - -#if !CLR2COMPATIBILITY - EngineServices = new EngineServicesImpl(this); -#endif } #region IBuildEngine Implementation (Properties) @@ -417,134 +398,6 @@ public void Reacquire() #endregion // IBuildEngine3 Implementation -#if !CLR2COMPATIBILITY - #region IBuildEngine4 Implementation - - /// - /// Registers an object with the system that will be disposed of at some specified time - /// in the future. - /// - /// The key used to retrieve the object. - /// The object to be held for later disposal. - /// The lifetime of the object. - /// The object may be disposed earlier that the requested time if - /// MSBuild needs to reclaim memory. - public void RegisterTaskObject(object key, object obj, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection) - { - _registeredTaskObjectCache.RegisterTaskObject(key, obj, lifetime, allowEarlyCollection); - } - - /// - /// Retrieves a previously registered task object stored with the specified key. - /// - /// The key used to retrieve the object. - /// The lifetime of the object. - /// - /// The registered object, or null is there is no object registered under that key or the object - /// has been discarded through early collection. - /// - public object GetRegisteredTaskObject(object key, RegisteredTaskObjectLifetime lifetime) - { - return _registeredTaskObjectCache.GetRegisteredTaskObject(key, lifetime); - } - - /// - /// Unregisters a previously-registered task object. - /// - /// The key used to retrieve the object. - /// The lifetime of the object. - /// - /// The registered object, or null is there is no object registered under that key or the object - /// has been discarded through early collection. - /// - public object UnregisterTaskObject(object key, RegisteredTaskObjectLifetime lifetime) - { - return _registeredTaskObjectCache.UnregisterTaskObject(key, lifetime); - } - - #endregion - - #region IBuildEngine5 Implementation - - /// - /// Logs a telemetry event. - /// - /// The event name. - /// The list of properties associated with the event. - public void LogTelemetry(string eventName, IDictionary properties) - { - SendBuildEvent(new TelemetryEventArgs - { - EventName = eventName, - Properties = properties == null ? new Dictionary() : new Dictionary(properties), - }); - } - - #endregion - - #region IBuildEngine6 Implementation - - /// - /// Gets the global properties for the current project. - /// - /// An containing the global properties of the current project. - public IReadOnlyDictionary GetGlobalProperties() - { - return new Dictionary(_currentConfiguration.GlobalProperties); - } - - #endregion - - #region IBuildEngine9 Implementation - - public int RequestCores(int requestedCores) - { - // No resource management in OOP nodes - throw new NotImplementedException(); - } - - public void ReleaseCores(int coresToRelease) - { - // No resource management in OOP nodes - throw new NotImplementedException(); - } - - #endregion - - #region IBuildEngine10 Members - - [Serializable] - private sealed class EngineServicesImpl : EngineServices - { - private readonly OutOfProcTaskHostNode _taskHost; - - internal EngineServicesImpl(OutOfProcTaskHostNode taskHost) - { - _taskHost = taskHost; - } - - /// - /// No logging verbosity optimization in OOP nodes. - /// - public override bool LogsMessagesOfImportance(MessageImportance importance) => true; - - /// - public override bool IsTaskInputLoggingEnabled - { - get - { - ErrorUtilities.VerifyThrow(_taskHost._currentConfiguration != null, "We should never have a null configuration during a BuildEngine callback!"); - return _taskHost._currentConfiguration.IsTaskInputLoggingEnabled; - } - } - } - - public EngineServices EngineServices { get; } - - #endregion - -#endif - #region INodePacketFactory Members /// @@ -628,9 +481,6 @@ public void PacketReceived(int node, INodePacket packet) /// The reason for shutting down. public NodeEngineShutdownReason Run(out Exception shutdownException, bool nodeReuse = false, byte parentPacketVersion = 1) { -#if !CLR2COMPATIBILITY - _registeredTaskObjectCache = new RegisteredTaskObjectCacheBase(); -#endif shutdownException = null; // Snapshot the current environment @@ -819,11 +669,6 @@ private NodeEngineShutdownReason HandleShutdown() debugWriter?.WriteLine("Node shutting down with reason {0}.", _shutdownReason); -#if !CLR2COMPATIBILITY - _registeredTaskObjectCache.DisposeCacheObjects(RegisteredTaskObjectLifetime.Build); - _registeredTaskObjectCache = null; -#endif - // On Windows, a process holds a handle to the current directory, // so reset it away from a user-requested folder that may get deleted. NativeMethodsShared.SetCurrentDirectory(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory); @@ -850,17 +695,10 @@ private NodeEngineShutdownReason HandleShutdown() _nodeEndpoint.Disconnect(); // Dispose these WaitHandles -#if CLR2COMPATIBILITY _packetReceivedEvent.Close(); _shutdownEvent.Close(); _taskCompleteEvent.Close(); _taskCancelledEvent.Close(); -#else - _packetReceivedEvent.Dispose(); - _shutdownEvent.Dispose(); - _taskCompleteEvent.Dispose(); - _taskCancelledEvent.Dispose(); -#endif return _shutdownReason; } @@ -924,9 +762,7 @@ private void RunTask(object state) string taskName = taskConfiguration.TaskName; string taskLocation = taskConfiguration.TaskLocation; -#if !CLR2COMPATIBILITY - TaskFactoryUtilities.RegisterAssemblyResolveHandlersFromManifest(taskLocation); -#endif + // We will not create an appdomain now because of a bug // As a fix, we will create the class directly without wrapping it in a domain _taskWrapper = new OutOfProcTaskAppDomainWrapper(); diff --git a/src/MSBuildTaskHost/Modifiers.cs b/src/MSBuildTaskHost/Modifiers.cs index 5e17fc8b41d..1e0ea38c2c3 100644 --- a/src/MSBuildTaskHost/Modifiers.cs +++ b/src/MSBuildTaskHost/Modifiers.cs @@ -2,11 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -#if !CLR2COMPATIBILITY -using System.Collections.Frozen; -#else using System.Collections.Generic; -#endif using System.Diagnostics.CodeAnalysis; using System.IO; using Microsoft.Build.Framework; @@ -65,7 +61,6 @@ internal static class ItemSpecModifiers DefiningProjectExtension }; -#if CLR2COMPATIBILITY private static readonly HashSet s_tableOfItemSpecModifiers = new HashSet(All, StringComparer.OrdinalIgnoreCase); private static readonly HashSet s_tableOfDefiningProjectModifiers = new HashSet( [ @@ -74,16 +69,6 @@ internal static class ItemSpecModifiers DefiningProjectName, DefiningProjectExtension, ], StringComparer.OrdinalIgnoreCase); -#else - private static readonly FrozenSet s_tableOfItemSpecModifiers = FrozenSet.Create(StringComparer.OrdinalIgnoreCase, All); - private static readonly FrozenSet s_tableOfDefiningProjectModifiers = FrozenSet.Create(StringComparer.OrdinalIgnoreCase, - [ - DefiningProjectFullPath, - DefiningProjectDirectory, - DefiningProjectName, - DefiningProjectExtension, - ]); -#endif /// /// Indicates if the given name is reserved for an item-spec modifier. diff --git a/src/MSBuildTaskHost/NamedPipeUtil.cs b/src/MSBuildTaskHost/NamedPipeUtil.cs index 0b85b05bacd..f9fce908434 100644 --- a/src/MSBuildTaskHost/NamedPipeUtil.cs +++ b/src/MSBuildTaskHost/NamedPipeUtil.cs @@ -24,19 +24,9 @@ internal static string GetPlatformSpecificPipeName(string pipeName) { if (NativeMethodsShared.IsUnixLike) { - // If we're on a Unix machine then named pipes are implemented using Unix Domain Sockets. - // Most Unix systems have a maximum path length limit for Unix Domain Sockets, with - // Mac having a particularly short one. Mac also has a generated temp directory that - // can be quite long, leaving very little room for the actual pipe name. Fortunately, - // '/tmp' is mandated by POSIX to always be a valid temp directory, so we can use that - // instead. -#if !CLR2COMPATIBILITY - return Path.Combine("/tmp", pipeName); -#else // We should never get here. This would be a net35 task host running on unix. ErrorUtilities.ThrowInternalError("Task host used on unix in retrieving the pipe name."); return string.Empty; -#endif } else { diff --git a/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs b/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs index 82c723243ec..cae216b87e8 100644 --- a/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs +++ b/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs @@ -3,11 +3,7 @@ using System; using System.Diagnostics.CodeAnalysis; -#if CLR2COMPATIBILITY using Microsoft.Build.Shared.Concurrent; -#else -using System.Collections.Concurrent; -#endif using System.Threading; using Microsoft.Build.Internal; using Microsoft.Build.Shared; @@ -274,11 +270,7 @@ private void InternalDisconnect() ErrorUtilities.VerifyThrow(_packetPump.ManagedThreadId != Thread.CurrentThread.ManagedThreadId, "Can't join on the same thread."); _terminatePacketPump.Set(); _packetPump.Join(); -#if CLR2COMPATIBILITY _terminatePacketPump.Close(); -#else - _terminatePacketPump.Dispose(); -#endif _pipeServer.Dispose(); _packetPump = null; ChangeLinkStatus(LinkStatus.Inactive); diff --git a/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs b/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs index 44b243ab0f0..ef6162fd211 100644 --- a/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs +++ b/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs @@ -113,12 +113,7 @@ private Assembly ResolveAssembly(AssemblyLoadContext assemblyLoadContext, Assemb if (taskAssemblyName.Equals(argAssemblyName)) { -#if (!CLR2COMPATIBILITY) - return Assembly.UnsafeLoadFrom(_taskAssemblyFile); -#else return Assembly.LoadFrom(_taskAssemblyFile); -#endif - } #else // !FEATURE_APPDOMAIN AssemblyNameExtension taskAssemblyName = new AssemblyNameExtension(AssemblyLoadContext.GetAssemblyName(_taskAssemblyFile)); diff --git a/src/MSBuildTaskHost/TaskHostConfiguration.cs b/src/MSBuildTaskHost/TaskHostConfiguration.cs index 8b2ecc5fedc..df17bd8f9ef 100644 --- a/src/MSBuildTaskHost/TaskHostConfiguration.cs +++ b/src/MSBuildTaskHost/TaskHostConfiguration.cs @@ -497,25 +497,13 @@ public void Translate(ITranslator translator) translator.TranslateDictionary(ref _globalParameters, StringComparer.OrdinalIgnoreCase); translator.Translate(collection: ref _warningsAsErrors, objectTranslator: (ITranslator t, ref string s) => t.Translate(ref s), -#if CLR2COMPATIBILITY collectionFactory: count => new HashSet(StringComparer.OrdinalIgnoreCase)); -#else - collectionFactory: count => new HashSet(count, StringComparer.OrdinalIgnoreCase)); -#endif translator.Translate(collection: ref _warningsNotAsErrors, objectTranslator: (ITranslator t, ref string s) => t.Translate(ref s), -#if CLR2COMPATIBILITY collectionFactory: count => new HashSet(StringComparer.OrdinalIgnoreCase)); -#else - collectionFactory: count => new HashSet(count, StringComparer.OrdinalIgnoreCase)); -#endif translator.Translate(collection: ref _warningsAsMessages, objectTranslator: (ITranslator t, ref string s) => t.Translate(ref s), -#if CLR2COMPATIBILITY collectionFactory: count => new HashSet(StringComparer.OrdinalIgnoreCase)); -#else - collectionFactory: count => new HashSet(count, StringComparer.OrdinalIgnoreCase)); -#endif } /// diff --git a/src/MSBuildTaskHost/XMakeAttributes.cs b/src/MSBuildTaskHost/XMakeAttributes.cs index 213b5889c81..99a3e5c36d4 100644 --- a/src/MSBuildTaskHost/XMakeAttributes.cs +++ b/src/MSBuildTaskHost/XMakeAttributes.cs @@ -3,9 +3,6 @@ using System; using System.Collections.Generic; -#if !CLR2COMPATIBILITY -using System.Runtime.InteropServices; -#endif #nullable disable @@ -420,29 +417,7 @@ internal static bool TryMergeArchitectureValues(string architectureA, string arc /// 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; } From 629bbbb9bc9f185f4373cd369a912af6a62d1ac8 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 11:19:36 -0800 Subject: [PATCH 026/136] MSBuildTaskHost: Remove TASKHOST disabled code blocks MSBuildTaskHost is always compiled with TASKHOST. So, code blocks disabled when compiled with TASKHOST can be removed. --- src/MSBuildTaskHost/BinaryReaderExtensions.cs | 44 ----- src/MSBuildTaskHost/BinaryWriterExtensions.cs | 43 ----- src/MSBuildTaskHost/ExceptionHandling.cs | 3 - .../Framework/FileUtilities.cs | 58 +------ .../Framework/IConstrainedEqualityComparer.cs | 4 - .../ImmutableDictionaryExtensions.cs | 28 ---- src/MSBuildTaskHost/LogMessagePacketBase.cs | 156 ------------------ src/MSBuildTaskHost/TaskParameter.cs | 22 --- src/MSBuildTaskHost/TranslatorHelpers.cs | 85 ---------- 9 files changed, 1 insertion(+), 442 deletions(-) diff --git a/src/MSBuildTaskHost/BinaryReaderExtensions.cs b/src/MSBuildTaskHost/BinaryReaderExtensions.cs index 9078401ba2f..26ea90fad0f 100644 --- a/src/MSBuildTaskHost/BinaryReaderExtensions.cs +++ b/src/MSBuildTaskHost/BinaryReaderExtensions.cs @@ -11,25 +11,16 @@ namespace Microsoft.Build.Shared { internal static class BinaryReaderExtensions { -#if !TASKHOST - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif public static string? ReadOptionalString(this BinaryReader reader) { return reader.ReadByte() == 0 ? null : reader.ReadString(); } -#if !TASKHOST - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif public static int? ReadOptionalInt32(this BinaryReader reader) { return reader.ReadByte() == 0 ? null : reader.ReadInt32(); } -#if !TASKHOST - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif public static int Read7BitEncodedInt(this BinaryReader reader) { // Read out an Int32 7 bits at a time. The high bit @@ -53,10 +44,6 @@ public static int Read7BitEncodedInt(this BinaryReader reader) } while ((b & 0x80) != 0); return count; } - -#if !TASKHOST - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif public static DateTime ReadTimestamp(this BinaryReader reader) { long timestampTicks = reader.ReadInt64(); @@ -65,37 +52,6 @@ public static DateTime ReadTimestamp(this BinaryReader reader) return timestamp; } -#if !TASKHOST - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static BuildEventContext? ReadOptionalBuildEventContext(this BinaryReader reader) - { - if (reader.ReadByte() == 0) - { - return null; - } - - return reader.ReadBuildEventContext(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static BuildEventContext ReadBuildEventContext(this BinaryReader reader) - { - int nodeId = reader.ReadInt32(); - int projectContextId = reader.ReadInt32(); - int targetId = reader.ReadInt32(); - int taskId = reader.ReadInt32(); - int submissionId = reader.ReadInt32(); - int projectInstanceId = reader.ReadInt32(); - int evaluationId = reader.ReadInt32(); - - var buildEventContext = new BuildEventContext(submissionId, nodeId, evaluationId, projectInstanceId, projectContextId, targetId, taskId); - return buildEventContext; - } -#endif - -#if !TASKHOST - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif public static unsafe Guid ReadGuid(this BinaryReader reader) { return new Guid(reader.ReadBytes(sizeof(Guid))); diff --git a/src/MSBuildTaskHost/BinaryWriterExtensions.cs b/src/MSBuildTaskHost/BinaryWriterExtensions.cs index 9cb458f4ec7..2eb5504e221 100644 --- a/src/MSBuildTaskHost/BinaryWriterExtensions.cs +++ b/src/MSBuildTaskHost/BinaryWriterExtensions.cs @@ -11,9 +11,6 @@ namespace Microsoft.Build.Shared { internal static class BinaryWriterExtensions { -#if !TASKHOST - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif public static void WriteOptionalString(this BinaryWriter writer, string? value) { if (value == null) @@ -27,9 +24,6 @@ public static void WriteOptionalString(this BinaryWriter writer, string? value) } } -#if !TASKHOST - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif public static void WriteOptionalInt32(this BinaryWriter writer, int? value) { if (value == null) @@ -43,18 +37,12 @@ public static void WriteOptionalInt32(this BinaryWriter writer, int? value) } } -#if !TASKHOST - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif public static void WriteTimestamp(this BinaryWriter writer, DateTime timestamp) { writer.Write(timestamp.Ticks); writer.Write((Int32)timestamp.Kind); } -#if !TASKHOST - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif public static void Write7BitEncodedInt(this BinaryWriter writer, int value) { // Write out an int 7 bits at a time. The high bit of the byte, @@ -69,37 +57,6 @@ public static void Write7BitEncodedInt(this BinaryWriter writer, int value) writer.Write((byte)v); } -#if !TASKHOST - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteOptionalBuildEventContext(this BinaryWriter writer, BuildEventContext? context) - { - if (context == null) - { - writer.Write((byte)0); - } - else - { - writer.Write((byte)1); - writer.WriteBuildEventContext(context); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteBuildEventContext(this BinaryWriter writer, BuildEventContext context) - { - writer.Write(context.NodeId); - writer.Write(context.ProjectContextId); - writer.Write(context.TargetId); - writer.Write(context.TaskId); - writer.Write(context.SubmissionId); - writer.Write(context.ProjectInstanceId); - writer.Write(context.EvaluationId); - } -#endif - -#if !TASKHOST - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif public static void WriteGuid(this BinaryWriter writer, Guid value) { Guid val = value; diff --git a/src/MSBuildTaskHost/ExceptionHandling.cs b/src/MSBuildTaskHost/ExceptionHandling.cs index e825f1de9f5..7f870f0bdb7 100644 --- a/src/MSBuildTaskHost/ExceptionHandling.cs +++ b/src/MSBuildTaskHost/ExceptionHandling.cs @@ -91,9 +91,6 @@ internal static bool IsCriticalException(Exception e) || e is ThreadAbortException || e is ThreadInterruptedException || e is AccessViolationException -#if !TASKHOST - || e is CriticalTaskException -#endif || e is InternalErrorException) { // Ideally we would include NullReferenceException, because it should only ever be thrown by CLR (use ArgumentNullException for arguments) diff --git a/src/MSBuildTaskHost/Framework/FileUtilities.cs b/src/MSBuildTaskHost/Framework/FileUtilities.cs index 23cc707631b..4b8022e8a37 100644 --- a/src/MSBuildTaskHost/Framework/FileUtilities.cs +++ b/src/MSBuildTaskHost/Framework/FileUtilities.cs @@ -73,61 +73,5 @@ internal static string EnsureNoTrailingSlash(string path) return path; } - -#if !TASKHOST - /// - /// If the given path doesn't have a trailing slash then add one. - /// - /// The absolute path to check. - /// An absolute path with a trailing slash. - internal static AbsolutePath EnsureTrailingSlash(AbsolutePath path) - { - return new AbsolutePath(EnsureTrailingSlash(path.Value), - original: path.OriginalValue, - ignoreRootedCheck: true); - } - - /// - /// Ensures the absolute path does not have a trailing slash. - /// - /// The absolute path to check. - /// An absolute path without a trailing slash. - internal static AbsolutePath EnsureNoTrailingSlash(AbsolutePath path) - { - return new AbsolutePath(EnsureNoTrailingSlash(path.Value), - original: path.OriginalValue, - ignoreRootedCheck: true); - } - - /// - /// Gets the canonicalized full path of the provided path. - /// Resolves relative segments like "." and "..". Fixes directory separators. - /// ASSUMES INPUT IS ALREADY UNESCAPED. - /// - internal static AbsolutePath NormalizePath(AbsolutePath path) - { - return new AbsolutePath(FixFilePath(Path.GetFullPath(path.Value)), - original: path.OriginalValue, - ignoreRootedCheck: true); - } - - /// - /// Resolves relative segments like "." and "..". - /// ASSUMES INPUT IS ALREADY UNESCAPED. - /// - internal static AbsolutePath RemoveRelativeSegments(AbsolutePath path) - { - return new AbsolutePath(Path.GetFullPath(path.Value), - original: path.OriginalValue, - ignoreRootedCheck: true); - } - - internal static AbsolutePath FixFilePath(AbsolutePath path) - { - return new AbsolutePath(FixFilePath(path.Value), - original: path.OriginalValue, - ignoreRootedCheck: true); - } -#endif } -} \ No newline at end of file +} diff --git a/src/MSBuildTaskHost/Framework/IConstrainedEqualityComparer.cs b/src/MSBuildTaskHost/Framework/IConstrainedEqualityComparer.cs index 3ecc49524e9..1ef95844f9a 100644 --- a/src/MSBuildTaskHost/Framework/IConstrainedEqualityComparer.cs +++ b/src/MSBuildTaskHost/Framework/IConstrainedEqualityComparer.cs @@ -11,11 +11,7 @@ namespace Microsoft.Build.Collections /// Defines methods to support the comparison of objects for /// equality over constrained inputs. /// -#if TASKHOST internal interface IConstrainedEqualityComparer : IEqualityComparer -#else - internal interface IConstrainedEqualityComparer : IEqualityComparer -#endif { /// /// Determines whether the specified objects are equal, factoring in the specified bounds when comparing . diff --git a/src/MSBuildTaskHost/Framework/ImmutableDictionaryExtensions.cs b/src/MSBuildTaskHost/Framework/ImmutableDictionaryExtensions.cs index ba7b04d91ae..28949998580 100644 --- a/src/MSBuildTaskHost/Framework/ImmutableDictionaryExtensions.cs +++ b/src/MSBuildTaskHost/Framework/ImmutableDictionaryExtensions.cs @@ -15,33 +15,5 @@ internal static class ImmutableDictionaryExtensions /// public static readonly ImmutableDictionary EmptyMetadata = ImmutableDictionary.Empty.WithComparers(MSBuildNameIgnoreCaseComparer.Default); - -#if !TASKHOST - /// - /// Sets the given items while running a validation function on each key. - /// - /// - /// ProjectItemInstance.TaskItem exposes dictionary values as ProjectMetadataInstance. For perf reasons, - /// we don't want to internally store ProjectMetadataInstance since it prevents us from sharing immutable - /// dictionaries with Utilities.TaskItem, and it results in more than 2x memory allocated per-entry. - /// - public static ImmutableDictionary SetItems( - this ImmutableDictionary dictionary, - IEnumerable> items, - Action verifyThrowKey) - { - ImmutableDictionary.Builder builder = dictionary.ToBuilder(); - - foreach (KeyValuePair item in items) - { - verifyThrowKey(item.Key); - - // Set null as empty string to match behavior with ProjectMetadataInstance. - builder[item.Key] = item.Value ?? string.Empty; - } - - return builder.ToImmutable(); - } -#endif } } diff --git a/src/MSBuildTaskHost/LogMessagePacketBase.cs b/src/MSBuildTaskHost/LogMessagePacketBase.cs index 5ce14f6e420..830ca1b8dae 100644 --- a/src/MSBuildTaskHost/LogMessagePacketBase.cs +++ b/src/MSBuildTaskHost/LogMessagePacketBase.cs @@ -9,11 +9,6 @@ using Microsoft.Build.BackEnd; using Microsoft.Build.Framework; -#if !TASKHOST -using Microsoft.Build.Framework.Telemetry; -using Microsoft.Build.Experimental.BuildCheck; -#endif - #nullable disable namespace Microsoft.Build.Shared @@ -260,13 +255,11 @@ internal class LogMessagePacketBase : INodePacket /// private static readonly int s_defaultPacketVersion = (Environment.Version.Major * 10) + Environment.Version.Minor; -#if TASKHOST /// /// Dictionary of methods used to read BuildEventArgs. /// private static readonly Dictionary s_readMethodCache = new Dictionary(); -#endif /// /// Dictionary of methods used to write BuildEventArgs. /// @@ -442,7 +435,6 @@ internal void ReadFromStream(ITranslator translator) if (eventCanSerializeItself) { -#if TASKHOST MethodInfo methodInfo = null; lock (s_readMethodCache) { @@ -458,10 +450,6 @@ internal void ReadFromStream(ITranslator translator) readerMethod(translator.Reader, packetVersion); -#else - _buildEvent.CreateFromStream(translator.Reader, packetVersion); -#endif - TranslateAdditionalProperties(translator, _eventType, _buildEvent); } else @@ -542,37 +530,6 @@ private BuildEventArgs GetBuildEventArgFromId() LoggingEventType.TaskCommandLineEvent => new TaskCommandLineEventArgs(null, null, MessageImportance.Normal), LoggingEventType.ResponseFileUsedEvent => new ResponseFileUsedEventArgs(null), -#if !TASKHOST // MSBuildTaskHost is targeting Microsoft.Build.Framework.dll 3.5 - LoggingEventType.AssemblyLoadEvent => new AssemblyLoadBuildEventArgs(), - LoggingEventType.TaskParameterEvent => new TaskParameterEventArgs(0, null, null, true, default), - LoggingEventType.ProjectEvaluationStartedEvent => new ProjectEvaluationStartedEventArgs(), - LoggingEventType.ProjectEvaluationFinishedEvent => new ProjectEvaluationFinishedEventArgs(), - LoggingEventType.ProjectImportedEvent => new ProjectImportedEventArgs(), - LoggingEventType.TargetSkipped => new TargetSkippedEventArgs(), - LoggingEventType.Telemetry => new TelemetryEventArgs(), - LoggingEventType.ExtendedCustomEvent => new ExtendedCustomBuildEventArgs(), - LoggingEventType.ExtendedBuildErrorEvent => new ExtendedBuildErrorEventArgs(), - LoggingEventType.ExtendedBuildWarningEvent => new ExtendedBuildWarningEventArgs(), - LoggingEventType.ExtendedBuildMessageEvent => new ExtendedBuildMessageEventArgs(), - LoggingEventType.ExtendedCriticalBuildMessageEvent => new ExtendedCriticalBuildMessageEventArgs(), - LoggingEventType.ExternalProjectStartedEvent => new ExternalProjectStartedEventArgs(null, null, null, null, null), - LoggingEventType.ExternalProjectFinishedEvent => new ExternalProjectFinishedEventArgs(null, null, null, null, false), - LoggingEventType.CriticalBuildMessage => new CriticalBuildMessageEventArgs(null, null, null, -1, -1, -1, -1, null, null, null), - LoggingEventType.MetaprojectGenerated => new MetaprojectGeneratedEventArgs(null, null, null), - LoggingEventType.PropertyInitialValueSet => new PropertyInitialValueSetEventArgs(), - LoggingEventType.PropertyReassignment => new PropertyReassignmentEventArgs(), - LoggingEventType.UninitializedPropertyRead => new UninitializedPropertyReadEventArgs(), - LoggingEventType.GeneratedFileUsedEvent => new GeneratedFileUsedEventArgs(), - LoggingEventType.BuildCheckMessageEvent => new BuildCheckResultMessage(), - LoggingEventType.BuildCheckWarningEvent => new BuildCheckResultWarning(), - LoggingEventType.BuildCheckErrorEvent => new BuildCheckResultError(), - LoggingEventType.BuildCheckAcquisitionEvent => new BuildCheckAcquisitionEventArgs(), - LoggingEventType.BuildCheckTracingEvent => new BuildCheckTracingEventArgs(), - LoggingEventType.EnvironmentVariableReadEvent => new EnvironmentVariableReadEventArgs(), - LoggingEventType.BuildSubmissionStartedEvent => new BuildSubmissionStartedEventArgs(), - LoggingEventType.BuildCanceledEvent => new BuildCanceledEventArgs("Build canceled."), - LoggingEventType.WorkerNodeTelemetryEvent => new WorkerNodeTelemetryEventArgs(), -#endif _ => throw new InternalErrorException("Should not get to the default of GetBuildEventArgFromId ID: " + _eventType) }; } @@ -595,12 +552,6 @@ private LoggingEventType GetLoggingEventId(BuildEventArgs eventArg) { return LoggingEventType.TaskCommandLineEvent; } -#if !TASKHOST - else if (eventType == typeof(TaskParameterEventArgs)) - { - return LoggingEventType.TaskParameterEvent; - } -#endif else if (eventType == typeof(ProjectFinishedEventArgs)) { return LoggingEventType.ProjectFinishedEvent; @@ -617,113 +568,6 @@ private LoggingEventType GetLoggingEventId(BuildEventArgs eventArg) { return LoggingEventType.ExternalProjectFinishedEvent; } - -#if !TASKHOST - else if (eventType == typeof(ProjectEvaluationFinishedEventArgs)) - { - return LoggingEventType.ProjectEvaluationFinishedEvent; - } - else if (eventType == typeof(ProjectEvaluationStartedEventArgs)) - { - return LoggingEventType.ProjectEvaluationStartedEvent; - } - else if (eventType == typeof(ProjectImportedEventArgs)) - { - return LoggingEventType.ProjectImportedEvent; - } - else if (eventType == typeof(TargetSkippedEventArgs)) - { - return LoggingEventType.TargetSkipped; - } - else if (eventType == typeof(TelemetryEventArgs)) - { - return LoggingEventType.Telemetry; - } - else if (eventType == typeof(AssemblyLoadBuildEventArgs)) - { - return LoggingEventType.AssemblyLoadEvent; - } - else if (eventType == typeof(ExtendedCustomBuildEventArgs)) - { - return LoggingEventType.ExtendedCustomEvent; - } - else if (eventType == typeof(ExtendedBuildErrorEventArgs)) - { - return LoggingEventType.ExtendedBuildErrorEvent; - } - else if (eventType == typeof(ExtendedBuildWarningEventArgs)) - { - return LoggingEventType.ExtendedBuildWarningEvent; - } - else if (eventType == typeof(ExtendedBuildMessageEventArgs)) - { - return LoggingEventType.ExtendedBuildMessageEvent; - } - else if (eventType == typeof(CriticalBuildMessageEventArgs)) - { - return LoggingEventType.CriticalBuildMessage; - } - else if (eventType == typeof(ExtendedCriticalBuildMessageEventArgs)) - { - return LoggingEventType.ExtendedCriticalBuildMessageEvent; - } - else if (eventType == typeof(MetaprojectGeneratedEventArgs)) - { - return LoggingEventType.MetaprojectGenerated; - } - else if (eventType == typeof(PropertyInitialValueSetEventArgs)) - { - return LoggingEventType.PropertyInitialValueSet; - } - else if (eventType == typeof(PropertyReassignmentEventArgs)) - { - return LoggingEventType.PropertyReassignment; - } - else if (eventType == typeof(UninitializedPropertyReadEventArgs)) - { - return LoggingEventType.UninitializedPropertyRead; - } - else if (eventType == typeof(GeneratedFileUsedEventArgs)) - { - return LoggingEventType.GeneratedFileUsedEvent; - } - else if (eventType == typeof(BuildCheckResultMessage)) - { - return LoggingEventType.BuildCheckMessageEvent; - } - else if (eventType == typeof(BuildCheckResultWarning)) - { - return LoggingEventType.BuildCheckWarningEvent; - } - else if (eventType == typeof(BuildCheckResultError)) - { - return LoggingEventType.BuildCheckErrorEvent; - } - else if (eventType == typeof(BuildCheckAcquisitionEventArgs)) - { - return LoggingEventType.BuildCheckAcquisitionEvent; - } - else if (eventType == typeof(BuildCheckTracingEventArgs)) - { - return LoggingEventType.BuildCheckTracingEvent; - } - else if (eventType == typeof(EnvironmentVariableReadEventArgs)) - { - return LoggingEventType.EnvironmentVariableReadEvent; - } - else if (eventType == typeof(BuildSubmissionStartedEventArgs)) - { - return LoggingEventType.BuildSubmissionStartedEvent; - } - else if (eventType == typeof(BuildCanceledEventArgs)) - { - return LoggingEventType.BuildCanceledEvent; - } - else if (eventType == typeof(WorkerNodeTelemetryEventArgs)) - { - return LoggingEventType.WorkerNodeTelemetryEvent; - } -#endif else if (eventType == typeof(TargetStartedEventArgs)) { return LoggingEventType.TargetStartedEvent; diff --git a/src/MSBuildTaskHost/TaskParameter.cs b/src/MSBuildTaskHost/TaskParameter.cs index 6c54e0e8f06..ff6fc293323 100644 --- a/src/MSBuildTaskHost/TaskParameter.cs +++ b/src/MSBuildTaskHost/TaskParameter.cs @@ -542,9 +542,6 @@ private class TaskParameterTaskItem : ITaskItem, ITaskItem2, ITranslatable -#if !TASKHOST - , IMetadataContainer -#endif { /// /// The item spec @@ -750,25 +747,6 @@ public void CopyMetadataTo(ITaskItem destinationItem) // between items, and need to know the source item where the metadata came from string originalItemSpec = destinationItem.GetMetadata("OriginalItemSpec"); -#if !TASKHOST - if (_customEscapedMetadata != null && destinationItem is IMetadataContainer destinationItemAsMetadataContainer) - { - // The destination implements IMetadataContainer so we can use the ImportMetadata bulk-set operation. - IEnumerable> metadataToImport = _customEscapedMetadata - .Where(metadatum => string.IsNullOrEmpty(destinationItem.GetMetadata(metadatum.Key))); - -#if FEATURE_APPDOMAIN - if (RemotingServices.IsTransparentProxy(destinationItem)) - { - // Linq is not serializable so materialize the collection before making the call. - metadataToImport = metadataToImport.ToList(); - } -#endif - - destinationItemAsMetadataContainer.ImportMetadata(metadataToImport); - } - else -#endif if (_customEscapedMetadata != null) { foreach (KeyValuePair entry in _customEscapedMetadata) diff --git a/src/MSBuildTaskHost/TranslatorHelpers.cs b/src/MSBuildTaskHost/TranslatorHelpers.cs index 52bf3941657..1892a45b484 100644 --- a/src/MSBuildTaskHost/TranslatorHelpers.cs +++ b/src/MSBuildTaskHost/TranslatorHelpers.cs @@ -2,10 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -#if !TASKHOST -using System.Collections.Frozen; -using System.Collections.Immutable; -#endif using System.Collections.Generic; using System.Configuration.Assemblies; using System.Globalization; @@ -178,87 +174,6 @@ public static void TranslateDictionary( translator.TranslateDictionary(ref dictionary, AdaptFactory(valueFactory), valueFactory, collectionCreator); } -#if !TASKHOST - public static void TranslateDictionary( - this ITranslator translator, - ref FrozenDictionary dictionary, - IEqualityComparer comparer) - { - IDictionary localDict = dictionary; - translator.TranslateDictionary(ref localDict, capacity => new Dictionary(capacity, comparer)); - - if (translator.Mode == TranslationDirection.ReadFromStream) - { - dictionary = localDict?.ToFrozenDictionary(comparer); - } - } - - public static void TranslateDictionary( - this ITranslator translator, - ref IReadOnlyDictionary dictionary, - IEqualityComparer comparer) - { - // Defensive copy since immutable dictionaries are expected to be overwritten. - IReadOnlyDictionary localDict = dictionary; - - if (!translator.TranslateNullable(localDict)) - { - return; - } - - if (translator.Mode == TranslationDirection.WriteToStream) - { - int count = localDict.Count; - translator.Translate(ref count); - - foreach (KeyValuePair kvp in localDict) - { - string key = kvp.Key; - string value = kvp.Value; - - translator.Translate(ref key); - translator.Translate(ref value); - } - } - else - { - int count = default; - translator.Translate(ref count); - - ImmutableDictionary.Builder builder = ImmutableDictionary.Create(comparer).ToBuilder(); - - for (int i = 0; i < count; i++) - { - string key = null; - string value = null; - - translator.Translate(ref key); - translator.Translate(ref value); - - builder[key] = value; - } - - dictionary = builder.ToImmutable(); - } - } - - public static void TranslateDictionary( - this ITranslator translator, - ref ImmutableDictionary dictionary, - IEqualityComparer comparer) - { - // Defensive copy since immutable dictionaries are expected to be overwritten. - IReadOnlyDictionary localDict = dictionary; - - TranslateDictionary(translator, ref localDict, comparer); - - if (translator.Mode == TranslationDirection.ReadFromStream) - { - dictionary = (ImmutableDictionary)localDict; - } - } -#endif - public static void TranslateHashSet( this ITranslator translator, ref HashSet hashSet, From fc3acdecfac44b37926d4b1e7ca8cc0a33d2bf92 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 11:20:26 -0800 Subject: [PATCH 027/136] MSBuildTaskHost: Remove FEATURE_ASSEMBLYLOADCONTEXT MSBuildTaskHost is never compiled with FEATURE_ASSEMBLYLOADCONTEXT. --- src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs b/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs index ef6162fd211..fa1d80776f4 100644 --- a/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs +++ b/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs @@ -5,10 +5,6 @@ using System.IO; using System.Reflection; using System.Diagnostics; - -#if FEATURE_ASSEMBLYLOADCONTEXT -using System.Runtime.Loader; -#endif using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; From c89a8f48e00d739efd05e7a4b3137a530dae6f0c Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 11:21:26 -0800 Subject: [PATCH 028/136] MSBuildTaskHost: Remove FEATURE_PIPEOPTIONS_CURRENTUSERONLY MSBuildTaskHost is never compiled with FEATURE_PIPEOPTIONS_CURRENTUSERONLY. So, code blocks disabled with FEATURE_PIPEOPTIONS_CURRENTUSERONLY can be removed. --- src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs b/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs index cae216b87e8..5bb0bc38d9f 100644 --- a/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs +++ b/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs @@ -220,11 +220,7 @@ internal void InternalConstruct(string pipeName = null, byte parentPacketVersion PipeDirection.InOut, 1, // Only allow one connection at a time. PipeTransmissionMode.Byte, - PipeOptions.Asynchronous | PipeOptions.WriteThrough -#if FEATURE_PIPEOPTIONS_CURRENTUSERONLY - | PipeOptions.CurrentUserOnly -#endif - , + PipeOptions.Asynchronous | PipeOptions.WriteThrough, PipeBufferSize, // Default input buffer PipeBufferSize, // Default output buffer security, From 652e1b0a77c05ded3d65859320af22a5c983a783 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 11:23:04 -0800 Subject: [PATCH 029/136] MSBuildTaskHost: Remove FEATURE_NET35_TASKHOST MSBuildTaskHost is always compiled with FEATURE_NET35_TASKHOST. --- src/Directory.BeforeCommon.targets | 1 - src/MSBuildTaskHost/Framework/ChangeWaves.cs | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets index 569fe9551f9..98bfeb2fba6 100644 --- a/src/Directory.BeforeCommon.targets +++ b/src/Directory.BeforeCommon.targets @@ -74,7 +74,6 @@ $(DefineConstants);FEATURE_APARTMENT_STATE $(DefineConstants);FEATURE_APPDOMAIN - $(DefineConstants);FEATURE_NET35_TASKHOST diff --git a/src/MSBuildTaskHost/Framework/ChangeWaves.cs b/src/MSBuildTaskHost/Framework/ChangeWaves.cs index d40cd86c8f3..a01ff47556a 100644 --- a/src/MSBuildTaskHost/Framework/ChangeWaves.cs +++ b/src/MSBuildTaskHost/Framework/ChangeWaves.cs @@ -196,7 +196,6 @@ internal static void ResetStateForTests() private static bool TryParseVersion(string stringVersion, out Version version) { -#if FEATURE_NET35_TASKHOST try { version = new Version(stringVersion); @@ -207,9 +206,6 @@ private static bool TryParseVersion(string stringVersion, out Version version) version = null; return false; } -#else - return Version.TryParse(stringVersion, out version); -#endif } } } From f742101f3743f0f23f2db829520396568a6fda0a Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 11:24:50 -0800 Subject: [PATCH 030/136] MSBuildTaskHost: Remove NO_FRAMEWORK_IVT MSBuildTaskHost is always compiled with NO_FRAMEWORK_IVT. So, code blocks disabled under NO_FRAMEWORK_IVT can be removed. --- src/MSBuildTaskHost/BuildEnvironmentHelper.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs index 6610257dcb6..f5cebf37590 100644 --- a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs +++ b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs @@ -466,11 +466,6 @@ public BuildEnvironment(BuildEnvironmentMode mode, string currentMSBuildExePath, CurrentMSBuildExePath = currentMSBuildExePath; VisualStudioInstallRootDirectory = visualStudioPath; -#if !NO_FRAMEWORK_IVT - Framework.BuildEnvironmentState.s_runningTests = runningTests; - Framework.BuildEnvironmentState.s_runningInVisualStudio = runningInVisualStudio; -#endif - if (!string.IsNullOrEmpty(currentMSBuildExePath)) { currentMSBuildExeFile = new FileInfo(currentMSBuildExePath); From c13ff0fda12b7235f2b9e34aacd7b055cc1d1a20 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 11:36:57 -0800 Subject: [PATCH 031/136] MSBuildTaskHost: Remove FEATURE_APPDOMAIN MSBuildTaskHost is always compiled with FEATURE_APPDOMAIN. So, code blocks disabled under FEATURE_APPDOMAIN can be removed. --- src/Directory.BeforeCommon.targets | 1 - .../OutOfProcTaskAppDomainWrapperBase.cs | 37 ++---------------- .../MSBuild/OutOfProcTaskHostNode.cs | 12 +----- .../TaskEngineAssemblyResolver.cs | 36 +----------------- src/MSBuildTaskHost/TaskHostConfiguration.cs | 38 +------------------ src/MSBuildTaskHost/TaskLoader.cs | 20 +--------- src/MSBuildTaskHost/TaskParameter.cs | 24 +----------- 7 files changed, 12 insertions(+), 156 deletions(-) diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets index 98bfeb2fba6..556921d7930 100644 --- a/src/Directory.BeforeCommon.targets +++ b/src/Directory.BeforeCommon.targets @@ -73,7 +73,6 @@ $(DefineConstants);FEATURE_APARTMENT_STATE - $(DefineConstants);FEATURE_APPDOMAIN diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs index d2933c31cf3..4a47455560e 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -#if FEATURE_APPDOMAIN using System.Threading; -#endif using System.Reflection; using Microsoft.Build.BackEnd; @@ -20,18 +18,13 @@ namespace Microsoft.Build.CommandLine /// Class for executing a task in an AppDomain /// [Serializable] - internal class OutOfProcTaskAppDomainWrapperBase -#if FEATURE_APPDOMAIN - : MarshalByRefObject -#endif + internal class OutOfProcTaskAppDomainWrapperBase : MarshalByRefObject { /// /// This is the actual user task whose instance we will create and invoke Execute /// private ITask wrappedTask; - -#if FEATURE_APPDOMAIN /// /// This is an appDomain instance if any is created for running this task /// @@ -42,7 +35,6 @@ internal class OutOfProcTaskAppDomainWrapperBase /// [NonSerialized] private AppDomain _taskAppDomain; -#endif /// /// Need to keep the build engine around in order to log from the task loader. @@ -100,17 +92,13 @@ internal OutOfProcTaskHostTaskResult ExecuteTask( int taskColumn, string targetName, string projectFile, -#if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, -#endif IDictionary taskParams) { buildEngine = oopTaskHostNode; this.taskName = taskName; -#if FEATURE_APPDOMAIN _taskAppDomain = null; -#endif wrappedTask = null; LoadedType taskType = null; @@ -151,9 +139,7 @@ internal OutOfProcTaskHostTaskResult ExecuteTask( taskColumn, targetName, projectFile, -#if FEATURE_APPDOMAIN appDomainSetup, -#endif taskParams); #else return new OutOfProcTaskHostTaskResult( @@ -175,9 +161,7 @@ internal OutOfProcTaskHostTaskResult ExecuteTask( taskColumn, targetName, projectFile, -#if FEATURE_APPDOMAIN appDomainSetup, -#endif taskParams); } @@ -191,14 +175,12 @@ internal OutOfProcTaskHostTaskResult ExecuteTask( /// internal void CleanupTask() { -#if FEATURE_APPDOMAIN if (_taskAppDomain != null) { AppDomain.Unload(_taskAppDomain); } TaskLoader.RemoveAssemblyResolver(); -#endif wrappedTask = null; } @@ -220,9 +202,7 @@ private OutOfProcTaskHostTaskResult InstantiateAndExecuteTaskInSTAThread( int taskColumn, string targetName, string projectFile, -#if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, -#endif IDictionary taskParams) { ManualResetEvent taskRunnerFinished = new ManualResetEvent(false); @@ -245,9 +225,7 @@ private OutOfProcTaskHostTaskResult InstantiateAndExecuteTaskInSTAThread( taskColumn, targetName, projectFile, -#if FEATURE_APPDOMAIN appDomainSetup, -#endif taskParams); } catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) @@ -299,14 +277,10 @@ private OutOfProcTaskHostTaskResult InstantiateAndExecuteTask( int taskColumn, string targetName, string projectFile, -#if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, -#endif IDictionary taskParams) { -#if FEATURE_APPDOMAIN _taskAppDomain = null; -#endif wrappedTask = null; try @@ -319,16 +293,11 @@ private OutOfProcTaskHostTaskResult InstantiateAndExecuteTask( taskLine, taskColumn, new TaskLoader.LogError(LogErrorDelegate), -#if FEATURE_APPDOMAIN appDomainSetup, // custom app domain assembly loading won't be available for task host null, -#endif - true /* always out of proc */ -#if FEATURE_APPDOMAIN - , out _taskAppDomain -#endif - ); + true, /* always out of proc */ + out _taskAppDomain); #pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter wrappedTask.BuildEngine = oopTaskHostNode; diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs index 32d90754047..495e5a7a011 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs @@ -13,9 +13,7 @@ using Microsoft.Build.Framework; using Microsoft.Build.Internal; using Microsoft.Build.Shared; -#if FEATURE_APPDOMAIN using System.Runtime.Remoting; -#endif #nullable disable @@ -24,11 +22,7 @@ namespace Microsoft.Build.CommandLine /// /// This class represents an implementation of INode for out-of-proc node for hosting tasks. /// - internal class OutOfProcTaskHostNode : -#if FEATURE_APPDOMAIN - MarshalByRefObject, -#endif - INodePacketFactory, INodePacketHandler, IBuildEngine3 + internal class OutOfProcTaskHostNode : MarshalByRefObject, INodePacketFactory, INodePacketHandler, IBuildEngine3 { /// /// Keeps a record of all environment variables that, on startup of the task host, have a different @@ -776,9 +770,7 @@ private void RunTask(object state) taskConfiguration.ColumnNumberOfTask, taskConfiguration.TargetName, taskConfiguration.ProjectFile, -#if FEATURE_APPDOMAIN taskConfiguration.AppDomainSetup, -#endif taskParams); } catch (ThreadAbortException) @@ -806,13 +798,11 @@ private void RunTask(object state) _taskCompletePacket = new TaskHostTaskComplete(taskResult, currentEnvironment); } -#if FEATURE_APPDOMAIN foreach (TaskParameter param in taskParams.Values) { // Tell remoting to forget connections to the parameter RemotingServices.Disconnect(param); } -#endif // Restore the original clean environment CommunicationsUtilities.SetEnvironment(_savedEnvironment); diff --git a/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs b/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs index fa1d80776f4..22d5244dd7e 100644 --- a/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs +++ b/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs @@ -15,10 +15,7 @@ namespace Microsoft.Build.BackEnd.Logging /// /// This is a helper class to install an AssemblyResolver event handler in whatever AppDomain this class is created in. /// - internal class TaskEngineAssemblyResolver -#if FEATURE_APPDOMAIN - : MarshalByRefObject -#endif + internal class TaskEngineAssemblyResolver : MarshalByRefObject { /// /// This public default constructor is needed so that instances of this class can be created by NDP. @@ -46,21 +43,13 @@ internal void Initialize(string taskAssemblyFileToResolve) /// internal void InstallHandler() { -#if FEATURE_APPDOMAIN Debug.Assert(_eventHandler == null, "The TaskEngineAssemblyResolver.InstallHandler method should only be called once!"); _eventHandler = new ResolveEventHandler(ResolveAssembly); AppDomain.CurrentDomain.AssemblyResolve += _eventHandler; -#else - _eventHandler = new Func(ResolveAssembly); - - AssemblyLoadContext.Default.Resolving += _eventHandler; -#endif } - - /// /// Removes the event handler. /// @@ -68,11 +57,7 @@ internal void RemoveHandler() { if (_eventHandler != null) { -#if FEATURE_APPDOMAIN AppDomain.CurrentDomain.AssemblyResolve -= _eventHandler; -#else - AssemblyLoadContext.Default.Resolving -= _eventHandler; -#endif _eventHandler = null; } else @@ -81,8 +66,6 @@ internal void RemoveHandler() } } - -#if FEATURE_APPDOMAIN /// /// This is an assembly resolution handler necessary for fixing up types instantiated in different /// AppDomains and loaded with a Assembly.LoadFrom equivalent call. See comments in TaskEngine.ExecuteTask @@ -92,9 +75,6 @@ internal void RemoveHandler() /// /// internal Assembly ResolveAssembly(object sender, ResolveEventArgs args) -#else - private Assembly ResolveAssembly(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName) -#endif { // Is this our task assembly? if (_taskAssemblyFile != null) @@ -103,7 +83,6 @@ private Assembly ResolveAssembly(AssemblyLoadContext assemblyLoadContext, Assemb { try { -#if FEATURE_APPDOMAIN AssemblyNameExtension taskAssemblyName = new AssemblyNameExtension(AssemblyName.GetAssemblyName(_taskAssemblyFile)); AssemblyNameExtension argAssemblyName = new AssemblyNameExtension(args.Name); @@ -111,14 +90,6 @@ private Assembly ResolveAssembly(AssemblyLoadContext assemblyLoadContext, Assemb { return Assembly.LoadFrom(_taskAssemblyFile); } -#else // !FEATURE_APPDOMAIN - AssemblyNameExtension taskAssemblyName = new AssemblyNameExtension(AssemblyLoadContext.GetAssemblyName(_taskAssemblyFile)); - AssemblyNameExtension argAssemblyName = new AssemblyNameExtension(assemblyName); - if (taskAssemblyName.Equals(argAssemblyName)) - { - return AssemblyLoadContext.Default.LoadFromAssemblyPath(_taskAssemblyFile); - } -#endif } // any problems with the task assembly? return null. catch (FileNotFoundException) @@ -136,7 +107,6 @@ private Assembly ResolveAssembly(AssemblyLoadContext assemblyLoadContext, Assemb return null; } -#if FEATURE_APPDOMAIN /// /// Overridden to give this class infinite lease time. Otherwise we end up with a limited /// lease (5 minutes I think) and instances can expire if they take long time processing. @@ -151,9 +121,7 @@ public override object InitializeLifetimeService() // we have to store the event handler instance in case we have to remove it private ResolveEventHandler _eventHandler = null; -#else - private Func _eventHandler = null; -#endif + // path to the task assembly, but only if it's loaded using LoadFrom. If it's loaded with Load, this is null. private string _taskAssemblyFile = null; } diff --git a/src/MSBuildTaskHost/TaskHostConfiguration.cs b/src/MSBuildTaskHost/TaskHostConfiguration.cs index df17bd8f9ef..f473dd7031b 100644 --- a/src/MSBuildTaskHost/TaskHostConfiguration.cs +++ b/src/MSBuildTaskHost/TaskHostConfiguration.cs @@ -43,12 +43,10 @@ internal class TaskHostConfiguration : INodePacket /// private CultureInfo _uiCulture = CultureInfo.CurrentUICulture; -#if FEATURE_APPDOMAIN /// /// The AppDomainSetup that we may want to use on AppDomainIsolated tasks. /// private AppDomainSetup _appDomainSetup; -#endif /// /// Line number where the instance of this task is defined. @@ -107,7 +105,6 @@ internal class TaskHostConfiguration : INodePacket private ICollection _warningsAsMessages; -#if FEATURE_APPDOMAIN /// /// Initializes a new instance of the class. /// @@ -132,40 +129,13 @@ internal class TaskHostConfiguration : INodePacket /// A collection of warning codes to be treated as errors. /// A collection of warning codes not to be treated as errors. /// A collection of warning codes to be treated as messages. -#else - /// - /// Initializes a new instance of the class. - /// - /// The ID of the node being configured. - /// The startup directory for the task being executed. - /// The set of environment variables to apply to the task execution process. - /// The culture of the thread that will execute the task. - /// The UI culture of the thread that will execute the task. - /// The host services to be used by the task host. - /// The line number of the location from which this task was invoked. - /// The column number of the location from which this task was invoked. - /// The project file from which this task was invoked. - /// A flag to indicate whether to continue with the build after the task fails. - /// The name of the task. - /// The location of the assembly from which the task is to be loaded. - /// The name of the target that is requesting the task execution. - /// The project path that invokes the task. - /// A flag to indicate whether task inputs are logged. - /// The parameters to apply to the task. - /// The global properties for the current project. - /// A collection of warning codes to be treated as errors. - /// A collection of warning codes not to be treated as errors. - /// A collection of warning codes to be treated as messages. -#endif public TaskHostConfiguration( int nodeId, string startupDirectory, IDictionary buildProcessEnvironment, CultureInfo culture, CultureInfo uiCulture, -#if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, -#endif int lineNumberOfTask, int columnNumberOfTask, string projectFileOfTask, @@ -199,9 +169,7 @@ public TaskHostConfiguration( _culture = culture; _uiCulture = uiCulture; -#if FEATURE_APPDOMAIN _appDomainSetup = appDomainSetup; -#endif _lineNumberOfTask = lineNumberOfTask; _columnNumberOfTask = columnNumberOfTask; _projectFileOfTask = projectFileOfTask; @@ -285,7 +253,6 @@ public CultureInfo UICulture { return _uiCulture; } } -#if FEATURE_APPDOMAIN /// /// The AppDomain configuration bytes that we may want to use to initialize /// AppDomainIsolated tasks. @@ -296,7 +263,6 @@ public AppDomainSetup AppDomainSetup get { return _appDomainSetup; } } -#endif /// /// Line number where the instance of this task is defined. @@ -456,7 +422,7 @@ public void Translate(ITranslator translator) translator.TranslateDictionary(ref _buildProcessEnvironment, StringComparer.OrdinalIgnoreCase); translator.TranslateCulture(ref _culture); translator.TranslateCulture(ref _uiCulture); -#if FEATURE_APPDOMAIN + // The packet version is used to determine if the AppDomain configuration should be serialized. // If the packet version is bigger then 0, it means the task host will running under .NET. // Although MSBuild.exe runs under .NET Framework and has AppDomain support, @@ -479,7 +445,7 @@ public void Translate(ITranslator translator) _appDomainSetup.SetConfigurationBytes(appDomainConfigBytes); } } -#endif + translator.Translate(ref _lineNumberOfTask); translator.Translate(ref _columnNumberOfTask); translator.Translate(ref _projectFileOfTask); diff --git a/src/MSBuildTaskHost/TaskLoader.cs b/src/MSBuildTaskHost/TaskLoader.cs index 602a36871ed..6aa105b557d 100644 --- a/src/MSBuildTaskHost/TaskLoader.cs +++ b/src/MSBuildTaskHost/TaskLoader.cs @@ -12,13 +12,11 @@ namespace Microsoft.Build.Shared /// internal static class TaskLoader { -#if FEATURE_APPDOMAIN /// /// For saving the assembly that was loaded by the TypeLoader /// We only use this when the assembly failed to load properly into the appdomain /// private static LoadedType? s_resolverLoadedType; -#endif /// /// Delegate for logging task loading errors. @@ -47,27 +45,20 @@ internal static bool IsTaskClass(Type type, object unused) int taskLine, int taskColumn, LogError logError, -#if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, Action appDomainCreated, -#endif - bool isOutOfProc -#if FEATURE_APPDOMAIN - , out AppDomain? taskAppDomain -#endif + bool isOutOfProc, + out AppDomain? taskAppDomain ) #pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter { -#if FEATURE_APPDOMAIN bool separateAppDomain = loadedType.HasLoadInSeparateAppDomainAttribute; s_resolverLoadedType = null; taskAppDomain = null; ITask? taskInstanceInOtherAppDomain = null; -#endif try { -#if FEATURE_APPDOMAIN if (separateAppDomain) { if (!loadedType.IsMarshalByRef) @@ -120,14 +111,12 @@ bool isOutOfProc } } else -#endif { // perf improvement for the same appdomain case - we already have the type object // and don't want to go through reflection to recreate it from the name. return (ITask?)Activator.CreateInstance(loadedType.Type); } -#if FEATURE_APPDOMAIN if (loadedType.Assembly.AssemblyFile != null) { taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceFromAndUnwrap(loadedType.Assembly.AssemblyFile, loadedType.Type.FullName); @@ -157,22 +146,18 @@ bool isOutOfProc } return taskInstanceInOtherAppDomain; -#endif } finally { -#if FEATURE_APPDOMAIN // Don't leave appdomains open if (taskAppDomain != null && taskInstanceInOtherAppDomain == null) { AppDomain.Unload(taskAppDomain); RemoveAssemblyResolver(); } -#endif } } -#if FEATURE_APPDOMAIN /// /// This is a resolver to help created AppDomains when they are unable to load an assembly into their domain we will help /// them succeed by providing the already loaded one in the currentdomain so that they can derive AssemblyName info from it @@ -202,6 +187,5 @@ internal static void RemoveAssemblyResolver() s_resolverLoadedType = null; } } -#endif } } diff --git a/src/MSBuildTaskHost/TaskParameter.cs b/src/MSBuildTaskHost/TaskParameter.cs index ff6fc293323..5bf1375f9d9 100644 --- a/src/MSBuildTaskHost/TaskParameter.cs +++ b/src/MSBuildTaskHost/TaskParameter.cs @@ -9,10 +9,8 @@ using System.Reflection; using Microsoft.Build.Collections; -#if FEATURE_APPDOMAIN using System.Runtime.Remoting; using System.Security; -#endif using Microsoft.Build.Framework; using Microsoft.Build.Shared; @@ -71,11 +69,7 @@ internal enum TaskParameterType /// Wrapper for task parameters, to allow proper serialization even /// in cases where the parameter is not .NET serializable. /// - internal class TaskParameter : -#if FEATURE_APPDOMAIN - MarshalByRefObject, -#endif - ITranslatable + internal class TaskParameter : MarshalByRefObject, ITranslatable { /// /// The TaskParameterType of the wrapped parameter. @@ -262,7 +256,6 @@ public void Translate(ITranslator translator) } } -#if FEATURE_APPDOMAIN /// /// Overridden to give this class infinite lease time. Otherwise we end up with a limited /// lease (5 minutes I think) and instances can expire if they take long time processing. @@ -273,7 +266,6 @@ public override object InitializeLifetimeService() // null means infinite lease time return null; } -#endif /// /// Factory for deserialization. @@ -535,13 +527,7 @@ private void TranslateValueTypeArray(ITranslator translator) /// /// Super simple ITaskItem derivative that we can use as a container for read items. /// - private class TaskParameterTaskItem : -#if FEATURE_APPDOMAIN - MarshalByRefObject, -#endif - ITaskItem, - ITaskItem2, - ITranslatable + private class TaskParameterTaskItem : MarshalByRefObject, ITaskItem, ITaskItem2, ITranslatable { /// /// The item spec @@ -790,7 +776,6 @@ public IDictionary CloneCustomMetadata() return (IDictionary)clonedMetadata; } -#if FEATURE_APPDOMAIN /// /// Overridden to give this class infinite lease time. Otherwise we end up with a limited /// lease (5 minutes I think) and instances can expire if they take long time processing. @@ -801,7 +786,6 @@ public override object InitializeLifetimeService() // null means infinite lease time return null; } -#endif /// /// Returns the escaped value of the requested metadata name. @@ -845,17 +829,14 @@ IDictionary ITaskItem2.CloneCustomMetadataEscaped() public IEnumerable> EnumerateMetadata() { -#if FEATURE_APPDOMAIN if (!AppDomain.CurrentDomain.IsDefaultAppDomain()) { return EnumerateMetadataEager(); } -#endif return EnumerateMetadataLazy(); } -#if FEATURE_APPDOMAIN private IEnumerable> EnumerateMetadataEager() { if (_customEscapedMetadata == null || _customEscapedMetadata.Count == 0) @@ -873,7 +854,6 @@ private IEnumerable> EnumerateMetadataEager() return result; } -#endif private IEnumerable> EnumerateMetadataLazy() { From 73e68d4bcea1a13e436f45bd38a9aae73be3e9df Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 11:47:08 -0800 Subject: [PATCH 032/136] MSBuildTaskHost: Remove OS runtime checks MSBuildTaskHost can only ever run on Windows. So, runtime checks for the OS platform can be completely removed. --- src/MSBuildTaskHost/BuildEnvironmentHelper.cs | 11 +- src/MSBuildTaskHost/FileUtilities.cs | 4 +- .../Framework/NativeMethods.cs | 206 +++++------------- src/MSBuildTaskHost/Modifiers.cs | 35 +-- src/MSBuildTaskHost/NamedPipeUtil.cs | 11 +- 5 files changed, 76 insertions(+), 191 deletions(-) diff --git a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs index f5cebf37590..f2aeaad9a5c 100644 --- a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs +++ b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs @@ -128,11 +128,6 @@ private static BuildEnvironment TryFromEnvironmentVariable() private static BuildEnvironment TryFromVisualStudioProcess() { - if (!NativeMethodsShared.IsWindows) - { - return null; - } - var vsProcess = s_getProcessFromRunningProcess(); if (!IsProcessInList(vsProcess, s_visualStudioProcess)) { @@ -160,8 +155,7 @@ private static BuildEnvironment TryFromMSBuildProcess() } // First check if we're in a VS installation - if (NativeMethodsShared.IsWindows && - Regex.IsMatch(msBuildExe, $@".*\\MSBuild\\{CurrentToolsVersion}\\Bin\\.*MSBuild(?:TaskHost)?\.exe", RegexOptions.IgnoreCase)) + if (Regex.IsMatch(msBuildExe, $@".*\\MSBuild\\{CurrentToolsVersion}\\Bin\\.*MSBuild(?:TaskHost)?\.exe", RegexOptions.IgnoreCase)) { return new BuildEnvironment( BuildEnvironmentMode.VisualStudio, @@ -233,8 +227,7 @@ private static BuildEnvironment TryFromMSBuildExeUnderVisualStudio(string msbuil ? $@".*\\MSBuild\\({CurrentToolsVersion}|\d+\.0)\\Bin\\.*" : $@".*\\MSBuild\\{CurrentToolsVersion}\\Bin\\.*"; - if (NativeMethodsShared.IsWindows && - Regex.IsMatch(msbuildExe, msBuildPathPattern, RegexOptions.IgnoreCase)) + if (Regex.IsMatch(msbuildExe, msBuildPathPattern, RegexOptions.IgnoreCase)) { string visualStudioRoot = GetVsRootFromMSBuildAssembly(msbuildExe); return new BuildEnvironment( diff --git a/src/MSBuildTaskHost/FileUtilities.cs b/src/MSBuildTaskHost/FileUtilities.cs index b683368acb2..62c4f7a3377 100644 --- a/src/MSBuildTaskHost/FileUtilities.cs +++ b/src/MSBuildTaskHost/FileUtilities.cs @@ -419,7 +419,7 @@ private static string GetFullPath(string path) private static bool IsUNCPath(string path) { - if (!NativeMethodsShared.IsWindows || !path.StartsWith(@"\\", StringComparison.Ordinal)) + if (!path.StartsWith(@"\\", StringComparison.Ordinal)) { return false; } @@ -549,7 +549,7 @@ internal static string GetFullPath(string fileSpec, string currentDirectory, boo fullPath = EscapingUtilities.Escape(fullPath); } - if (NativeMethodsShared.IsWindows && !FrameworkFileUtilities.EndsWithSlash(fullPath)) + if (!FrameworkFileUtilities.EndsWithSlash(fullPath)) { if (FileUtilitiesRegex.IsDrivePattern(fileSpec) || FileUtilitiesRegex.IsUncPattern(fullPath)) diff --git a/src/MSBuildTaskHost/Framework/NativeMethods.cs b/src/MSBuildTaskHost/Framework/NativeMethods.cs index 8129a9c19a9..a649d4d83a4 100644 --- a/src/MSBuildTaskHost/Framework/NativeMethods.cs +++ b/src/MSBuildTaskHost/Framework/NativeMethods.cs @@ -438,37 +438,27 @@ public SystemInformationData() ProcessorArchitectureType = ProcessorArchitectures.Unknown; ProcessorArchitectureTypeNative = ProcessorArchitectures.Unknown; - if (IsWindows) - { - var systemInfo = new SYSTEM_INFO(); + var systemInfo = new SYSTEM_INFO(); - GetSystemInfo(ref systemInfo); - ProcessorArchitectureType = ConvertSystemArchitecture(systemInfo.wProcessorArchitecture); + GetSystemInfo(ref systemInfo); + ProcessorArchitectureType = ConvertSystemArchitecture(systemInfo.wProcessorArchitecture); - GetNativeSystemInfo(ref systemInfo); - ProcessorArchitectureTypeNative = ConvertSystemArchitecture(systemInfo.wProcessorArchitecture); - } - else - { - ProcessorArchitectures processorArchitecture = ProcessorArchitectures.Unknown; - ProcessorArchitectureTypeNative = ProcessorArchitectureType = processorArchitecture; - } + GetNativeSystemInfo(ref systemInfo); + ProcessorArchitectureTypeNative = ConvertSystemArchitecture(systemInfo.wProcessorArchitecture); } } public static int GetLogicalCoreCount() { int numberOfCpus = Environment.ProcessorCount; + // .NET on Windows returns a core count limited to the current NUMA node // https://github.com/dotnet/runtime/issues/29686 // so always double-check it. - if (IsWindows) + var result = GetLogicalCoreCountOnWindows(); + if (result != -1) { - var result = GetLogicalCoreCountOnWindows(); - if (result != -1) - { - numberOfCpus = result; - } + numberOfCpus = result; } return numberOfCpus; @@ -588,11 +578,6 @@ internal enum LongPathsStatus internal static LongPathsStatus IsLongPathsEnabled() { - if (!IsWindows) - { - return LongPathsStatus.NotApplicable; - } - try { return IsLongPathsEnabledRegistry(); @@ -645,19 +630,14 @@ internal static SAC_State GetSACState() internal static SAC_State GetSACStateInternal() { - if (IsWindows) + try { - try - { - return GetSACStateRegistry(); - } - catch - { - return SAC_State.Missing; - } + return GetSACStateRegistry(); + } + catch + { + return SAC_State.Missing; } - - return SAC_State.NotApplicable; } [SupportedOSPlatform("windows")] @@ -890,39 +870,25 @@ internal static bool GetLastWriteDirectoryUtcTime(string fullPath, out DateTime { // This code was copied from the reference manager, if there is a bug fix in that code, see if the same fix should also be made // there - if (IsWindows) - { - fileModifiedTimeUtc = DateTime.MinValue; + fileModifiedTimeUtc = DateTime.MinValue; - WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); - bool success = GetFileAttributesEx(fullPath, 0, ref data); - if (success) + WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); + bool success = GetFileAttributesEx(fullPath, 0, ref data); + if (success) + { + if ((data.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { - if ((data.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) - { - long dt = ((long)(data.ftLastWriteTimeHigh) << 32) | ((long)data.ftLastWriteTimeLow); - fileModifiedTimeUtc = DateTime.FromFileTimeUtc(dt); - } - else - { - // Path does not point to a directory - success = false; - } + long dt = ((long)(data.ftLastWriteTimeHigh) << 32) | ((long)data.ftLastWriteTimeLow); + fileModifiedTimeUtc = DateTime.FromFileTimeUtc(dt); + } + else + { + // Path does not point to a directory + success = false; } - - return success; } - if (Directory.Exists(fullPath)) - { - fileModifiedTimeUtc = Directory.GetLastWriteTimeUtc(fullPath); - return true; - } - else - { - fileModifiedTimeUtc = DateTime.MinValue; - return false; - } + return success; } /// @@ -930,11 +896,6 @@ internal static bool GetLastWriteDirectoryUtcTime(string fullPath, out DateTime /// internal static string GetShortFilePath(string path) { - if (!IsWindows) - { - return path; - } - if (path != null) { int length = GetShortPathName(path, null, 0); @@ -970,11 +931,6 @@ internal static string GetShortFilePath(string path) [SupportedOSPlatform("windows")] internal static string GetLongFilePath(string path) { - if (IsUnixLike) - { - return path; - } - if (path != null) { int length = GetLongPathName(path, null, 0); @@ -1007,42 +963,28 @@ internal static string GetLongFilePath(string path) /// internal static MemoryStatus GetMemoryStatus() { - if (IsWindows) + MemoryStatus status = new MemoryStatus(); + bool returnValue = GlobalMemoryStatusEx(status); + if (!returnValue) { - MemoryStatus status = new MemoryStatus(); - bool returnValue = GlobalMemoryStatusEx(status); - if (!returnValue) - { - return null; - } - - return status; + return null; } - return null; + return status; } internal static bool MakeSymbolicLink(string newFileName, string existingFileName, ref string errorMessage) { - bool symbolicLinkCreated; - if (IsWindows) + Version osVersion = Environment.OSVersion.Version; + SymbolicLink flags = SymbolicLink.File; + if (osVersion.Major >= 11 || (osVersion.Major == 10 && osVersion.Build >= 14972)) { - Version osVersion = Environment.OSVersion.Version; - SymbolicLink flags = SymbolicLink.File; - if (osVersion.Major >= 11 || (osVersion.Major == 10 && osVersion.Build >= 14972)) - { - flags |= SymbolicLink.AllowUnprivilegedCreate; - } - - symbolicLinkCreated = CreateSymbolicLink(newFileName, existingFileName, flags); - errorMessage = symbolicLinkCreated ? null : Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()).Message; - } - else - { - symbolicLinkCreated = symlink(existingFileName, newFileName) == 0; - errorMessage = symbolicLinkCreated ? null : Marshal.GetLastWin32Error().ToString(); + flags |= SymbolicLink.AllowUnprivilegedCreate; } + bool symbolicLinkCreated = CreateSymbolicLink(newFileName, existingFileName, flags); + errorMessage = symbolicLinkCreated ? null : Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()).Message; + return symbolicLinkCreated; } @@ -1064,36 +1006,27 @@ DateTime LastWriteFileUtcTime(string path) { DateTime fileModifiedTime = DateTime.MinValue; - if (IsWindows) + if (Traits.Instance.EscapeHatches.AlwaysUseContentTimestamp) { - if (Traits.Instance.EscapeHatches.AlwaysUseContentTimestamp) - { - return GetContentLastWriteFileUtcTime(path); - } + return GetContentLastWriteFileUtcTime(path); + } - WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); - bool success = NativeMethods.GetFileAttributesEx(path, 0, ref data); + WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); + bool success = NativeMethods.GetFileAttributesEx(path, 0, ref data); - if (success && (data.fileAttributes & NativeMethods.FILE_ATTRIBUTE_DIRECTORY) == 0) - { - long dt = ((long)(data.ftLastWriteTimeHigh) << 32) | ((long)data.ftLastWriteTimeLow); - fileModifiedTime = DateTime.FromFileTimeUtc(dt); + if (success && (data.fileAttributes & NativeMethods.FILE_ATTRIBUTE_DIRECTORY) == 0) + { + long dt = ((long)(data.ftLastWriteTimeHigh) << 32) | ((long)data.ftLastWriteTimeLow); + fileModifiedTime = DateTime.FromFileTimeUtc(dt); - // If file is a symlink _and_ we're not instructed to do the wrong thing, get a more accurate timestamp. - if ((data.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT && !Traits.Instance.EscapeHatches.UseSymlinkTimeInsteadOfTargetTime) - { - fileModifiedTime = GetContentLastWriteFileUtcTime(path); - } + // If file is a symlink _and_ we're not instructed to do the wrong thing, get a more accurate timestamp. + if ((data.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT && !Traits.Instance.EscapeHatches.UseSymlinkTimeInsteadOfTargetTime) + { + fileModifiedTime = GetContentLastWriteFileUtcTime(path); } - - return fileModifiedTime; - } - else - { - return File.Exists(path) - ? File.GetLastWriteTimeUtc(path) - : DateTime.MinValue; } + + return fileModifiedTime; } } @@ -1494,20 +1427,7 @@ internal static void VerifyThrowWin32Result(int result) internal static bool SetCurrentDirectory(string path) { - if (IsWindows) - { - return SetCurrentDirectoryWindows(path); - } - - // Make sure this does not throw - try - { - Directory.SetCurrentDirectory(path); - } - catch - { - } - return true; + return SetCurrentDirectoryWindows(path); } [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] @@ -1601,9 +1521,7 @@ internal static extern bool GetFileTime( internal static bool DirectoryExists(string fullPath) { - return IsWindows - ? DirectoryExistsWindows(fullPath) - : Directory.Exists(fullPath); + return DirectoryExistsWindows(fullPath); } [SupportedOSPlatform("windows")] @@ -1616,9 +1534,7 @@ internal static bool DirectoryExistsWindows(string fullPath) internal static bool FileExists(string fullPath) { - return IsWindows - ? FileExistsWindows(fullPath) - : File.Exists(fullPath); + return FileExistsWindows(fullPath); } [SupportedOSPlatform("windows")] @@ -1631,9 +1547,7 @@ internal static bool FileExistsWindows(string fullPath) internal static bool FileOrDirectoryExists(string path) { - return IsWindows - ? FileOrDirectoryExistsWindows(path) - : File.Exists(path) || Directory.Exists(path); + return FileOrDirectoryExistsWindows(path); } [SupportedOSPlatform("windows")] diff --git a/src/MSBuildTaskHost/Modifiers.cs b/src/MSBuildTaskHost/Modifiers.cs index 1e0ea38c2c3..2d38b2f68e4 100644 --- a/src/MSBuildTaskHost/Modifiers.cs +++ b/src/MSBuildTaskHost/Modifiers.cs @@ -248,35 +248,22 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS modifiedItemSpec = GetDirectory(fullPath); - if (NativeMethodsShared.IsWindows) + int length = -1; + if (FileUtilitiesRegex.StartsWithDrivePattern(modifiedItemSpec)) { - int length = -1; - if (FileUtilitiesRegex.StartsWithDrivePattern(modifiedItemSpec)) - { - length = 2; - } - else - { - length = FileUtilitiesRegex.StartsWithUncPatternMatchLength(modifiedItemSpec); - } - - if (length != -1) - { - ErrorUtilities.VerifyThrow((modifiedItemSpec.Length > length) && FrameworkFileUtilities.IsSlash(modifiedItemSpec[length]), - "Root directory must have a trailing slash."); - - modifiedItemSpec = modifiedItemSpec.Substring(length + 1); - } + length = 2; } else { - ErrorUtilities.VerifyThrow(!string.IsNullOrEmpty(modifiedItemSpec) && FrameworkFileUtilities.IsSlash(modifiedItemSpec[0]), - "Expected a full non-windows path rooted at '/'."); + length = FileUtilitiesRegex.StartsWithUncPatternMatchLength(modifiedItemSpec); + } + + if (length != -1) + { + ErrorUtilities.VerifyThrow((modifiedItemSpec.Length > length) && FrameworkFileUtilities.IsSlash(modifiedItemSpec[length]), + "Root directory must have a trailing slash."); - // A full unix path is always rooted at - // `/`, and a root-relative path is the - // rest of the string. - modifiedItemSpec = modifiedItemSpec.Substring(1); + modifiedItemSpec = modifiedItemSpec.Substring(length + 1); } } else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.RecursiveDir, StringComparison.OrdinalIgnoreCase)) diff --git a/src/MSBuildTaskHost/NamedPipeUtil.cs b/src/MSBuildTaskHost/NamedPipeUtil.cs index f9fce908434..798f9415f4d 100644 --- a/src/MSBuildTaskHost/NamedPipeUtil.cs +++ b/src/MSBuildTaskHost/NamedPipeUtil.cs @@ -22,16 +22,7 @@ internal static string GetPlatformSpecificPipeName(int? processId = null) internal static string GetPlatformSpecificPipeName(string pipeName) { - if (NativeMethodsShared.IsUnixLike) - { - // We should never get here. This would be a net35 task host running on unix. - ErrorUtilities.ThrowInternalError("Task host used on unix in retrieving the pipe name."); - return string.Empty; - } - else - { - return pipeName; - } + return pipeName; } internal static string GetRarNodePipeName(ServerNodeHandshake handshake) From 6db17449a5baa75f6fac7c5526f87748fafeeaa7 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 11:57:24 -0800 Subject: [PATCH 033/136] MSBuildTaskHost: Remove uncalled members from FileUtilities.cs --- src/MSBuildTaskHost/FileUtilities.cs | 993 +-------------------------- 1 file changed, 2 insertions(+), 991 deletions(-) diff --git a/src/MSBuildTaskHost/FileUtilities.cs b/src/MSBuildTaskHost/FileUtilities.cs index 62c4f7a3377..a7bff193f86 100644 --- a/src/MSBuildTaskHost/FileUtilities.cs +++ b/src/MSBuildTaskHost/FileUtilities.cs @@ -2,19 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Microsoft.Build.Shared.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Globalization; using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; using System.Text; -using System.Threading; using Microsoft.Build.Framework; -using Microsoft.Build.Shared.FileSystem; #nullable disable @@ -27,75 +18,23 @@ namespace Microsoft.Build.Shared /// internal static partial class FileUtilities { - // A list of possible test runners. If the program running has one of these substrings in the name, we assume - // this is a test harness. - - // This flag, when set, indicates that we are running tests. Initially assume it's true. It also implies that - // the currentExecutableOverride is set to a path (that is non-null). Assume this is not initialized when we - // have the impossible combination of runningTests = false and currentExecutableOverride = null. - - // This is the fake current executable we use in case we are running tests. - - /// - /// The directory where MSBuild stores cache information used during the build. - /// - internal static string cacheDirectory = null; - // net35 taskhost does not support AsyncLocal, and the scenario is not relevant there. internal static string CurrentThreadWorkingDirectory = null; internal static string TempFileDirectory => Path.GetTempPath(); - /// - /// FOR UNIT TESTS ONLY - /// Clear out the static variable used for the cache directory so that tests that - /// modify it can validate their modifications. - /// - internal static void ClearCacheDirectoryPath() - { - cacheDirectory = null; - } - - internal static readonly StringComparison PathComparison = GetIsFileSystemCaseSensitive() ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; - - internal static readonly StringComparer PathComparer = GetIsFileSystemCaseSensitive() ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; - - /// - /// Determines whether the file system is case sensitive. - /// Copied from https://github.com/dotnet/runtime/blob/73ba11f3015216b39cb866d9fb7d3d25e93489f2/src/libraries/Common/src/System/IO/PathInternal.CaseSensitivity.cs#L41-L59 - /// - public static bool GetIsFileSystemCaseSensitive() - { - try - { - string pathWithUpperCase = Path.Combine(Path.GetTempPath(), $"CASESENSITIVETEST{Guid.NewGuid():N}"); - using (new FileStream(pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose)) - { - string lowerCased = pathWithUpperCase.ToLowerInvariant(); - return !FileSystems.Default.FileExists(lowerCased); - } - } - catch (Exception exc) - { - // In case something goes terribly wrong, we don't want to fail just because - // of a casing test, so we assume case-insensitive-but-preserving. - Debug.Fail("Casing test failed: " + exc); - return false; - } - } - /// /// Copied from https://github.com/dotnet/corefx/blob/056715ff70e14712419d82d51c8c50c54b9ea795/src/Common/src/System/IO/PathInternal.Windows.cs#L61 /// MSBuild should support the union of invalid path chars across the supported OSes, so builds can have the same behaviour crossplatform: https://github.com/dotnet/msbuild/issues/781#issuecomment-243942514 /// - internal static readonly char[] InvalidPathChars = ( + internal static readonly char[] InvalidPathChars = [ '|', '\0', (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, (char)31 - ]); + ]; /// /// Copied from https://github.com/dotnet/corefx/blob/387cf98c410bdca8fd195b28cbe53af578698f94/src/System.Runtime.Extensions/src/System/IO/Path.Windows.cs#L18 @@ -112,273 +51,6 @@ public static bool GetIsFileSystemCaseSensitive() internal static char[] InvalidFileNameChars => InvalidFileNameCharsArray; - internal static readonly string DirectorySeparatorString = Path.DirectorySeparatorChar.ToString(); - - private static readonly ConcurrentDictionary FileExistenceCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - - private static readonly IFileSystem DefaultFileSystem = FileSystems.Default; - - /// - /// Retrieves the MSBuild runtime cache directory - /// - internal static string GetCacheDirectory() - { - if (cacheDirectory == null) - { - cacheDirectory = Path.Combine(TempFileDirectory, string.Format(CultureInfo.CurrentUICulture, "MSBuild{0}-{1}", EnvironmentUtilities.CurrentProcessId, AppDomain.CurrentDomain.Id)); - } - - return cacheDirectory; - } - - /// - /// Get the hex hash string for the string - /// - internal static string GetHexHash(string stringToHash) - { - return stringToHash.GetHashCode().ToString("X", CultureInfo.InvariantCulture); - } - - /// - /// Get the hash for the assemblyPaths - /// - internal static int GetPathsHash(IEnumerable assemblyPaths) - { - StringBuilder builder = new StringBuilder(); - - foreach (string path in assemblyPaths) - { - if (path != null) - { - string directoryPath = path.Trim(); - if (directoryPath.Length > 0) - { - DateTime lastModifiedTime; - if (NativeMethodsShared.GetLastWriteDirectoryUtcTime(directoryPath, out lastModifiedTime)) - { - builder.Append(lastModifiedTime.Ticks); - builder.Append('|'); - builder.Append(directoryPath.ToUpperInvariant()); - builder.Append('|'); - } - } - } - } - - return builder.ToString().GetHashCode(); - } - - /// - /// Returns whether MSBuild can write to the given directory. Throws for PathTooLongExceptions - /// but not other exceptions. - /// - internal static bool CanWriteToDirectory(string directory) - { - try - { - string testFilePath = Path.Combine(directory, $"MSBuild_{Guid.NewGuid():N}_testFile.txt"); - FileInfo file = new(testFilePath); - file.Directory.Create(); // If the directory already exists, this method does nothing. - File.WriteAllText(testFilePath, $"MSBuild process {EnvironmentUtilities.CurrentProcessId} successfully wrote to file."); - File.Delete(testFilePath); - return true; - } - catch (PathTooLongException) - { - ErrorUtilities.ThrowArgument("DebugPathTooLong", directory); - return false; // Should never reach here. - } - catch (Exception) - { - return false; - } - } - - /// - /// Clears the MSBuild runtime cache - /// - internal static void ClearCacheDirectory() - { - string cacheDirectory = GetCacheDirectory(); - - if (DefaultFileSystem.DirectoryExists(cacheDirectory)) - { - DeleteDirectoryNoThrow(cacheDirectory, true); - } - } - - /// - /// Ensures the path does not have a leading or trailing slash after removing the first 'start' characters. - /// - internal static string EnsureNoLeadingOrTrailingSlash(string path, int start) - { - int stop = path.Length; - while (start < stop && FrameworkFileUtilities.IsSlash(path[start])) - { - start++; - } - while (start < stop && FrameworkFileUtilities.IsSlash(path[stop - 1])) - { - stop--; - } - - return FrameworkFileUtilities.FixFilePath(path.Substring(start, stop - start)); - } - - /// - /// Ensures the path does not have a leading slash after removing the first 'start' characters but does end in a slash. - /// - internal static string EnsureTrailingNoLeadingSlash(string path, int start) - { - int stop = path.Length; - while (start < stop && FrameworkFileUtilities.IsSlash(path[start])) - { - start++; - } - - return FrameworkFileUtilities.FixFilePath(start < stop && FrameworkFileUtilities.IsSlash(path[stop - 1]) ? - path.Substring(start) : - path.Substring(start) + Path.DirectorySeparatorChar); - } - - /// - /// Ensures the path is enclosed within single quotes. - /// - /// The path to check. - /// The path enclosed by quotes. - internal static string EnsureSingleQuotes(string path) - { - return EnsureQuotes(path); - } - - /// - /// Ensures the path is enclosed within double quotes. - /// - /// The path to check. - /// The path enclosed by quotes. - internal static string EnsureDoubleQuotes(string path) - { - return EnsureQuotes(path, isSingleQuote: false); - } - - /// - /// Ensures the path is enclosed within quotes. - /// - /// The path to check. - /// Indicates if single or double quotes should be used - /// The path enclosed by quotes. - internal static string EnsureQuotes(string path, bool isSingleQuote = true) - { - path = FrameworkFileUtilities.FixFilePath(path); - - const char singleQuote = '\''; - const char doubleQuote = '\"'; - var targetQuote = isSingleQuote ? singleQuote : doubleQuote; - var convertQuote = isSingleQuote ? doubleQuote : singleQuote; - - if (!string.IsNullOrEmpty(path)) - { - // Special case: convert the quotes. - if (path.Length > 1 && path[0] == convertQuote && path[path.Length - 1] == convertQuote) - { - path = $"{targetQuote}{path.Substring(1, path.Length - 2)}{targetQuote}"; - } - // Enclose the path in a set of the 'target' quote unless the string is already quoted with the 'target' quotes. - else if (path.Length == 1 || path[0] != targetQuote || path[path.Length - 1] != targetQuote) - { - path = $"{targetQuote}{path}{targetQuote}"; - } - } - - return path; - } - - /// - /// Trims the string and removes any double quotes around it. - /// - internal static string TrimAndStripAnyQuotes(string path) - { - if (path is null) - { - return path; - } - - // Trim returns the same string if trimming isn't needed - path = path.Trim(); - path = path.Trim(['"']); - - return path; - } - - /// - /// Get the directory name of a rooted full path - /// - /// - /// - internal static String GetDirectoryNameOfFullPath(String fullPath) - { - if (fullPath != null) - { - int i = fullPath.Length; - while (i > 0 && fullPath[--i] != Path.DirectorySeparatorChar && fullPath[i] != Path.AltDirectorySeparatorChar) - { - ; - } - - return FrameworkFileUtilities.FixFilePath(fullPath.Substring(0, i)); - } - return null; - } - - internal static string TruncatePathToTrailingSegments(string path, int trailingSegmentsToKeep) - { - return path; - } - - internal static bool ContainsRelativePathSegments(string path) - { - for (int i = 0; i < path.Length; i++) - { - if (i + 1 < path.Length && path[i] == '.' && path[i + 1] == '.') - { - if (RelativePathBoundsAreValid(path, i, i + 1)) - { - return true; - } - else - { - i += 2; - continue; - } - } - - if (path[i] == '.' && RelativePathBoundsAreValid(path, i, i)) - { - return true; - } - } - - return false; - } - - private static bool RelativePathBoundsAreValid(string path, int leftIndex, int rightIndex) - { - var leftBound = leftIndex - 1 >= 0 - ? path[leftIndex - 1] - : (char?)null; - - var rightBound = rightIndex + 1 < path.Length - ? path[rightIndex + 1] - : (char?)null; - - return IsValidRelativePathBound(leftBound) && IsValidRelativePathBound(rightBound); - } - - private static bool IsValidRelativePathBound(char? c) - { - return c == null || IsAnySlash(c.Value); - } - /// /// Gets the canonicalized full path of the provided path. /// Guidance for use: call this on all paths accepted through public entry @@ -393,11 +65,6 @@ internal static string NormalizePath(string path) return FrameworkFileUtilities.FixFilePath(fullPath); } - internal static string NormalizePath(string directory, string file) - { - return NormalizePath(Path.Combine(directory, file)); - } - private static string GetFullPath(string path) { string uncheckedFullPath = NativeMethodsShared.GetFullPath(path); @@ -453,20 +120,6 @@ From Path.cs in the CLR return isUNC || path.IndexOf(@"\\?\globalroot", StringComparison.OrdinalIgnoreCase) != -1; } - /// - /// Normalizes all path separators (both forward and back slashes) to forward slashes. - /// This is platform-independent, unlike FrameworkFileUtilities.FixFilePath which only normalizes on non-Windows platforms. - /// Use this when you need consistent path comparison regardless of which separator style is used. - /// - /// The path to normalize - /// The path with all backslashes replaced by forward slashes, or the original path if null/empty - internal static string NormalizePathSeparatorsToForwardSlash(string path) - { - return string.IsNullOrEmpty(path) ? path : path.Replace('\\', '/'); - } - - internal static bool IsAnySlash(char c) => c == '/' || c == '\\'; - /// /// Extracts the directory from the given file-spec. /// @@ -492,34 +145,6 @@ internal static string GetDirectory(string fileSpec) return directory; } - /// - /// Determines whether the given assembly file name has one of the listed extensions. - /// - /// The name of the file - /// Array of extensions to consider. - /// - internal static bool HasExtension(string fileName, string[] allowedExtensions) - { - Debug.Assert(allowedExtensions?.Length > 0); - - // Easiest way to invoke invalid path chars - // check, which callers are relying on. - if (Path.HasExtension(fileName)) - { - foreach (string extension in allowedExtensions) - { - Debug.Assert(!String.IsNullOrEmpty(extension) && extension[0] == '.'); - - if (fileName.EndsWith(extension, PathComparison)) - { - return true; - } - } - } - - return false; - } - // ISO 8601 Universal time with sortable format internal const string FileTimeFormat = "yyyy'-'MM'-'dd HH':'mm':'ss'.'fffffff"; @@ -581,49 +206,6 @@ internal static string GetFullPathNoThrow(string path) return path; } - /// - /// Compare if two paths, relative to the given currentDirectory are equal. - /// Does not throw IO exceptions. See - /// - /// - /// - /// - /// - /// - internal static bool ComparePathsNoThrow(string first, string second, string currentDirectory, bool alwaysIgnoreCase = false) - { - StringComparison pathComparison = alwaysIgnoreCase ? StringComparison.OrdinalIgnoreCase : PathComparison; - // perf: try comparing the bare strings first - if (string.Equals(first, second, pathComparison)) - { - return true; - } - - var firstFullPath = NormalizePathForComparisonNoThrow(first, currentDirectory); - var secondFullPath = NormalizePathForComparisonNoThrow(second, currentDirectory); - - return string.Equals(firstFullPath, secondFullPath, pathComparison); - } - - /// - /// Normalizes a path for path comparison - /// Does not throw IO exceptions. See - /// - /// - internal static string NormalizePathForComparisonNoThrow(string path, string currentDirectory) - { - // file is invalid, return early to avoid triggering an exception - if (PathIsInvalid(path)) - { - return path; - } - - var normalizedPath = path.NormalizeForPathComparison(); - var fullPath = GetFullPathNoThrow(Path.Combine(currentDirectory, normalizedPath)); - - return fullPath; - } - internal static bool PathIsInvalid(string path) { // Path.GetFileName does not react well to malformed filenames. @@ -638,100 +220,6 @@ internal static bool PathIsInvalid(string path) return true; } - /// - /// A variation on File.Delete that will throw ExceptionHandling.NotExpectedException exceptions - /// - internal static void DeleteNoThrow(string path) - { - try - { - File.Delete(FrameworkFileUtilities.FixFilePath(path)); - } - catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) - { - } - } - - /// - /// A variation on Directory.Delete that will throw ExceptionHandling.NotExpectedException exceptions - /// - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Int32.TryParse(System.String,System.Int32@)", Justification = "We expect the out value to be 0 if the parse fails and compensate accordingly")] - internal static void DeleteDirectoryNoThrow(string path, bool recursive, int retryCount = 0, int retryTimeOut = 0) - { - // Try parse will set the out parameter to 0 if the string passed in is null, or is outside the range of an int. - if (!int.TryParse(Environment.GetEnvironmentVariable("MSBUILDDIRECTORYDELETERETRYCOUNT"), out retryCount)) - { - retryCount = 0; - } - - if (!int.TryParse(Environment.GetEnvironmentVariable("MSBUILDDIRECTORYDELETRETRYTIMEOUT"), out retryTimeOut)) - { - retryTimeOut = 0; - } - - retryCount = retryCount < 1 ? 2 : retryCount; - retryTimeOut = retryTimeOut < 1 ? 500 : retryTimeOut; - - path = FrameworkFileUtilities.FixFilePath(path); - - for (int i = 0; i < retryCount; i++) - { - try - { - if (DefaultFileSystem.DirectoryExists(path)) - { - Directory.Delete(path, recursive); - break; - } - } - catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) - { - } - - if (i + 1 < retryCount) // should not wait for the final iteration since we not gonna check anyway - { - Thread.Sleep(retryTimeOut); - } - } - } - - /// - /// Deletes a directory, ensuring that Directory.Delete does not get a path ending in a slash. - /// - /// - /// This is a workaround for https://github.com/dotnet/corefx/issues/3780, which clashed with a common - /// pattern in our tests. - /// - internal static void DeleteWithoutTrailingBackslash(string path, bool recursive = false) - { - // Some tests (such as FileMatcher and Evaluation tests) were failing with an UnauthorizedAccessException or directory not empty. - // This retry logic works around that issue. - const int NUM_TRIES = 3; - for (int i = 0; i < NUM_TRIES; i++) - { - try - { - Directory.Delete(FrameworkFileUtilities.EnsureNoTrailingSlash(path), recursive); - - // If we got here, the directory was successfully deleted - return; - } - catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) - { - if (i == NUM_TRIES - 1) - { - // var files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories); - // string fileString = string.Join(Environment.NewLine, files); - // string message = $"Unable to delete directory '{path}'. Contents:" + Environment.NewLine + fileString; - // throw new IOException(message, ex); - throw; - } - } - - Thread.Sleep(10); - } - } - /// /// Gets a file info object for the specified file path. If the file path /// is invalid, or is a directory, or cannot be accessed, or does not exist, @@ -770,214 +258,6 @@ internal static FileInfo GetFileInfoNoThrow(string filePath) } } - /// - /// Returns if the directory exists - /// - /// Full path to the directory in the filesystem - /// The file system - /// - internal static bool DirectoryExistsNoThrow(string fullPath, IFileSystem fileSystem = null) - { - fullPath = AttemptToShortenPath(fullPath); - - try - { - fileSystem ??= DefaultFileSystem; - - return Traits.Instance.CacheFileExistence - ? FileExistenceCache.GetOrAdd(fullPath, fullPath => fileSystem.DirectoryExists(fullPath)) - : fileSystem.DirectoryExists(fullPath); - } - catch - { - return false; - } - } - - /// - /// Returns if the directory exists - /// - /// Full path to the file in the filesystem - /// The file system - /// - internal static bool FileExistsNoThrow(string fullPath, IFileSystem fileSystem = null) - { - fullPath = AttemptToShortenPath(fullPath); - - try - { - fileSystem ??= DefaultFileSystem; - - return Traits.Instance.CacheFileExistence - ? FileExistenceCache.GetOrAdd(fullPath, fullPath => fileSystem.FileExists(fullPath)) - : fileSystem.FileExists(fullPath); - } - catch - { - return false; - } - } - - /// - /// If there is a directory or file at the specified path, returns true. - /// Otherwise, returns false. - /// Does not throw IO exceptions, to match Directory.Exists and File.Exists. - /// Unlike calling each of those in turn it only accesses the disk once, which is faster. - /// - internal static bool FileOrDirectoryExistsNoThrow(string fullPath, IFileSystem fileSystem = null) - { - fullPath = AttemptToShortenPath(fullPath); - - try - { - fileSystem ??= DefaultFileSystem; - - return Traits.Instance.CacheFileExistence - ? FileExistenceCache.GetOrAdd(fullPath, fullPath => fileSystem.FileOrDirectoryExists(fullPath)) - : fileSystem.FileOrDirectoryExists(fullPath); - } - catch - { - return false; - } - } - - /// - /// This method returns true if the specified filename is a solution file (.sln) or - /// solution filter file (.slnf); otherwise, it returns false. - /// - /// - /// Solution filters are included because they are a thin veneer over solutions, just - /// with a more limited set of projects to build, and should be treated the same way. - /// - internal static bool IsSolutionFilename(string filename) - { - return HasExtension(filename, ".sln") || - HasExtension(filename, ".slnf") || - HasExtension(filename, ".slnx"); - } - - internal static bool IsSolutionFilterFilename(string filename) - { - return HasExtension(filename, ".slnf"); - } - - internal static bool IsSolutionXFilename(string filename) - { - return HasExtension(filename, ".slnx"); - } - - /// - /// Returns true if the specified filename is a VC++ project file, otherwise returns false - /// - internal static bool IsVCProjFilename(string filename) - { - return HasExtension(filename, ".vcproj"); - } - - internal static bool IsDspFilename(string filename) - { - return HasExtension(filename, ".dsp"); - } - - /// - /// Returns true if the specified filename is a metaproject file (.metaproj), otherwise false. - /// - internal static bool IsMetaprojectFilename(string filename) - { - return HasExtension(filename, ".metaproj"); - } - - internal static bool IsBinaryLogFilename(string filename) - { - return HasExtension(filename, ".binlog"); - } - - private static bool HasExtension(string filename, string extension) - { - if (String.IsNullOrEmpty(filename)) - { - return false; - } - - return filename.EndsWith(extension, PathComparison); - } - - /// - /// Given the absolute location of a file, and a disc location, returns relative file path to that disk location. - /// Throws UriFormatException. - /// - /// - /// The base path we want to be relative to. Must be absolute. - /// Should not include a filename as the last segment will be interpreted as a directory. - /// - /// - /// The path we need to make relative to basePath. The path can be either absolute path or a relative path in which case it is relative to the base path. - /// If the path cannot be made relative to the base path (for example, it is on another drive), it is returned verbatim. - /// If the basePath is an empty string, returns the path. - /// - /// relative path (can be the full path) - internal static string MakeRelative(string basePath, string path) - { - ErrorUtilities.VerifyThrowArgumentNull(basePath); - ErrorUtilities.VerifyThrowArgumentLength(path); - - string fullBase = Path.GetFullPath(basePath); - string fullPath = Path.GetFullPath(path); - - string[] splitBase = fullBase.Split(MSBuildConstants.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); - string[] splitPath = fullPath.Split(MSBuildConstants.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); - - ErrorUtilities.VerifyThrow(splitPath.Length > 0, "Cannot call MakeRelative on a path of only slashes."); - - // On a mac, the path could start with any number of slashes and still be valid. We have to check them all. - int indexOfFirstNonSlashChar = 0; - while (path[indexOfFirstNonSlashChar] == Path.DirectorySeparatorChar) - { - indexOfFirstNonSlashChar++; - } - if (path.IndexOf(splitPath[0]) != indexOfFirstNonSlashChar) - { - // path was already relative so just return it - return FrameworkFileUtilities.FixFilePath(path); - } - - int index = 0; - while (index < splitBase.Length && index < splitPath.Length && splitBase[index].Equals(splitPath[index], PathComparison)) - { - index++; - } - - if (index == splitBase.Length && index == splitPath.Length) - { - return "."; - } - - // If the paths have no component in common, the only valid relative path is the full path. - if (index == 0) - { - return fullPath; - } - - StringBuilder sb = StringBuilderCache.Acquire(); - - for (int i = index; i < splitBase.Length; i++) - { - sb.Append("..").Append(Path.DirectorySeparatorChar); - } - for (int i = index; i < splitPath.Length; i++) - { - sb.Append(splitPath[i]).Append(Path.DirectorySeparatorChar); - } - - if (fullPath[fullPath.Length - 1] != Path.DirectorySeparatorChar) - { - sb.Length--; - } - - return StringBuilderCache.GetStringAndRelease(sb); - } - /// /// Normalizes the path if and only if it is longer than max path, /// or would be if rooted by the current directory. @@ -1063,132 +343,6 @@ internal static string CombinePaths(string root, params string[] paths) return paths.Aggregate(root, Path.Combine); } - internal static string TrimTrailingSlashes(this string s) - { - return s.TrimEnd(FrameworkFileUtilities.Slashes); - } - - /// - /// Replace all backward slashes to forward slashes - /// - internal static string ToSlash(this string s) - { - return s.Replace('\\', '/'); - } - - internal static string ToBackslash(this string s) - { - return s.Replace('/', '\\'); - } - - /// - /// Ensure all slashes are the current platform's slash - /// - /// - /// - internal static string ToPlatformSlash(this string s) - { - var separator = Path.DirectorySeparatorChar; - - return s.Replace(separator == '/' ? '\\' : '/', separator); - } - - internal static string WithTrailingSlash(this string s) - { - return FrameworkFileUtilities.EnsureTrailingSlash(s); - } - - internal static string NormalizeForPathComparison(this string s) => s.ToPlatformSlash().TrimTrailingSlashes(); - - // TODO: assumption on file system case sensitivity: https://github.com/dotnet/msbuild/issues/781 - internal static bool PathsEqual(string path1, string path2) - { - if (path1 == null && path2 == null) - { - return true; - } - if (path1 == null || path2 == null) - { - return false; - } - - var endA = path1.Length - 1; - var endB = path2.Length - 1; - - // Trim trailing slashes - for (var i = endA; i >= 0; i--) - { - var c = path1[i]; - if (c == '/' || c == '\\') - { - endA--; - } - else - { - break; - } - } - - for (var i = endB; i >= 0; i--) - { - var c = path2[i]; - if (c == '/' || c == '\\') - { - endB--; - } - else - { - break; - } - } - - if (endA != endB) - { - // Lengths not the same - return false; - } - - for (var i = 0; i <= endA; i++) - { - var charA = (uint)path1[i]; - var charB = (uint)path2[i]; - - if ((charA | charB) > 0x7F) - { - // Non-ascii chars move to non fast path - return PathsEqualNonAscii(path1, path2, i, endA - i + 1); - } - - // uppercase both chars - notice that we need just one compare per char - if ((uint)(charA - 'a') <= (uint)('z' - 'a')) - { - charA -= 0x20; - } - - if ((uint)(charB - 'a') <= (uint)('z' - 'a')) - { - charB -= 0x20; - } - - // Set path delimiters the same - if (charA == '\\') - { - charA = '/'; - } - if (charB == '\\') - { - charB = '/'; - } - - if (charA != charB) - { - return false; - } - } - - return true; - } - internal static StreamWriter OpenWrite(string path, bool append, Encoding encoding = null) { const int DefaultFileStreamBufferSize = 4096; @@ -1203,148 +357,5 @@ internal static StreamWriter OpenWrite(string path, bool append, Encoding encodi return new StreamWriter(fileStream, encoding); } } - - internal static StreamReader OpenRead(string path, Encoding encoding = null, bool detectEncodingFromByteOrderMarks = true) - { - const int DefaultFileStreamBufferSize = 4096; - Stream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan); - if (encoding == null) - { - return new StreamReader(fileStream); - } - else - { - return new StreamReader(fileStream, encoding, detectEncodingFromByteOrderMarks); - } - } - - /// - /// Locate a file in either the directory specified or a location in the - /// directory structure above that directory. - /// - internal static string GetDirectoryNameOfFileAbove(string startingDirectory, string fileName, IFileSystem fileSystem = null) - { - fileSystem ??= DefaultFileSystem; - - // Canonicalize our starting location - string lookInDirectory = GetFullPath(startingDirectory); - - do - { - // Construct the path that we will use to test against - string possibleFileDirectory = Path.Combine(lookInDirectory, fileName); - - // If we successfully locate the file in the directory that we're - // looking in, simply return that location. Otherwise we'll - // keep moving up the tree. - if (fileSystem.FileExists(possibleFileDirectory)) - { - // We've found the file, return the directory we found it in - return lookInDirectory; - } - else - { - // GetDirectoryName will return null when we reach the root - // terminating our search - lookInDirectory = Path.GetDirectoryName(lookInDirectory); - } - } - while (lookInDirectory != null); - - // When we didn't find the location, then return an empty string - return string.Empty; - } - - /// - /// Searches for a file based on the specified starting directory. - /// - /// The file to search for. - /// An optional directory to start the search in. The default location is the directory - /// of the file containing the property function. - /// The filesystem - /// The full path of the file if it is found, otherwise an empty string. - internal static string GetPathOfFileAbove(string file, string startingDirectory, IFileSystem fileSystem = null) - { - // This method does not accept a path, only a file name - if (file.Any(i => i.Equals(Path.DirectorySeparatorChar) || i.Equals(Path.AltDirectorySeparatorChar))) - { - ErrorUtilities.ThrowArgument("InvalidGetPathOfFileAboveParameter", file); - } - - // Search for a directory that contains that file - string directoryName = GetDirectoryNameOfFileAbove(startingDirectory, file, fileSystem); - - return String.IsNullOrEmpty(directoryName) ? String.Empty : NormalizePath(directoryName, file); - } - - internal static void EnsureDirectoryExists(string directoryPath) - { - if (!string.IsNullOrEmpty(directoryPath) && !DefaultFileSystem.DirectoryExists(directoryPath)) - { - Directory.CreateDirectory(directoryPath); - } - } - - // Method is simple set of function calls and may inline; - // we don't want it inlining into the tight loop that calls it as an exit case, - // so mark as non-inlining - [MethodImpl(MethodImplOptions.NoInlining)] - private static bool PathsEqualNonAscii(string strA, string strB, int i, int length) - { - if (string.Compare(strA, i, strB, i, length, StringComparison.OrdinalIgnoreCase) == 0) - { - return true; - } - - var slash1 = strA.ToSlash(); - var slash2 = strB.ToSlash(); - - if (string.Compare(slash1, i, slash2, i, length, StringComparison.OrdinalIgnoreCase) == 0) - { - return true; - } - - return false; - } - - internal static void ReadFromStream(this Stream stream, byte[] content, int startIndex, int length) - { - stream.ReadExactly(content, startIndex, length); - } - } -} - -#if !NET -namespace System.IO -{ - internal static class StreamExtensions - { - internal static void ReadExactly(this Stream stream, byte[] buffer, int offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - if (offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - if ((uint)count > buffer.Length - offset) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - while (count > 0) - { - int read = stream.Read(buffer, offset, count); - if (read <= 0) - { - throw new EndOfStreamException(); - } - offset += read; - count -= read; - } - } } } -#endif From 9a58fc9be8b511254cdc32c2e0cd9c4e235c0100 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 12:02:33 -0800 Subject: [PATCH 034/136] MSBuildTaskHost: Reduce accessibility of FileUtilities.cs members Several FileUtilities members are internal or public but only called from with FileUtilities. These can be reduced to private. --- src/MSBuildTaskHost/FileUtilities.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/MSBuildTaskHost/FileUtilities.cs b/src/MSBuildTaskHost/FileUtilities.cs index a7bff193f86..f83c77b9a84 100644 --- a/src/MSBuildTaskHost/FileUtilities.cs +++ b/src/MSBuildTaskHost/FileUtilities.cs @@ -58,7 +58,7 @@ internal static partial class FileUtilities /// is rooted, using ErrorUtilities.VerifyThrowPathRooted. /// ASSUMES INPUT IS ALREADY UNESCAPED. /// - internal static string NormalizePath(string path) + private static string NormalizePath(string path) { ErrorUtilities.VerifyThrowArgumentLength(path); string fullPath = GetFullPath(path); @@ -125,7 +125,7 @@ From Path.cs in the CLR /// /// The filespec. /// directory path - internal static string GetDirectory(string fileSpec) + private static string GetDirectory(string fileSpec) { string directory = Path.GetDirectoryName(FrameworkFileUtilities.FixFilePath(fileSpec)); @@ -161,7 +161,7 @@ internal static string GetDirectory(string fileSpec) /// /// Whether to escape the path after getting the full path. /// Full path to the file, escaped if not specified otherwise. - internal static string GetFullPath(string fileSpec, string currentDirectory, bool escape = true) + private static string GetFullPath(string fileSpec, string currentDirectory, bool escape = true) { // Sending data out of the engine into the filesystem, so time to unescape. fileSpec = FrameworkFileUtilities.FixFilePath(EscapingUtilities.UnescapeAll(fileSpec)); @@ -193,7 +193,7 @@ internal static string GetFullPath(string fileSpec, string currentDirectory, boo /// Useful to get a better path for an error message, without the risk of throwing /// if the error message was itself caused by the path being invalid! /// - internal static string GetFullPathNoThrow(string path) + private static string GetFullPathNoThrow(string path) { try { @@ -230,7 +230,7 @@ internal static bool PathIsInvalid(string path) /// /// /// FileInfo around path if it is an existing /file/, else null - internal static FileInfo GetFileInfoNoThrow(string filePath) + private static FileInfo GetFileInfoNoThrow(string filePath) { filePath = AttemptToShortenPath(filePath); @@ -263,7 +263,7 @@ internal static FileInfo GetFileInfoNoThrow(string filePath) /// or would be if rooted by the current directory. /// This may make it shorter by removing ".."'s. /// - internal static string AttemptToShortenPath(string path) + private static string AttemptToShortenPath(string path) { if (IsPathTooLong(path) || IsPathTooLongIfRooted(path)) { @@ -273,7 +273,7 @@ internal static string AttemptToShortenPath(string path) return FrameworkFileUtilities.FixFilePath(path); } - public static bool IsPathTooLong(string path) + private static bool IsPathTooLong(string path) { // >= not > because MAX_PATH assumes a trailing null return path.Length >= NativeMethodsShared.MaxPath; From 422f4624bfb854b2497d5fe37b745fb6ad531bc2 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 12:07:48 -0800 Subject: [PATCH 035/136] MSBuildTaskHost: Merge FrameworkFileUtilities into FileUtilities --- src/MSBuildTaskHost/FileUtilities.cs | 45 +++++++++-- .../Framework/FileUtilities.cs | 77 ------------------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - src/MSBuildTaskHost/Modifiers.cs | 6 +- 4 files changed, 40 insertions(+), 89 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/FileUtilities.cs diff --git a/src/MSBuildTaskHost/FileUtilities.cs b/src/MSBuildTaskHost/FileUtilities.cs index f83c77b9a84..3ca001e7f95 100644 --- a/src/MSBuildTaskHost/FileUtilities.cs +++ b/src/MSBuildTaskHost/FileUtilities.cs @@ -18,6 +18,8 @@ namespace Microsoft.Build.Shared /// internal static partial class FileUtilities { + private static readonly char[] s_slashes = ['/', '\\']; + // net35 taskhost does not support AsyncLocal, and the scenario is not relevant there. internal static string CurrentThreadWorkingDirectory = null; @@ -51,6 +53,33 @@ internal static partial class FileUtilities internal static char[] InvalidFileNameChars => InvalidFileNameCharsArray; + /// + /// Indicates if the given character is a slash. + /// + /// + /// true, if slash + private static bool IsSlash(char c) + { + return (c == Path.DirectorySeparatorChar) || (c == Path.AltDirectorySeparatorChar); + } + + /// + /// Indicates if the given file-spec ends with a slash. + /// + /// The file spec. + /// true, if file-spec has trailing slash + private static bool EndsWithSlash(string fileSpec) + { + return (fileSpec.Length > 0) + ? IsSlash(fileSpec[fileSpec.Length - 1]) + : false; + } + + private static string FixFilePath(string path) + { + return string.IsNullOrEmpty(path) || Path.DirectorySeparatorChar == '\\' ? path : path.Replace('\\', '/'); + } + /// /// Gets the canonicalized full path of the provided path. /// Guidance for use: call this on all paths accepted through public entry @@ -62,7 +91,7 @@ private static string NormalizePath(string path) { ErrorUtilities.VerifyThrowArgumentLength(path); string fullPath = GetFullPath(path); - return FrameworkFileUtilities.FixFilePath(fullPath); + return FixFilePath(fullPath); } private static string GetFullPath(string path) @@ -127,7 +156,7 @@ From Path.cs in the CLR /// directory path private static string GetDirectory(string fileSpec) { - string directory = Path.GetDirectoryName(FrameworkFileUtilities.FixFilePath(fileSpec)); + string directory = Path.GetDirectoryName(FixFilePath(fileSpec)); // if file-spec is a root directory e.g. c:, c:\, \, \\server\share // NOTE: Path.GetDirectoryName also treats invalid UNC file-specs as root directories e.g. \\, \\server @@ -136,7 +165,7 @@ private static string GetDirectory(string fileSpec) // just use the file-spec as-is directory = fileSpec; } - else if ((directory.Length > 0) && !FrameworkFileUtilities.EndsWithSlash(directory)) + else if ((directory.Length > 0) && !EndsWithSlash(directory)) { // restore trailing slash if Path.GetDirectoryName has removed it (this happens with non-root directories) directory += Path.DirectorySeparatorChar; @@ -164,7 +193,7 @@ private static string GetDirectory(string fileSpec) private static string GetFullPath(string fileSpec, string currentDirectory, bool escape = true) { // Sending data out of the engine into the filesystem, so time to unescape. - fileSpec = FrameworkFileUtilities.FixFilePath(EscapingUtilities.UnescapeAll(fileSpec)); + fileSpec = FixFilePath(EscapingUtilities.UnescapeAll(fileSpec)); string fullPath = NormalizePath(Path.Combine(currentDirectory, fileSpec)); // In some cases we might want to NOT escape in order to preserve symbols like @, %, $ etc. @@ -174,7 +203,7 @@ private static string GetFullPath(string fileSpec, string currentDirectory, bool fullPath = EscapingUtilities.Escape(fullPath); } - if (!FrameworkFileUtilities.EndsWithSlash(fullPath)) + if (!EndsWithSlash(fullPath)) { if (FileUtilitiesRegex.IsDrivePattern(fileSpec) || FileUtilitiesRegex.IsUncPattern(fullPath)) @@ -213,7 +242,7 @@ internal static bool PathIsInvalid(string path) // It also throws exceptions on illegal path characters if (path.IndexOfAny(InvalidPathChars) < 0) { - int lastDirectorySeparator = path.LastIndexOfAny(FrameworkFileUtilities.Slashes); + int lastDirectorySeparator = path.LastIndexOfAny(s_slashes); return path.IndexOfAny(InvalidFileNameChars, lastDirectorySeparator >= 0 ? lastDirectorySeparator + 1 : 0) >= 0; } @@ -270,7 +299,7 @@ private static string AttemptToShortenPath(string path) // Attempt to make it shorter -- perhaps there are some \..\ elements path = GetFullPathNoThrow(path); } - return FrameworkFileUtilities.FixFilePath(path); + return FixFilePath(path); } private static bool IsPathTooLong(string path) @@ -294,7 +323,7 @@ private static bool IsRootedNoThrow(string path) { try { - return Path.IsPathRooted(FrameworkFileUtilities.FixFilePath(path)); + return Path.IsPathRooted(FixFilePath(path)); } catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) { diff --git a/src/MSBuildTaskHost/Framework/FileUtilities.cs b/src/MSBuildTaskHost/Framework/FileUtilities.cs deleted file mode 100644 index 4b8022e8a37..00000000000 --- a/src/MSBuildTaskHost/Framework/FileUtilities.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.IO; - -namespace Microsoft.Build.Framework -{ - // TODO: this should be unified with Shared\FileUtilities, but it is hard to untangle everything in one go. - // Moved some of the methods here for now. - - /// - /// This class contains utility methods for file IO. - /// Functions from FileUtilities are transferred here as part of the effort to remove Shared files. - /// - internal static class FrameworkFileUtilities - { - internal static readonly char[] Slashes = ['/', '\\']; - - /// - /// Indicates if the given character is a slash. - /// - /// - /// true, if slash - internal static bool IsSlash(char c) - { - return (c == Path.DirectorySeparatorChar) || (c == Path.AltDirectorySeparatorChar); - } - - /// - /// Indicates if the given file-spec ends with a slash. - /// - /// The file spec. - /// true, if file-spec has trailing slash - internal static bool EndsWithSlash(string fileSpec) - { - return (fileSpec.Length > 0) - ? IsSlash(fileSpec[fileSpec.Length - 1]) - : false; - } - - internal static string FixFilePath(string path) - { - return string.IsNullOrEmpty(path) || Path.DirectorySeparatorChar == '\\' ? path : path.Replace('\\', '/'); - } - - /// - /// If the given path doesn't have a trailing slash then add one. - /// If the path is an empty string, does not modify it. - /// - /// The path to check. - /// A path with a slash. - internal static string EnsureTrailingSlash(string fileSpec) - { - fileSpec = FixFilePath(fileSpec); - if (fileSpec.Length > 0 && !IsSlash(fileSpec[fileSpec.Length - 1])) - { - fileSpec += Path.DirectorySeparatorChar; - } - - return fileSpec; - } - - /// - /// Ensures the path does not have a trailing slash. - /// - internal static string EnsureNoTrailingSlash(string path) - { - path = FixFilePath(path); - if (EndsWithSlash(path)) - { - path = path.Substring(0, path.Length - 1); - } - - return path; - } - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 1aecb136088..456d5b3060a 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -71,7 +71,6 @@ - diff --git a/src/MSBuildTaskHost/Modifiers.cs b/src/MSBuildTaskHost/Modifiers.cs index 2d38b2f68e4..d396dfddc2b 100644 --- a/src/MSBuildTaskHost/Modifiers.cs +++ b/src/MSBuildTaskHost/Modifiers.cs @@ -199,7 +199,7 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS modifiedItemSpec = Path.GetPathRoot(fullPath); - if (!FrameworkFileUtilities.EndsWithSlash(modifiedItemSpec)) + if (!EndsWithSlash(modifiedItemSpec)) { ErrorUtilities.VerifyThrow(FileUtilitiesRegex.StartsWithUncPattern(modifiedItemSpec), "Only UNC shares should be missing trailing slashes."); @@ -221,7 +221,7 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS else { // Fix path to avoid problem with Path.GetFileNameWithoutExtension when backslashes in itemSpec on Unix - modifiedItemSpec = Path.GetFileNameWithoutExtension(FrameworkFileUtilities.FixFilePath(itemSpec)); + modifiedItemSpec = Path.GetFileNameWithoutExtension(FixFilePath(itemSpec)); } } else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.Extension, StringComparison.OrdinalIgnoreCase)) @@ -260,7 +260,7 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS if (length != -1) { - ErrorUtilities.VerifyThrow((modifiedItemSpec.Length > length) && FrameworkFileUtilities.IsSlash(modifiedItemSpec[length]), + ErrorUtilities.VerifyThrow((modifiedItemSpec.Length > length) && IsSlash(modifiedItemSpec[length]), "Root directory must have a trailing slash."); modifiedItemSpec = modifiedItemSpec.Substring(length + 1); From aa924a0a21822c793797daccfc92d73ab3a568e0 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 12:11:18 -0800 Subject: [PATCH 036/136] MSBuildTaskHost: Remove uncalled members from XMakeAttributes.cs --- src/MSBuildTaskHost/XMakeAttributes.cs | 408 ------------------------- 1 file changed, 408 deletions(-) diff --git a/src/MSBuildTaskHost/XMakeAttributes.cs b/src/MSBuildTaskHost/XMakeAttributes.cs index 99a3e5c36d4..70092a2b4d3 100644 --- a/src/MSBuildTaskHost/XMakeAttributes.cs +++ b/src/MSBuildTaskHost/XMakeAttributes.cs @@ -13,67 +13,6 @@ namespace Microsoft.Build.Shared /// 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"; @@ -92,323 +31,6 @@ internal struct MSBuildArchitectureValues 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 KnownSpecialTaskAttributes = new HashSet { condition, continueOnError, msbuildRuntime, msbuildArchitecture, xmlns }; - - private static readonly HashSet KnownSpecialTaskAttributesIgnoreCase = new HashSet(StringComparer.OrdinalIgnoreCase) { condition, continueOnError, msbuildRuntime, msbuildArchitecture, xmlns }; - - private static readonly HashSet KnownBatchingTargetAttributes = new HashSet { name, condition, dependsOnTargets, beforeTargets, afterTargets }; - - private static readonly HashSet ValidMSBuildRuntimeValues = new HashSet(StringComparer.OrdinalIgnoreCase) { MSBuildRuntimeValues.clr2, MSBuildRuntimeValues.clr4, MSBuildRuntimeValues.currentRuntime, MSBuildRuntimeValues.net, MSBuildRuntimeValues.any }; - - private static readonly HashSet ValidMSBuildArchitectureValues = new HashSet(StringComparer.OrdinalIgnoreCase) { MSBuildArchitectureValues.x86, MSBuildArchitectureValues.x64, MSBuildArchitectureValues.arm64, MSBuildArchitectureValues.currentArchitecture, MSBuildArchitectureValues.any }; - - /// - /// 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. - /// - /// - /// true, if given attribute is a reserved task attribute - 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); - } - - /// - /// Checks if the specified attribute is a reserved task attribute with incorrect casing. - /// - /// - /// true, if the given attribute is reserved and badly cased - internal static bool IsBadlyCasedSpecialTaskAttribute(string attribute) - { - return !IsSpecialTaskAttribute(attribute) && KnownSpecialTaskAttributesIgnoreCase.Contains(attribute); - } - - /// - /// Indicates if the specified attribute cannot be used for batching targets. - /// - /// - /// true, if a target cannot batch on the given attribute - internal static bool IsNonBatchingTargetAttribute(string attribute) - { - return KnownBatchingTargetAttributes.Contains(attribute); - } - - /// - /// Returns true if the given string is a valid member of the MSBuildRuntimeValues set - /// - internal static bool IsValidMSBuildRuntimeValue(string runtime) => runtime == null || ValidMSBuildRuntimeValues.Contains(runtime); - - /// - /// Returns true if the given string is a valid member of the MSBuildArchitectureValues set - /// - internal static bool IsValidMSBuildArchitectureValue(string architecture) => architecture == null || ValidMSBuildArchitectureValues.Contains(architecture); - - /// - /// Compares two members of MSBuildRuntimeValues, returning true if they count as a match, and false otherwise. - /// - 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; - } - - /// - /// 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". - /// - 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; - } - - /// - /// Compares two members of MSBuildArchitectureValues, returning true if they count as a match, and false otherwise. - /// - 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; - } - - /// - /// 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. - /// - 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; - } - } - - /// - /// 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. - /// - 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; - } - /// /// Returns the MSBuildArchitecture value corresponding to the current process' architecture. /// @@ -420,35 +42,5 @@ internal static string GetCurrentMSBuildArchitecture() string currentArchitecture = (IntPtr.Size == sizeof(Int64)) ? MSBuildArchitectureValues.x64 : MSBuildArchitectureValues.x86; return currentArchitecture; } - - /// - /// Returns the MSBuildRuntime value corresponding to the current process' runtime. - /// - internal static string GetCurrentMSBuildRuntime() - { - return MSBuildRuntimeValues.net; - } - - /// - /// 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. - /// - 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; - } - } } } From 5b3f141384ea592b48f2fb79f2bca7757566c763 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 12:14:04 -0800 Subject: [PATCH 037/136] MSBuildTaskHost: Remove uncalled members from TypeLoader.cs --- src/MSBuildTaskHost/TypeLoader.cs | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/src/MSBuildTaskHost/TypeLoader.cs b/src/MSBuildTaskHost/TypeLoader.cs index 741ec5db9fa..3627611b337 100644 --- a/src/MSBuildTaskHost/TypeLoader.cs +++ b/src/MSBuildTaskHost/TypeLoader.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Threading; using Microsoft.Build.Framework; +using Microsoft.Build.Shared.Concurrent; #nullable disable @@ -20,12 +21,7 @@ internal class TypeLoader /// /// Cache to keep track of the assemblyLoadInfos based on a given typeFilter. /// - private static Concurrent.ConcurrentDictionary> s_cacheOfLoadedTypesByFilter = new Concurrent.ConcurrentDictionary>(); - - /// - /// Cache to keep track of the assemblyLoadInfos based on a given type filter for assemblies which are to be loaded for reflectionOnlyLoads. - /// - private static Concurrent.ConcurrentDictionary> s_cacheOfReflectionOnlyLoadedTypesByFilter = new Concurrent.ConcurrentDictionary>(); + private static ConcurrentDictionary> s_cacheOfLoadedTypesByFilter = new ConcurrentDictionary>(); /// /// Typefilter for this typeloader @@ -151,25 +147,12 @@ internal LoadedType Load( /// any) is unambiguous; otherwise, if there are multiple types with the same name in different namespaces, the first type /// found will be returned. /// - /// The loaded type, or null if the type was not found. - internal LoadedType ReflectionOnlyLoad( - string typeName, - AssemblyLoadInfo assembly) - { - return GetLoadedType(s_cacheOfReflectionOnlyLoadedTypesByFilter, typeName, assembly); - } - - /// - /// Loads the specified type if it exists in the given assembly. If the type name is fully qualified, then a match (if - /// any) is unambiguous; otherwise, if there are multiple types with the same name in different namespaces, the first type - /// found will be returned. - /// - private LoadedType GetLoadedType(Concurrent.ConcurrentDictionary> cache, string typeName, AssemblyLoadInfo assembly) + private LoadedType GetLoadedType(ConcurrentDictionary> cache, string typeName, AssemblyLoadInfo assembly) { // A given type filter have been used on a number of assemblies, Based on the type filter we will get another dictionary which // will map a specific AssemblyLoadInfo to a AssemblyInfoToLoadedTypes class which knows how to find a typeName in a given assembly. - Concurrent.ConcurrentDictionary loadInfoToType = - cache.GetOrAdd(_isDesiredType, (_) => new Concurrent.ConcurrentDictionary()); + ConcurrentDictionary loadInfoToType = + cache.GetOrAdd(_isDesiredType, (_) => new ConcurrentDictionary()); // Get an object which is able to take a typename and determine if it is in the assembly pointed to by the AssemblyInfo. AssemblyInfoToLoadedTypes typeNameToType = @@ -205,7 +188,7 @@ private class AssemblyInfoToLoadedTypes /// /// What is the type for the given type name, this may be null if the typeName does not map to a type. /// - private Concurrent.ConcurrentDictionary _typeNameToType; + private ConcurrentDictionary _typeNameToType; /// /// List of public types in the assembly which match the typefilter and their corresponding types @@ -234,7 +217,7 @@ internal AssemblyInfoToLoadedTypes(TypeFilter typeFilter, AssemblyLoadInfo loadI _isDesiredType = typeFilter; _assemblyLoadInfo = loadInfo; - _typeNameToType = new Concurrent.ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + _typeNameToType = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); _publicTypeNameToType = new Dictionary(StringComparer.OrdinalIgnoreCase); } From 8e9421a421e0ce5d81c82bb4b8f5d936f3c80d17 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 12:18:17 -0800 Subject: [PATCH 038/136] MSBuildTaskHost: Remove NamedPipeUtil.cs Only NamedPipeUtil.GetPlatformSpecificPipeName(...) is called within MSBuildTaskHost, and it only has one called. So, the implementation can be inlined to the caller in NodeEndpointOutOfProcBase and NamedPipeUtilis.cs can be removed. --- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - src/MSBuildTaskHost/NamedPipeUtil.cs | 34 ------------------- .../NodeEndpointOutOfProcBase.cs | 2 +- 3 files changed, 1 insertion(+), 36 deletions(-) delete mode 100644 src/MSBuildTaskHost/NamedPipeUtil.cs diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 456d5b3060a..83121b31b41 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -105,7 +105,6 @@ - diff --git a/src/MSBuildTaskHost/NamedPipeUtil.cs b/src/MSBuildTaskHost/NamedPipeUtil.cs deleted file mode 100644 index 798f9415f4d..00000000000 --- a/src/MSBuildTaskHost/NamedPipeUtil.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.IO; -using Microsoft.Build.Internal; - -namespace Microsoft.Build.Shared -{ - internal static class NamedPipeUtil - { - internal static string GetPlatformSpecificPipeName(int? processId = null) - { - if (processId is null) - { - processId = EnvironmentUtilities.CurrentProcessId; - } - - string pipeName = $"MSBuild{processId}"; - - return GetPlatformSpecificPipeName(pipeName); - } - - internal static string GetPlatformSpecificPipeName(string pipeName) - { - return pipeName; - } - - internal static string GetRarNodePipeName(ServerNodeHandshake handshake) - => GetPlatformSpecificPipeName($"MSBuildRarNode-{handshake.ComputeHash()}"); - - internal static string GetRarNodeEndpointPipeName(ServerNodeHandshake handshake) - => GetPlatformSpecificPipeName($"MSBuildRarNodeEndpoint-{handshake.ComputeHash()}"); - } -} diff --git a/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs b/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs index 5bb0bc38d9f..7e2e6ee964a 100644 --- a/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs +++ b/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs @@ -201,7 +201,7 @@ internal void InternalConstruct(string pipeName = null, byte parentPacketVersion _binaryWriter = new BinaryWriter(_packetStream); _parentPacketVersion = parentPacketVersion; - pipeName ??= NamedPipeUtil.GetPlatformSpecificPipeName(); + pipeName ??= $"MSBuild{EnvironmentUtilities.CurrentProcessId}"; SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner; PipeSecurity security = new PipeSecurity(); From 1016f243c2407a99fcf79aa0632a8e978c987d45 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 12:27:13 -0800 Subject: [PATCH 039/136] MSBuildTaskHost: Remove IBuildEngine3.cs and BuildEngineResult.cs OutOfProcTaskHostNode implements IBuildEngine3 in MSBuildTaskHost, but none of the implemented methods are called and IBuildEngine3 isn't available on .NET 3.5. So, IBuildEngine3 can be removed, along with the implementation and BuildEngineResult. --- .../Framework/BuildEngineResult.cs | 64 ------------------ .../Framework/IBuildEngine3.cs | 65 ------------------- .../MSBuild/OutOfProcTaskHostNode.cs | 36 +--------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 2 - 4 files changed, 2 insertions(+), 165 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/BuildEngineResult.cs delete mode 100644 src/MSBuildTaskHost/Framework/IBuildEngine3.cs diff --git a/src/MSBuildTaskHost/Framework/BuildEngineResult.cs b/src/MSBuildTaskHost/Framework/BuildEngineResult.cs deleted file mode 100644 index 4371726a8a3..00000000000 --- a/src/MSBuildTaskHost/Framework/BuildEngineResult.cs +++ /dev/null @@ -1,64 +0,0 @@ -// 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; -using System.Diagnostics.CodeAnalysis; - -#nullable disable - -namespace Microsoft.Build.Framework -{ - /// - /// This structure is used to return the result of the build and the target outputs. - /// - [Serializable] - [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Would require a public API change -- currently we're trying to keep our surface area static.")] - public struct BuildEngineResult - { - /// - /// Did the build pass or fail - /// - private bool buildResult; - - /// - /// Target outputs by project - /// - private List> targetOutputsPerProject; - - /// - /// The constructor takes the result of the build and a list of the target outputs per project - /// - public BuildEngineResult(bool result, List> targetOutputsPerProject) - { - buildResult = result; - this.targetOutputsPerProject = targetOutputsPerProject; - if (this.targetOutputsPerProject == null) - { - this.targetOutputsPerProject = new List>(); - } - } - - /// - /// Did the build pass or fail. True means the build succeeded, False means the build failed. - /// - public readonly bool Result - { - get - { - return buildResult; - } - } - - /// - /// Outputs of the targets per project. - /// - public IList> TargetOutputsPerProject - { - get - { - return targetOutputsPerProject; - } - } - } -} diff --git a/src/MSBuildTaskHost/Framework/IBuildEngine3.cs b/src/MSBuildTaskHost/Framework/IBuildEngine3.cs deleted file mode 100644 index 21c0b1fc22d..00000000000 --- a/src/MSBuildTaskHost/Framework/IBuildEngine3.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections; -using System.Collections.Generic; - -#nullable disable - -namespace Microsoft.Build.Framework -{ - /// - /// This interface extends IBuildEngine to provide a method allowing building - /// project files in parallel. - /// - public interface IBuildEngine3 : IBuildEngine2 - { - /// - /// This method allows tasks to initiate a build on a - /// particular project file. If the build is successful, the outputs - /// (if any) of the specified targets are returned. - /// - /// - /// 1) it is acceptable to pass null for both targetNames and targetOutputs - /// 2) if no targets are specified, the default targets are built - /// - /// - /// The project to build. - /// The targets in the project to build (can be null). - /// An array of hashtables of additional global properties to apply - /// to the child project (array entries can be null). - /// The key and value in the hashtable should both be strings. - /// A list of global properties which should be removed. - /// A tools version recognized by the Engine that will be used during this build (can be null). - /// Should the target outputs be returned in the BuildEngineResult - /// Returns a structure containing the success or failure of the build and the target outputs by project. - BuildEngineResult BuildProjectFilesInParallel( - string[] projectFileNames, - string[] targetNames, - IDictionary[] globalProperties, - IList[] removeGlobalProperties, - string[] toolsVersion, - bool returnTargetOutputs); - - /// - /// Informs the system that this task has a long-running out-of-process component and other work can be done in the - /// build while that work completes. - /// - /// - /// After calling , global process state like environment variables and current working directory - /// can change arbitrarily until returns. As a result, if you are going to depend on any of - /// that state, for instance by opening files by relative path, rather than calling - /// ITaskItem.GetMetadata("FullPath"), you must do so before calling . - /// The recommended pattern is to figure out what all the long-running work is and start it before yielding. - /// - void Yield(); - - /// - /// Waits to reacquire control after yielding. - /// - /// - /// This method must be called to regain control after has been called. - /// - void Reacquire(); - } -} diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs index 495e5a7a011..6f8c634cc63 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; @@ -22,7 +22,7 @@ namespace Microsoft.Build.CommandLine /// /// This class represents an implementation of INode for out-of-proc node for hosting tasks. /// - internal class OutOfProcTaskHostNode : MarshalByRefObject, INodePacketFactory, INodePacketHandler, IBuildEngine3 + internal class OutOfProcTaskHostNode : MarshalByRefObject, INodePacketFactory, INodePacketHandler, IBuildEngine2 { /// /// Keeps a record of all environment variables that, on startup of the task host, have a different @@ -360,38 +360,6 @@ public bool BuildProjectFilesInParallel(string[] projectFileNames, string[] targ #endregion // IBuildEngine2 Implementation (Methods) - #region IBuildEngine3 Implementation - - /// - /// Stub implementation of IBuildEngine3.BuildProjectFilesInParallel. The task host does not support IBuildEngine - /// callbacks for the purposes of building projects, so error. - /// - public BuildEngineResult BuildProjectFilesInParallel(string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IList[] removeGlobalProperties, string[] toolsVersion, bool returnTargetOutputs) - { - LogErrorFromResource("BuildEngineCallbacksInTaskHostUnsupported"); - return new BuildEngineResult(false, null); - } - - /// - /// Stub implementation of IBuildEngine3.Yield. The task host does not support yielding, so just go ahead and silently - /// return, letting the task continue. - /// - public void Yield() - { - return; - } - - /// - /// Stub implementation of IBuildEngine3.Reacquire. The task host does not support yielding, so just go ahead and silently - /// return, letting the task continue. - /// - public void Reacquire() - { - return; - } - - #endregion // IBuildEngine3 Implementation - #region INodePacketFactory Members /// diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 83121b31b41..15d01f52f84 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -63,7 +63,6 @@ - @@ -71,7 +70,6 @@ - From 76b9ef7387d959091a408a68f74c910f60b3f1f6 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 12:34:28 -0800 Subject: [PATCH 040/136] MSBuildTaskHost: Remove IExtendedBuildEventArgs IExtendedBuildEventArgs did not exist in .NET Framework 3.5, so no BuildEventArgs will ever implement IExtendedBuildEventArgs -- certainly not the interface copied into MSBuildTaskHost. So, the test for "e is not IExtendedBuildEventArgs" in OutOfProcTaskHostNode would have incorrectly always returned true. --- src/MSBuildTaskHost/BinaryReaderExtensions.cs | 28 +--------------- src/MSBuildTaskHost/BinaryWriterExtensions.cs | 19 ----------- .../Framework/IExtendedBuildEventArgs.cs | 33 ------------------- .../MSBuild/OutOfProcTaskHostNode.cs | 4 +-- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - 5 files changed, 3 insertions(+), 82 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/IExtendedBuildEventArgs.cs diff --git a/src/MSBuildTaskHost/BinaryReaderExtensions.cs b/src/MSBuildTaskHost/BinaryReaderExtensions.cs index 26ea90fad0f..55f3111717b 100644 --- a/src/MSBuildTaskHost/BinaryReaderExtensions.cs +++ b/src/MSBuildTaskHost/BinaryReaderExtensions.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Runtime.CompilerServices; -using Microsoft.Build.Framework; namespace Microsoft.Build.Shared { @@ -44,6 +42,7 @@ public static int Read7BitEncodedInt(this BinaryReader reader) } while ((b & 0x80) != 0); return count; } + public static DateTime ReadTimestamp(this BinaryReader reader) { long timestampTicks = reader.ReadInt64(); @@ -57,31 +56,6 @@ public static unsafe Guid ReadGuid(this BinaryReader reader) return new Guid(reader.ReadBytes(sizeof(Guid))); } - public static void ReadExtendedBuildEventData(this BinaryReader reader, IExtendedBuildEventArgs data) - { - data.ExtendedType = reader.ReadString(); - data.ExtendedData = reader.ReadOptionalString(); - - bool haveMetadata = reader.ReadBoolean(); - if (haveMetadata) - { - data.ExtendedMetadata = new Dictionary(); - - int count = reader.Read7BitEncodedInt(); - for (int i = 0; i < count; i++) - { - string key = reader.ReadString(); - string? value = reader.ReadOptionalString(); - - data.ExtendedMetadata.Add(key, value); - } - } - else - { - data.ExtendedMetadata = null; - } - } - public static Dictionary ReadDurationDictionary(this BinaryReader reader) { int count = reader.Read7BitEncodedInt(); diff --git a/src/MSBuildTaskHost/BinaryWriterExtensions.cs b/src/MSBuildTaskHost/BinaryWriterExtensions.cs index 2eb5504e221..cda207f3932 100644 --- a/src/MSBuildTaskHost/BinaryWriterExtensions.cs +++ b/src/MSBuildTaskHost/BinaryWriterExtensions.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Runtime.CompilerServices; -using Microsoft.Build.Framework; namespace Microsoft.Build.Shared { @@ -70,23 +68,6 @@ public static void WriteGuid(this BinaryWriter writer, Guid value) } } - public static void WriteExtendedBuildEventData(this BinaryWriter writer, IExtendedBuildEventArgs data) - { - writer.Write(data.ExtendedType); - writer.WriteOptionalString(data.ExtendedData); - - writer.Write(data.ExtendedMetadata != null); - if (data.ExtendedMetadata != null) - { - writer.Write7BitEncodedInt(data.ExtendedMetadata.Count); - foreach (KeyValuePair kvp in data.ExtendedMetadata) - { - writer.Write(kvp.Key); - writer.WriteOptionalString(kvp.Value); - } - } - } - public static void WriteDurationsDictionary(this BinaryWriter writer, Dictionary durations) { writer.Write7BitEncodedInt(durations.Count); diff --git a/src/MSBuildTaskHost/Framework/IExtendedBuildEventArgs.cs b/src/MSBuildTaskHost/Framework/IExtendedBuildEventArgs.cs deleted file mode 100644 index 0c73ddb914f..00000000000 --- a/src/MSBuildTaskHost/Framework/IExtendedBuildEventArgs.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; - -namespace Microsoft.Build.Framework; - -/// -/// Interface for Extended EventArgs to allow enriching particular events with extended data. -/// Deriving from EventArgs will be deprecated soon and using Extended EventArgs is recommended for custom Event Args. -/// -public interface IExtendedBuildEventArgs -{ - /// - /// Unique string identifying type of extended data so receiver side knows how to interpret, deserialize and handle . - /// - string ExtendedType { get; set; } - - /// - /// Metadata of . - /// Example usage: - /// - data which needed in custom code to properly routing this message without interpreting/deserializing . - /// - simple extended data can be transferred in form of dictionary key-value per one extended property. - /// - Dictionary? ExtendedMetadata { get; set; } - - /// - /// Transparent data as string. - /// Custom code is responsible to serialize and deserialize this string to structured data - if needed. - /// Custom code can use any serialization they deem safe - e.g. json for textual data, base64 for binary data... - /// - string? ExtendedData { get; set; } -} diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs index 6f8c634cc63..2a760dffe94 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; @@ -975,7 +975,7 @@ private void SendBuildEvent(BuildEventArgs e) #pragma warning disable SYSLIB0050 // Types which are not serializable and are not IExtendedBuildEventArgs as // those always implement custom serialization by WriteToStream and CreateFromStream. - if (!e.GetType().GetTypeInfo().IsSerializable && e is not IExtendedBuildEventArgs) + if (!e.GetType().GetTypeInfo().IsSerializable) #pragma warning disable SYSLIB0050 { // log a warning and bail. This will end up re-calling SendBuildEvent, but we know for a fact diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 15d01f52f84..80ba7707f1e 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -71,7 +71,6 @@ - From e99fe71c004a2097785f1cb6f9bf0b4b4033b9c3 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 12:39:20 -0800 Subject: [PATCH 041/136] MSBuildTaskHost: Remove SerializableMetadata.cs SerializableMetadata is only returned by TaskParameterTaskItem.BackingMetadata, but that property is never accessed in MSBuildTaskHost. Once the property is removed, SerializableMetadata.cs can be completely removed. --- .../Framework/SerializableMetadata.cs | 62 ------------------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - src/MSBuildTaskHost/TaskParameter.cs | 4 -- 3 files changed, 67 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/SerializableMetadata.cs diff --git a/src/MSBuildTaskHost/Framework/SerializableMetadata.cs b/src/MSBuildTaskHost/Framework/SerializableMetadata.cs deleted file mode 100644 index 857a4c15fb6..00000000000 --- a/src/MSBuildTaskHost/Framework/SerializableMetadata.cs +++ /dev/null @@ -1,62 +0,0 @@ -// 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; -using System.Collections.Immutable; -using System.Linq; -using System.Runtime.Serialization; - -namespace Microsoft.Build.Framework -{ - /// - /// Serializable wrapper for immutable metadata dictionaries. - /// Safe to use across AppDomains. - /// - [Serializable] - internal readonly struct SerializableMetadata : ISerializable - { - /// - /// Initializes a new instance of the struct. - /// - /// The metadata dictionary to set. - /// - /// Calling this constructor implies that the instance value is usable. As such, a null input will be converted - /// to an empty instance. - /// - public SerializableMetadata(ImmutableDictionary dictionary) => - Dictionary = dictionary ?? ImmutableDictionaryExtensions.EmptyMetadata; - - public SerializableMetadata(SerializationInfo info, StreamingContext context) - { - bool hasValue = info.GetBoolean("hasValue"); - if (hasValue) - { - object entries = info.GetValue("value", typeof(KeyValuePair[]))!; - Dictionary = ImmutableDictionaryExtensions.EmptyMetadata.AddRange((KeyValuePair[])entries); - } - } - - /// - /// Gets the backing metadata dictionary. - /// - internal ImmutableDictionary? Dictionary { get; } - - /// - /// Gets a value indicating whether the wrapped dictionary represents a usable instance. - /// - /// - /// Since SerializableMetadata is a struct, this allows the default value to represent an unusable instance. - /// - internal bool HasValue => Dictionary != null; - - public void GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue("hasValue", HasValue); - if (HasValue) - { - info.AddValue("value", Dictionary!.ToArray()); - } - } - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 80ba7707f1e..ca3d4c9bbcb 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -84,7 +84,6 @@ - diff --git a/src/MSBuildTaskHost/TaskParameter.cs b/src/MSBuildTaskHost/TaskParameter.cs index 5bf1375f9d9..2f07bfb5041 100644 --- a/src/MSBuildTaskHost/TaskParameter.cs +++ b/src/MSBuildTaskHost/TaskParameter.cs @@ -665,10 +665,6 @@ string ITaskItem2.EvaluatedIncludeEscaped } } - public SerializableMetadata BackingMetadata => default; - - public bool HasCustomMetadata => _customEscapedMetadata?.Count > 0; - /// /// Allows the values of metadata on the item to be queried. /// From a679727e72587785fbb735db69169b91a43f18b8 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 12:44:36 -0800 Subject: [PATCH 042/136] MSBuildTaskHost: Remove ImmutableDictionary ImmutableDictionaryExtensions.EmptyMetadata is not referenced within MSBuildTaskHost. So, ImmutableDictionaryExtensions.cs can be removed. That leaves ImmutableDictionary without any references outside of unit tests. Since this was an MSBuildTaskHost-specific implementation, it can be removed along with the unit tests. --- .../Microsoft.Build.Engine.UnitTests.csproj | 3 - .../ImmutableDictionaryExtensions.cs | 19 -- .../Immutable/ImmutableDictionary.cs | 284 ------------------ src/MSBuildTaskHost/MSBuildTaskHost.csproj | 2 - .../UnitTests/ImmutableDictionary_Tests.cs | 260 ---------------- 5 files changed, 568 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/ImmutableDictionaryExtensions.cs delete mode 100644 src/MSBuildTaskHost/Immutable/ImmutableDictionary.cs delete mode 100644 src/Shared/UnitTests/ImmutableDictionary_Tests.cs diff --git a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj index 25ce6ea2727..0d284cca584 100644 --- a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj +++ b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj @@ -77,9 +77,6 @@ Collections\CopyOnWriteDictionary_Tests.cs - - Collections\ImmutableDictionary_Tests.cs - diff --git a/src/MSBuildTaskHost/Framework/ImmutableDictionaryExtensions.cs b/src/MSBuildTaskHost/Framework/ImmutableDictionaryExtensions.cs deleted file mode 100644 index 28949998580..00000000000 --- a/src/MSBuildTaskHost/Framework/ImmutableDictionaryExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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; -using System.Collections.Immutable; -using Microsoft.Build.Collections; - -namespace Microsoft.Build.Framework -{ - internal static class ImmutableDictionaryExtensions - { - /// - /// An empty dictionary pre-configured with a comparer for metadata dictionaries. - /// - public static readonly ImmutableDictionary EmptyMetadata = - ImmutableDictionary.Empty.WithComparers(MSBuildNameIgnoreCaseComparer.Default); - } -} diff --git a/src/MSBuildTaskHost/Immutable/ImmutableDictionary.cs b/src/MSBuildTaskHost/Immutable/ImmutableDictionary.cs deleted file mode 100644 index 025bb7c5536..00000000000 --- a/src/MSBuildTaskHost/Immutable/ImmutableDictionary.cs +++ /dev/null @@ -1,284 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Linq; - -#nullable disable - -namespace System.Collections.Immutable -{ - internal static class ImmutableExtensions - { - public static ImmutableDictionary ToImmutableDictionary(this IDictionary dictionary) - { - return new ImmutableDictionary(dictionary); - } - } - - internal static class ImmutableDictionary - { - internal static ImmutableDictionary Create(IEqualityComparer comparer) - { - return new ImmutableDictionary(comparer); - } - } - - /// - /// Inefficient ImmutableDictionary implementation: keep a mutable dictionary and wrap all operations. - /// - /// - /// - internal sealed class ImmutableDictionary : IDictionary, IDictionary - { - /// - /// The underlying dictionary. - /// - private Dictionary _backing; - - #region Read-only Operations - - public ICollection Keys => _backing.Keys; - public ICollection Values => _backing.Values; - - ICollection IDictionary.Keys => _backing.Keys; - ICollection IDictionary.Values => _backing.Values; - - public int Count => _backing.Count; - - public V this[K key] => _backing[key]; - - public bool IsReadOnly => true; - public bool IsFixedSize => true; - public bool IsSynchronized => true; - - public object SyncRoot => this; - - public bool TryGetValue(K key, out V value) - { - return _backing.TryGetValue(key, out value); - } - - public bool Contains(KeyValuePair item) - { - return _backing.Contains(item); - } - - bool IDictionary.Contains(object key) - { - return ((IDictionary)_backing).Contains(key); - } - - public bool ContainsKey(K key) - { - return _backing.ContainsKey(key); - } - - public IEnumerator> GetEnumerator() - { - return _backing.GetEnumerator(); - } - - IDictionaryEnumerator IDictionary.GetEnumerator() - { - return _backing.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return _backing.GetEnumerator(); - } - - void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) - { - CheckCopyToArguments(array, arrayIndex); - foreach (var item in this) - { - array[arrayIndex++] = item; - } - } - - void ICollection.CopyTo(Array array, int arrayIndex) - { - CheckCopyToArguments(array, arrayIndex); - foreach (var item in this) - { - array.SetValue(new DictionaryEntry(item.Key, item.Value), arrayIndex++); - } - } - - private void CheckCopyToArguments(Array array, int arrayIndex) - { - if (array == null) - { - throw new ArgumentNullException(nameof(array)); - } - if (arrayIndex < 0) - { - throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - } - if (arrayIndex + Count > array.Length) - { - throw new ArgumentException(nameof(arrayIndex)); - } - } - - #endregion - - #region Write Operations - - internal ImmutableDictionary SetItem(K key, V value) - { - if (TryGetValue(key, out V existingValue) && Object.Equals(existingValue, value)) - { - return this; - } - - var clone = new ImmutableDictionary(_backing); - clone._backing[key] = value; - - return clone; - } - - internal ImmutableDictionary SetItems(IEnumerable> items) - { - var clone = new ImmutableDictionary(_backing); - foreach (KeyValuePair item in items) - { - clone._backing[item.Key] = item.Value; - } - - return clone; - } - - internal ImmutableDictionary Remove(K key) - { - if (!ContainsKey(key)) - { - return this; - } - - var clone = new ImmutableDictionary(_backing); - clone._backing.Remove(key); - - return clone; - } - - internal ImmutableDictionary Clear() - { - return new ImmutableDictionary(_backing.Comparer); - } - - internal ImmutableDictionary() - { - _backing = new Dictionary(); - } - - internal ImmutableDictionary(IEqualityComparer comparer) - { - _backing = new Dictionary(comparer); - } - - internal ImmutableDictionary(IDictionary source, IEqualityComparer keyComparer = null) - { - if (source is ImmutableDictionary imm) - { - _backing = new Dictionary(imm._backing, keyComparer ?? imm._backing.Comparer); - } - else - { - _backing = new Dictionary(source, keyComparer); - } - } - - internal static ImmutableDictionary Empty - { - get - { - return new ImmutableDictionary(); - } - } - - public IEqualityComparer KeyComparer { get => _backing.Comparer; internal set => throw new NotSupportedException(); } - - internal KeyValuePair[] ToArray() - { - return _backing.ToArray(); - } - - internal ImmutableDictionary AddRange(KeyValuePair[] v) - { - var n = new Dictionary(_backing, _backing.Comparer); - - foreach (var item in v) - { - n.Add(item.Key, item.Value); - } - - return new ImmutableDictionary(n); - } - - internal ImmutableDictionary WithComparers(IEqualityComparer keyComparer) - { - return new ImmutableDictionary(_backing, keyComparer); - } - - #endregion - - #region Unsupported Operations - - object IDictionary.this[object key] - { - get { return _backing[(K)key]; } - set { throw new NotSupportedException(); } - } - - void IDictionary.Add(object key, object value) - { - throw new NotSupportedException(); - } - - void IDictionary.Remove(object key) - { - throw new NotSupportedException(); - } - - void IDictionary.Clear() - { - throw new NotSupportedException(); - } - - V IDictionary.this[K key] - { - get { return _backing[key]; } - set { throw new NotSupportedException(); } - } - - void IDictionary.Add(K key, V value) - { - throw new NotSupportedException(); - } - - bool IDictionary.Remove(K key) - { - throw new NotSupportedException(); - } - - void ICollection>.Add(KeyValuePair item) - { - throw new NotSupportedException(); - } - - void ICollection>.Clear() - { - throw new NotSupportedException(); - } - - bool ICollection>.Remove(KeyValuePair item) - { - throw new NotSupportedException(); - } - - #endregion - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index ca3d4c9bbcb..71e4caf73a7 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -71,7 +71,6 @@ - @@ -89,7 +88,6 @@ - diff --git a/src/Shared/UnitTests/ImmutableDictionary_Tests.cs b/src/Shared/UnitTests/ImmutableDictionary_Tests.cs deleted file mode 100644 index e0ecf9bc051..00000000000 --- a/src/Shared/UnitTests/ImmutableDictionary_Tests.cs +++ /dev/null @@ -1,260 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// We don't automatically run these tests against the BCL implementation of ImmutableDictionary as it would require dual-compiling -// this file. When making changes to this test, though, it is recommended to run them manually by uncommenting the following line. -// This helps ensure that the real thing has the same behavior that we expect in our implementation. -// #define _TEST_BCL_IMMUTABLE_DICTIONARY - -extern alias MSBuildTaskHost; - -using System; -using System.Collections; -using System.Collections.Generic; - -using Shouldly; -using Xunit; - -#if _TEST_BCL_IMMUTABLE_DICTIONARY -using ImmutableDictionary = System.Collections.Immutable.ImmutableDictionary; -#else -using ImmutableDictionary = MSBuildTaskHost::System.Collections.Immutable.ImmutableDictionary; -#endif - -#nullable disable - -namespace Microsoft.Build.UnitTests -{ - public class ImmutableDictionary_Tests - { - private readonly ImmutableDictionary _emptyDict = ImmutableDictionary.Empty; - - [Fact] - public void SimplesBoolPropertiesReturnExpectedValues() - { - ((IDictionary)_emptyDict).IsFixedSize.ShouldBeTrue(); - ((IDictionary)_emptyDict).IsReadOnly.ShouldBeTrue(); - ((IDictionary)_emptyDict).IsSynchronized.ShouldBeTrue(); - } - - [Fact] - public void CountReturnsExpectedValue() - { - _emptyDict.Count.ShouldBe(0); - ImmutableDictionary dict = _emptyDict.SetItem("Key1", "Value1"); - dict.Count.ShouldBe(1); - dict = dict.SetItem("Key2", "Value2"); - dict.Count.ShouldBe(2); - dict = dict.Clear(); - dict.Count.ShouldBe(0); - } - - [Fact] - public void IndexerReturnsPreviouslySetItem() - { - ImmutableDictionary dict = _emptyDict.SetItem("Key1", "Value1"); - dict["Key1"].ShouldBe("Value1"); - ((IDictionary)dict)["Key1"].ShouldBe("Value1"); - ((IDictionary)dict)["Key1"].ShouldBe("Value1"); - } - - [Fact] - public void IndexerThrowsForItemNotPreviouslySet() - { - ImmutableDictionary dict = _emptyDict.SetItem("Key1", "Value1"); - Should.Throw(() => _ = dict["Key2"]); - Should.Throw(() => _ = ((IDictionary)dict)["Key2"]); - Should.Throw(() => _ = ((IDictionary)dict)["Key2"]); - } - - [Fact] - public void ContainsReturnsTrueForPeviouslySetItem() - { - ImmutableDictionary dict = _emptyDict.SetItem("Key1", "Value1"); - dict.Contains(new KeyValuePair("Key1", "Value1")).ShouldBeTrue(); - dict.ContainsKey("Key1").ShouldBeTrue(); - ((IDictionary)dict).Contains("Key1").ShouldBeTrue(); - } - - [Fact] - public void ContainsReturnsFalseForItemNotPeviouslySet() - { - ImmutableDictionary dict = _emptyDict.SetItem("Key1", "Value1"); - dict.Contains(new KeyValuePair("Key2", "Value2")).ShouldBeFalse(); - dict.ContainsKey("Key2").ShouldBeFalse(); - ((IDictionary)dict).Contains("Key2").ShouldBeFalse(); - } - - [Fact] - public void EnumeratorEnumeratesItems() - { - ImmutableDictionary dict = _emptyDict.SetItem("Key1", "Value1"); - - IEnumerator> enumerator1 = dict.GetEnumerator(); - int i = 0; - while (enumerator1.MoveNext()) - { - i++; - enumerator1.Current.Key.ShouldBe("Key1"); - enumerator1.Current.Value.ShouldBe("Value1"); - } - i.ShouldBe(dict.Count); - - IDictionaryEnumerator enumerator2 = ((IDictionary)dict).GetEnumerator(); - i = 0; - while (enumerator2.MoveNext()) - { - i++; - enumerator2.Key.ShouldBe("Key1"); - enumerator2.Value.ShouldBe("Value1"); - } - i.ShouldBe(dict.Count); - - IEnumerator enumerator3 = ((IEnumerable)dict).GetEnumerator(); - i = 0; - while (enumerator3.MoveNext()) - { - i++; - KeyValuePair entry = (KeyValuePair)enumerator3.Current; - entry.Key.ShouldBe("Key1"); - entry.Value.ShouldBe("Value1"); - } - i.ShouldBe(dict.Count); - } - - [Fact] - public void CopyToCopiesItemsToArray() - { - ImmutableDictionary dict = _emptyDict.SetItem("Key1", "Value1"); - - KeyValuePair[] array1 = new KeyValuePair[1]; - ((ICollection>)dict).CopyTo(array1, 0); - array1[0].Key.ShouldBe("Key1"); - array1[0].Value.ShouldBe("Value1"); - - array1 = new KeyValuePair[2]; - ((ICollection>)dict).CopyTo(array1, 1); - array1[1].Key.ShouldBe("Key1"); - array1[1].Value.ShouldBe("Value1"); - - DictionaryEntry[] array2 = new DictionaryEntry[1]; - ((ICollection)dict).CopyTo(array2, 0); - array2[0].Key.ShouldBe("Key1"); - array2[0].Value.ShouldBe("Value1"); - - array2 = new DictionaryEntry[2]; - ((ICollection)dict).CopyTo(array2, 1); - array2[1].Key.ShouldBe("Key1"); - array2[1].Value.ShouldBe("Value1"); - } - - [Fact] - public void CopyToThrowsOnInvalidInput() - { - ImmutableDictionary dict = _emptyDict.SetItem("Key1", "Value1"); - - Should.Throw(() => ((ICollection>)dict).CopyTo(null, 0)); - Should.Throw(() => ((ICollection)dict).CopyTo(null, 0)); - - KeyValuePair[] array1 = new KeyValuePair[1]; - DictionaryEntry[] array2 = new DictionaryEntry[1]; - Should.Throw(() => ((ICollection>)dict).CopyTo(array1, -1)); - Should.Throw(() => ((ICollection)dict).CopyTo(array1, -1)); - - Should.Throw(() => ((ICollection>)dict).CopyTo(array1, 1)); - Should.Throw(() => ((ICollection)dict).CopyTo(array1, 1)); - } - - [Fact] - public void KeysReturnsKeys() - { - ImmutableDictionary dict = _emptyDict.SetItem("Key1", "Value1"); - - ICollection keys1 = ((IDictionary)dict).Keys; - keys1.ShouldBe(new string[] { "Key1" }); - - ICollection keys2 = ((IDictionary)dict).Keys; - keys2.ShouldBe(new string[] { "Key1" }); - } - - [Fact] - public void ValuesReturnsValues() - { - ImmutableDictionary dict = _emptyDict.SetItem("Key1", "Value1"); - - ICollection values1 = ((IDictionary)dict).Values; - values1.ShouldBe(new string[] { "Value1" }); - - ICollection values2 = ((IDictionary)dict).Values; - values2.ShouldBe(new string[] { "Value1" }); - } - - [Fact] - public void SetItemReturnsNewInstanceAfterAdding() - { - ImmutableDictionary dict = _emptyDict.SetItem("Key1", "Value1"); - dict.ShouldNotBeSameAs(_emptyDict); - } - - [Fact] - public void SetItemReturnsNewInstanceAfterUpdating() - { - ImmutableDictionary dict1 = _emptyDict.SetItem("Key1", "Value1"); - ImmutableDictionary dict2 = dict1.SetItem("Key1", "Value2"); - dict2.ShouldNotBeSameAs(dict1); - } - - [Fact] - public void SetItemReturnsSameInstanceWhenItemAlreadyExists() - { - ImmutableDictionary dict1 = _emptyDict.SetItem("Key1", "Value1"); - ImmutableDictionary dict2 = dict1.SetItem("Key1", "Value1"); - dict2.ShouldBeSameAs(dict1); - } - - [Fact] - public void RemoveReturnsNewInstanceAfterDeleting() - { - ImmutableDictionary dict1 = _emptyDict.SetItem("Key1", "Value1"); - ImmutableDictionary dict2 = dict1.Remove("Key1"); - dict2.ShouldNotBeSameAs(dict1); - } - - [Fact] - public void RemoveReturnsSameInstanceWhenItemDoesNotExist() - { - ImmutableDictionary dict1 = _emptyDict.SetItem("Key1", "Value1"); - ImmutableDictionary dict2 = dict1.Remove("Key2"); - dict2.ShouldBeSameAs(dict1); - } - - [Fact] - public void ClearReturnsNewInstance() - { - ImmutableDictionary dict1 = _emptyDict.SetItem("Key1", "Value1"); - ImmutableDictionary dict2 = dict1.Clear(); - dict2.ShouldNotBeSameAs(dict1); - } - - [Fact] - public void WithComparersCreatesNewInstanceWithSpecifiedKeyComparer() - { - ImmutableDictionary dict1 = _emptyDict.SetItem("Key1", "Value1"); - ImmutableDictionary dict2 = dict1.WithComparers(StringComparer.OrdinalIgnoreCase); - dict2["KEY1"].ShouldBe("Value1"); - } - - [Fact] - public void AddRangeAddsAllItems() - { - ImmutableDictionary dict = _emptyDict.AddRange(new KeyValuePair[] - { - new KeyValuePair("Key1", "Value1"), - new KeyValuePair("Key2", "Value2") - }); - dict.Count.ShouldBe(2); - dict["Key1"].ShouldBe("Value1"); - dict["Key2"].ShouldBe("Value2"); - } - } -} From 3be335838a2d5bd10b16ebf072b9d0b281858d68 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 12:51:32 -0800 Subject: [PATCH 043/136] MSBuildTaskHost: Remove uncalled members from EnvironmentUtilities.cs --- src/MSBuildTaskHost/EnvironmentUtilities.cs | 27 --------------------- 1 file changed, 27 deletions(-) diff --git a/src/MSBuildTaskHost/EnvironmentUtilities.cs b/src/MSBuildTaskHost/EnvironmentUtilities.cs index 8b011bdca01..1f70e51f3ff 100644 --- a/src/MSBuildTaskHost/EnvironmentUtilities.cs +++ b/src/MSBuildTaskHost/EnvironmentUtilities.cs @@ -3,19 +3,15 @@ #nullable enable -using System; using System.Diagnostics; -using System.Runtime.InteropServices; using System.Threading; namespace Microsoft.Build.Shared { internal static partial class EnvironmentUtilities { - private static volatile int s_processId; private static volatile string? s_processPath; - private static volatile string? s_processName; /// Gets the unique identifier for the current process. public static int CurrentProcessId @@ -63,28 +59,5 @@ public static string? ProcessPath return (processPath?.Length != 0) ? processPath : null; } } - - public static string ProcessName - { - get - { - string? processName = s_processName; - if (processName == null) - { - using Process currentProcess = Process.GetCurrentProcess(); - Interlocked.CompareExchange(ref s_processName, currentProcess.ProcessName, null); - processName = s_processName; - } - - return processName; - } - } - - public static bool IsWellKnownEnvironmentDerivedProperty(string propertyName) - { - return propertyName.StartsWith("MSBUILD", StringComparison.OrdinalIgnoreCase) || - propertyName.StartsWith("COMPLUS_", StringComparison.OrdinalIgnoreCase) || - propertyName.StartsWith("DOTNET_", StringComparison.OrdinalIgnoreCase); - } } } From 98b305fa394cbc66e1c79a8cfa3c7428ccf79e31 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 12:53:30 -0800 Subject: [PATCH 044/136] MSBuildTaskHost: Remove uncalled members from CollectionHelpers.cs --- src/MSBuildTaskHost/CollectionHelpers.cs | 26 ++---------------------- 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/src/MSBuildTaskHost/CollectionHelpers.cs b/src/MSBuildTaskHost/CollectionHelpers.cs index f949db5a683..6d8ba5188c7 100644 --- a/src/MSBuildTaskHost/CollectionHelpers.cs +++ b/src/MSBuildTaskHost/CollectionHelpers.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; #nullable disable @@ -14,36 +13,15 @@ namespace Microsoft.Build.Shared /// internal static class CollectionHelpers { - /// - /// Returns a new list containing the input list - /// contents, except for nulls - /// - /// Type of list elements - internal static List RemoveNulls(List inputs) - { - List inputsWithoutNulls = new List(inputs.Count); - - foreach (T entry in inputs) - { - if (entry != null) - { - inputsWithoutNulls.Add(entry); - } - } - - // Avoid possibly having two identical lists floating around - return (inputsWithoutNulls.Count == inputs.Count) ? inputs : inputsWithoutNulls; - } - /// /// Extension method -- combines a TryGet with a check to see that the value is equal. /// - internal static bool ContainsValueAndIsEqual(this Dictionary dictionary, string key, string value, StringComparison comparer) + internal static bool ContainsValueAndIsEqual(this Dictionary dictionary, string key, string value, StringComparison comparison) { string valueFromDictionary; if (dictionary.TryGetValue(key, out valueFromDictionary)) { - return String.Equals(value, valueFromDictionary, comparer); + return String.Equals(value, valueFromDictionary, comparison); } return false; From a6aa438454bb1ef0bde9eba845245b0cf107cad1 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 12:56:22 -0800 Subject: [PATCH 045/136] MSBuildTaskHost: Remove Constants.cs None of the code in this file is called within MSBuildTaskHost. --- src/MSBuildTaskHost/Constants.cs | 262 --------------------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - 2 files changed, 263 deletions(-) delete mode 100644 src/MSBuildTaskHost/Constants.cs diff --git a/src/MSBuildTaskHost/Constants.cs b/src/MSBuildTaskHost/Constants.cs deleted file mode 100644 index 7a365f065c8..00000000000 --- a/src/MSBuildTaskHost/Constants.cs +++ /dev/null @@ -1,262 +0,0 @@ -// 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.IO; - -#nullable disable - -namespace Microsoft.Build.Shared -{ - /// - /// Constants that we want to be shareable across all our assemblies. - /// - internal static class MSBuildConstants - { - /// - /// The name of the property that indicates the tools path - /// - internal const string ToolsPath = "MSBuildToolsPath"; - - /// - /// Name of the property that indicates the X64 tools path - /// - internal const string ToolsPath64 = "MSBuildToolsPath64"; - - /// - /// Name of the property that indicates the root of the SDKs folder - /// - internal const string SdksPath = "MSBuildSDKsPath"; - - /// - /// Name of the property that indicates that all warnings should be treated as errors. - /// - internal const string TreatWarningsAsErrors = "MSBuildTreatWarningsAsErrors"; - - /// - /// Name of the property that indicates a list of warnings to treat as errors. - /// - internal const string WarningsAsErrors = "MSBuildWarningsAsErrors"; - - /// - /// Name of the property that indicates a list of warnings to not treat as errors. - /// - internal const string WarningsNotAsErrors = "MSBuildWarningsNotAsErrors"; - - /// - /// Name of the property that indicates the list of warnings to treat as messages. - /// - internal const string WarningsAsMessages = "MSBuildWarningsAsMessages"; - - /// - /// The name of the environment variable that users can specify to override where NuGet assemblies are loaded from in the NuGetSdkResolver. - /// - internal const string NuGetAssemblyPathEnvironmentVariableName = "MSBUILD_NUGET_PATH"; - - /// - /// The name of the target to run when a user specifies the /restore command-line argument. - /// - internal const string RestoreTargetName = "Restore"; - /// - /// The most current Visual Studio Version known to this version of MSBuild. - /// - internal const string CurrentVisualStudioVersion = "18.0"; - - /// - /// The most current ToolsVersion known to this version of MSBuild. - /// - internal const string CurrentToolsVersion = "Current"; - - internal const string MSBuildDummyGlobalPropertyHeader = "MSBuildProjectInstance"; - - /// - /// A property set during an implicit restore (/restore) or explicit restore (/t:restore) to ensure that the evaluations are not re-used during build - /// - internal const string MSBuildRestoreSessionId = nameof(MSBuildRestoreSessionId); - - /// - /// A property set during an implicit restore (/restore) or explicit restore (/t:restore) to indicate that a restore is executing. - /// - internal const string MSBuildIsRestoring = nameof(MSBuildIsRestoring); - - - /// - /// The most current VSGeneralAssemblyVersion known to this version of MSBuild. - /// - internal const string CurrentAssemblyVersion = "15.1.0.0"; - - /// - /// Current version of this MSBuild Engine assembly in the form, e.g, "12.0" - /// - internal const string CurrentProductVersion = "18.0"; - - /// - /// Symbol used in ProjectReferenceTarget items to represent default targets - /// - internal const string DefaultTargetsMarker = ".default"; - - /// - /// Framework version against which our test projects should be built. - /// - /// - /// The targeting pack for this version of .NET Framework must be installed - /// on any machine that wants to run tests successfully, so this can be - /// periodically updated. - /// - internal const string StandardTestTargetFrameworkVersion = "v4.8"; - - /// - /// Symbol used in ProjectReferenceTarget items to represent targets specified on the ProjectReference item - /// with fallback to default targets if the ProjectReference item has no targets specified. - /// - internal const string ProjectReferenceTargetsOrDefaultTargetsMarker = ".projectReferenceTargetsOrDefaultTargets"; - - // One-time allocations to avoid implicit allocations for Split(), Trim(). - internal static readonly char[] SemicolonChar = [';']; - internal static readonly char[] SpaceChar = [' ']; - internal static readonly char[] SingleQuoteChar = ['\'']; - internal static readonly char[] EqualsChar = ['=']; - internal static readonly char[] ColonChar = [':']; - internal static readonly char[] BackslashChar = ['\\']; - internal static readonly char[] NewlineChar = ['\n']; - internal static readonly char[] CrLf = ['\r', '\n']; - internal static readonly char[] ForwardSlash = ['/']; - internal static readonly char[] ForwardSlashBackslash = ['/', '\\']; - internal static readonly char[] WildcardChars = ['*', '?']; - internal static readonly string[] CharactersForExpansion = ["*", "?", "$(", "@(", "%"]; - internal static readonly char[] CommaChar = [',']; - internal static readonly char[] HyphenChar = ['-']; - internal static readonly char[] DirectorySeparatorChar = [Path.DirectorySeparatorChar]; - internal static readonly char[] DotChar = ['.']; - internal static readonly string[] EnvironmentNewLine = [Environment.NewLine]; - internal static readonly char[] PipeChar = ['|']; - internal static readonly char[] PathSeparatorChar = [Path.PathSeparator]; - internal static readonly char[] InvalidPathChars = Path.GetInvalidPathChars(); - } - - internal static class PropertyNames - { - /// - /// Specifies whether the current evaluation / build is happening during a graph build - /// - internal const string IsGraphBuild = nameof(IsGraphBuild); - - internal const string InnerBuildProperty = nameof(InnerBuildProperty); - internal const string InnerBuildPropertyValues = nameof(InnerBuildPropertyValues); - internal const string TargetFrameworks = nameof(TargetFrameworks); - internal const string TargetFramework = nameof(TargetFramework); - internal const string UsingMicrosoftNETSdk = nameof(UsingMicrosoftNETSdk); - - /// - /// When true, `SkipNonexistentProjects=Build` becomes the default setting of MSBuild tasks. - /// - internal const string BuildNonexistentProjectsByDefault = "_" + nameof(BuildNonexistentProjectsByDefault); - } - - // TODO: Remove these when VS gets updated to setup project cache plugins. - internal static class DesignTimeProperties - { - internal const string DesignTimeBuild = nameof(DesignTimeBuild); - internal const string BuildingProject = nameof(BuildingProject); - } - - internal static class ItemTypeNames - { - /// - /// References to other msbuild projects - /// - internal const string ProjectReference = nameof(ProjectReference); - - /// - /// Statically specifies what targets a project calls on its references - /// - internal const string ProjectReferenceTargets = nameof(ProjectReferenceTargets); - - internal const string GraphIsolationExemptReference = nameof(GraphIsolationExemptReference); - - /// - /// Declares a project cache plugin and its configuration. - /// - internal const string ProjectCachePlugin = nameof(ProjectCachePlugin); - - /// - /// Embed specified files in the binary log - /// - internal const string EmbedInBinlog = nameof(EmbedInBinlog); - } - - /// - /// Constants naming well-known item metadata. - /// - internal static class ItemMetadataNames - { - internal const string fusionName = "FusionName"; - internal const string hintPath = "HintPath"; - internal const string assemblyFolderKey = "AssemblyFolderKey"; - internal const string alias = "Alias"; - internal const string aliases = "Aliases"; - internal const string parentFile = "ParentFile"; - internal const string privateMetadata = "Private"; - internal const string copyLocal = "CopyLocal"; - internal const string isRedistRoot = "IsRedistRoot"; - internal const string redist = "Redist"; - internal const string resolvedFrom = "ResolvedFrom"; - internal const string destinationSubDirectory = "DestinationSubDirectory"; - internal const string destinationSubPath = "DestinationSubPath"; - internal const string specificVersion = "SpecificVersion"; - internal const string link = "Link"; - internal const string subType = "SubType"; - internal const string executableExtension = "ExecutableExtension"; - internal const string embedInteropTypes = "EmbedInteropTypes"; - internal const string frameworkReferenceName = "FrameworkReferenceName"; - internal const string assemblyName = "AssemblyName"; - internal const string assemblyVersion = "AssemblyVersion"; - internal const string publicKeyToken = "PublicKeyToken"; - internal const string culture = "Culture"; - internal const string withCulture = "WithCulture"; - internal const string copyToOutputDirectory = "CopyToOutputDirectory"; - internal const string copyAlways = "Always"; - internal const string managed = "Managed"; - - /// - /// The output path for a given item. - /// - internal const string targetPath = "TargetPath"; - internal const string dependentUpon = "DependentUpon"; - internal const string msbuildSourceProjectFile = "MSBuildSourceProjectFile"; - internal const string msbuildSourceTargetName = "MSBuildSourceTargetName"; - internal const string isPrimary = "IsPrimary"; - internal const string targetFramework = "RequiredTargetFramework"; - internal const string frameworkDirectory = "FrameworkDirectory"; - internal const string version = "Version"; - internal const string imageRuntime = "ImageRuntime"; - internal const string winMDFile = "WinMDFile"; - internal const string winMDFileType = "WinMDFileType"; - internal const string msbuildReferenceSourceTarget = "ReferenceSourceTarget"; - internal const string msbuildReferenceGrouping = "ReferenceGrouping"; - internal const string msbuildReferenceGroupingDisplayName = "ReferenceGroupingDisplayName"; - internal const string msbuildReferenceFromSDK = "ReferenceFromSDK"; - internal const string winmdImplmentationFile = "Implementation"; - internal const string projectReferenceOriginalItemSpec = "ProjectReferenceOriginalItemSpec"; - internal const string IgnoreVersionForFrameworkReference = "IgnoreVersionForFrameworkReference"; - internal const string frameworkFile = "FrameworkFile"; - internal const string ProjectReferenceTargetsMetadataName = "Targets"; - internal const string PropertiesMetadataName = "Properties"; - internal const string UndefinePropertiesMetadataName = "UndefineProperties"; - internal const string AdditionalPropertiesMetadataName = "AdditionalProperties"; - internal const string ProjectConfigurationDescription = "ProjectConfigurationDescription"; - } - - /// - /// Constants naming well-known items. - /// - internal static class ItemNames - { - internal const string Compile = "Compile"; - internal const string Content = "Content"; - internal const string EmbeddedResource = "EmbeddedResource"; - internal const string None = "None"; - internal const string Reference = "Reference"; - internal const string ProjectCapability = "ProjectCapability"; - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 71e4caf73a7..cffaf90546f 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -47,7 +47,6 @@ - From a0f9fb8cb6d48e3c589d4ca44097b9fbf3c5e392 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 13:00:09 -0800 Subject: [PATCH 046/136] MSBuildTaskHost: Remove uncalled members from ExceptionHandling.cs --- src/MSBuildTaskHost/ExceptionHandling.cs | 207 +---------------------- 1 file changed, 1 insertion(+), 206 deletions(-) diff --git a/src/MSBuildTaskHost/ExceptionHandling.cs b/src/MSBuildTaskHost/ExceptionHandling.cs index 7f870f0bdb7..b11c89f64ba 100644 --- a/src/MSBuildTaskHost/ExceptionHandling.cs +++ b/src/MSBuildTaskHost/ExceptionHandling.cs @@ -4,19 +4,11 @@ #nullable disable using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; -using System.Linq; -using System.Reflection; using System.Security; -using System.Text; using System.Threading; -using System.Xml; -using Microsoft.Build.Shared.FileSystem; -using System.Xml.Schema; -using System.Runtime.Serialization; using Microsoft.Build.Framework; namespace Microsoft.Build.Shared @@ -68,15 +60,11 @@ internal static string DebugDumpPath } } - /// - /// The file used for diagnostic log files. - /// - internal static string DumpFilePath => s_dumpFileName; - /// /// The filename that exceptions will be dumped to /// private static string s_dumpFileName; + /// /// If the given exception is "ignorable under some circumstances" return false. /// Otherwise it's "really bad", and return true. @@ -103,17 +91,6 @@ internal static bool IsCriticalException(Exception e) return false; } - /// - /// If the given exception is file IO related or expected return false. - /// Otherwise, return true. - /// - /// The exception to check. - /// True if exception is not IO related or expected otherwise false. - internal static bool NotExpectedException(Exception e) - { - return !IsIoRelatedException(e); - } - /// /// Determine whether the exception is file-IO related. /// @@ -136,153 +113,6 @@ internal static bool IsIoRelatedException(Exception e) || e is IOException; } - /// Checks if the exception is an XML one. - /// Exception to check. - /// True if exception is related to XML parsing. - internal static bool IsXmlException(Exception e) - { - return e is XmlException - || e is XmlSyntaxException - || e is XmlSchemaException - || e is UriFormatException; // XmlTextReader for example uses this under the covers - } - - /// Extracts line and column numbers from the exception if it is XML-related one. - /// XML-related exception. - /// Line and column numbers if available, (0,0) if not. - /// This function works around the fact that XmlException and XmlSchemaException are not directly related. - internal static LineAndColumn GetXmlLineAndColumn(Exception e) - { - var line = 0; - var column = 0; - - var xmlException = e as XmlException; - if (xmlException != null) - { - line = xmlException.LineNumber; - column = xmlException.LinePosition; - } - else - { - var schemaException = e as XmlSchemaException; - if (schemaException != null) - { - line = schemaException.LineNumber; - column = schemaException.LinePosition; - } - } - - return new LineAndColumn - { - Line = line, - Column = column - }; - } - - /// - /// If the given exception is file IO related or Xml related return false. - /// Otherwise, return true. - /// - /// The exception to check. - internal static bool NotExpectedIoOrXmlException(Exception e) - { - if - ( - IsXmlException(e) - || !NotExpectedException(e)) - { - return false; - } - - return true; - } - - /// - /// If the given exception is reflection-related return false. - /// Otherwise, return true. - /// - /// The exception to check. - internal static bool NotExpectedReflectionException(Exception e) - { - // We are explicitly not handling TargetInvocationException. Those are just wrappers around - // exceptions thrown by the called code (such as a task or logger) which callers will typically - // want to treat differently. - if - ( - e is TypeLoadException // thrown when the common language runtime cannot find the assembly, the type within the assembly, or cannot load the type - || e is MethodAccessException // thrown when a class member is not found or access to the member is not permitted - || e is MissingMethodException // thrown when code in a dependent assembly attempts to access a missing method in an assembly that was modified - || e is MemberAccessException // thrown when a class member is not found or access to the member is not permitted - || e is BadImageFormatException // thrown when the file image of a DLL or an executable program is invalid - || e is ReflectionTypeLoadException // thrown by the Module.GetTypes method if any of the classes in a module cannot be loaded - || e is TargetParameterCountException // thrown when the number of parameters for an invocation does not match the number expected - || e is InvalidCastException - || e is AmbiguousMatchException // thrown when binding to a member results in more than one member matching the binding criteria - || e is CustomAttributeFormatException // thrown if a custom attribute on a data type is formatted incorrectly - || e is InvalidFilterCriteriaException // thrown in FindMembers when the filter criteria is not valid for the type of filter you are using - || e is TargetException // thrown when an attempt is made to invoke a non-static method on a null object. This may occur because the caller does not - // have access to the member, or because the target does not define the member, and so on. - || e is MissingFieldException // thrown when code in a dependent assembly attempts to access a missing field in an assembly that was modified. - || !NotExpectedException(e)) // Reflection can throw IO exceptions if the assembly cannot be opened - { - return false; - } - - return true; - } - - /// - /// Serialization has been observed to throw TypeLoadException as - /// well as SerializationException and IO exceptions. (Obviously - /// it has to do reflection but it ought to be wrapping the exceptions.) - /// - internal static bool NotExpectedSerializationException(Exception e) - { - if - ( - e is SerializationException || - !NotExpectedReflectionException(e)) - { - return false; - } - - return true; - } - - /// - /// Returns false if this is a known exception thrown by the registry API. - /// - internal static bool NotExpectedRegistryException(Exception e) - { - if (e is SecurityException - || e is UnauthorizedAccessException - || e is IOException - || e is ObjectDisposedException - || e is ArgumentException) - { - return false; - } - - return true; - } - - /// - /// Returns false if this is a known exception thrown by function evaluation - /// - internal static bool NotExpectedFunctionException(Exception e) - { - if (e is InvalidCastException - || e is ArgumentNullException - || e is FormatException - || e is InvalidOperationException - || !NotExpectedReflectionException(e)) - { - return false; - } - - return true; - } - /// /// Dump any unhandled exceptions to a file so they can be diagnosed /// @@ -343,40 +173,5 @@ internal static void DumpExceptionToFile(Exception ex) { } } - - /// - /// Returns the content of any exception dump files modified - /// since the provided time, otherwise returns an empty string. - /// - internal static string ReadAnyExceptionFromFile(DateTime fromTimeUtc) - { - var builder = new StringBuilder(); - IEnumerable files = FileSystems.Default.EnumerateFiles(DebugDumpPath, "MSBuild*failure.txt"); - - foreach (string file in files) - { - if (FileSystems.Default.GetLastWriteTimeUtc(file) >= fromTimeUtc) - { - builder.Append(Environment.NewLine); - builder.Append(file); - builder.Append(':'); - builder.Append(Environment.NewLine); - builder.Append(FileSystems.Default.ReadFileAllText(file)); - builder.Append(Environment.NewLine); - } - } - - return builder.ToString(); - } - - /// Line and column pair. - internal struct LineAndColumn - { - /// Gets or sets line number. - internal int Line { get; set; } - - /// Gets or sets column position. - internal int Column { get; set; } - } } } From 1694bdea952bbb33a9e8969acedd57cab5373147 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 13:04:06 -0800 Subject: [PATCH 047/136] MSBuildTaskHost: Remove uncalled members from ErrorUtilities.cs --- src/MSBuildTaskHost/ErrorUtilities.cs | 408 -------------------------- 1 file changed, 408 deletions(-) diff --git a/src/MSBuildTaskHost/ErrorUtilities.cs b/src/MSBuildTaskHost/ErrorUtilities.cs index dc1540ca745..6862a18fa2f 100644 --- a/src/MSBuildTaskHost/ErrorUtilities.cs +++ b/src/MSBuildTaskHost/ErrorUtilities.cs @@ -2,13 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.IO; using System.Runtime.CompilerServices; -using System.Threading; using Microsoft.Build.Framework; namespace Microsoft.Build.Shared @@ -18,31 +13,6 @@ namespace Microsoft.Build.Shared /// internal static class ErrorUtilities { - private static readonly bool s_enableMSBuildDebugTracing = !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDENABLEDEBUGTRACING")); - - public static void DebugTraceMessage(string category, string formatstring, params object[]? parameters) - { - if (s_enableMSBuildDebugTracing) - { - if (parameters != null) - { - Trace.WriteLine(String.Format(CultureInfo.CurrentCulture, formatstring, parameters), category); - } - else - { - Trace.WriteLine(formatstring, category); - } - } - } - - internal static void VerifyThrowInternalError([DoesNotReturnIf(false)] bool condition, string message, params object?[]? args) - { - if (!condition) - { - ThrowInternalError(message, args); - } - } - /// /// Throws InternalErrorException. /// This is only for situations that would mean that there is a bug in MSBuild itself. @@ -74,35 +44,6 @@ internal static void ThrowInternalErrorUnreachable() throw new InternalErrorException("Unreachable?"); } - /// - /// Throws InternalErrorException. - /// Indicates the code path followed should not have been possible. - /// This is only for situations that would mean that there is a bug in MSBuild itself. - /// - internal static void VerifyThrowInternalErrorUnreachable([DoesNotReturnIf(false)] bool condition) - { - if (!condition) - { - ThrowInternalErrorUnreachable(); - } - } - - /// - /// Throws InternalErrorException. - /// Indicates the code path followed should not have been possible. - /// This is only for situations that would mean that there is a bug in MSBuild itself. - /// - internal static void ThrowIfTypeDoesNotImplementToString(object param) - { -#if DEBUG - // Check it has a real implementation of ToString() - if (String.Equals(param.GetType().ToString(), param.ToString(), StringComparison.Ordinal)) - { - ThrowInternalError("This type does not implement ToString() properly {0}", param.GetType().FullName!); - } -#endif - } - /// /// Helper to throw an InternalErrorException when the specified parameter is null. /// This should be used ONLY if this would indicate a bug in MSBuild rather than @@ -118,16 +59,6 @@ internal static void VerifyThrowInternalNull([NotNull] object? parameter, [Calle } } - /// - /// Helper to throw an InternalErrorException when a lock on the specified object is not already held. - /// This should be used ONLY if this would indicate a bug in MSBuild rather than - /// anything caused by user action. - /// - /// The object that should already have been used as a lock. - internal static void VerifyThrowInternalLockHeld(object locker) - { - } - /// /// Helper to throw an InternalErrorException when the specified parameter is null or zero length. /// This should be used ONLY if this would indicate a bug in MSBuild rather than @@ -145,30 +76,6 @@ internal static void VerifyThrowInternalLength([NotNull] string? parameterValue, } } - public static void VerifyThrowInternalLength([NotNull] T[]? parameterValue, [CallerArgumentExpression(nameof(parameterValue))] string? parameterName = null) - { - VerifyThrowInternalNull(parameterValue, parameterName); - - if (parameterValue.Length == 0) - { - ThrowInternalError("{0} unexpectedly empty", parameterName); - } - } - - /// - /// Helper to throw an InternalErrorException when the specified parameter is not a rooted path. - /// This should be used ONLY if this would indicate a bug in MSBuild rather than - /// anything caused by user action. - /// - /// Parameter that should be a rooted path. - internal static void VerifyThrowInternalRooted(string value) - { - if (!Path.IsPathRooted(value)) - { - ThrowInternalError("{0} unexpectedly not a rooted path", value); - } - } - /// /// This method should be used in places where one would normally put /// an "assert". It should be used to validate that our assumptions are @@ -184,17 +91,6 @@ internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string } } - /// - /// Overload for one string format argument. - /// - internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string unformattedMessage, int arg0) - { - if (!condition) - { - ThrowInternalError(unformattedMessage, arg0); - } - } - /// /// Overload for one string format argument. /// @@ -206,50 +102,6 @@ internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string } } - /// - /// Overload for two string format arguments. - /// - internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string unformattedMessage, int arg0, int arg1) - { - if (!condition) - { - ThrowInternalError(unformattedMessage, arg0, arg1); - } - } - - /// - /// Overload for two string format arguments. - /// - internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string unformattedMessage, object arg0, object arg1) - { - if (!condition) - { - ThrowInternalError(unformattedMessage, arg0, arg1); - } - } - - /// - /// Overload for three string format arguments. - /// - internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string unformattedMessage, object arg0, object arg1, object arg2) - { - if (!condition) - { - ThrowInternalError(unformattedMessage, arg0, arg1, arg2); - } - } - - /// - /// Overload for four string format arguments. - /// - internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string unformattedMessage, object arg0, object arg1, object arg2, object arg3) - { - if (!condition) - { - ThrowInternalError(unformattedMessage, arg0, arg1, arg2, arg3); - } - } - /// /// Throws an InvalidOperationException with the specified resource string /// @@ -261,92 +113,6 @@ internal static void ThrowInvalidOperation(string resourceName, params object?[] throw new InvalidOperationException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword(resourceName, args)); } - /// - /// Throws an InvalidOperationException if the given condition is false. - /// - internal static void VerifyThrowInvalidOperation([DoesNotReturnIf(false)] bool condition, string resourceName) - { - ResourceUtilities.VerifyResourceStringExists(resourceName); - if (!condition) - { - ThrowInvalidOperation(resourceName, null); - } - } - - /// - /// Overload for one string format argument. - /// - internal static void VerifyThrowInvalidOperation([DoesNotReturnIf(false)] bool condition, string resourceName, object arg0) - { - ResourceUtilities.VerifyResourceStringExists(resourceName); - // PERF NOTE: check the condition here instead of pushing it into - // the ThrowInvalidOperation() method, because that method always - // allocates memory for its variable array of arguments - if (!condition) - { - ThrowInvalidOperation(resourceName, arg0); - } - } - - /// - /// Overload for two string format arguments. - /// - internal static void VerifyThrowInvalidOperation([DoesNotReturnIf(false)] bool condition, string resourceName, object arg0, object arg1) - { - ResourceUtilities.VerifyResourceStringExists(resourceName); - // PERF NOTE: check the condition here instead of pushing it into - // the ThrowInvalidOperation() method, because that method always - // allocates memory for its variable array of arguments - if (!condition) - { - ThrowInvalidOperation(resourceName, arg0, arg1); - } - } - - /// - /// Overload for three string format arguments. - /// - internal static void VerifyThrowInvalidOperation([DoesNotReturnIf(false)] bool condition, string resourceName, object arg0, object arg1, object arg2) - { - ResourceUtilities.VerifyResourceStringExists(resourceName); - // PERF NOTE: check the condition here instead of pushing it into - // the ThrowInvalidOperation() method, because that method always - // allocates memory for its variable array of arguments - if (!condition) - { - ThrowInvalidOperation(resourceName, arg0, arg1, arg2); - } - } - - /// - /// Overload for four string format arguments. - /// - internal static void VerifyThrowInvalidOperation([DoesNotReturnIf(false)] bool condition, string resourceName, object arg0, object arg1, object arg2, object arg3) - { - ResourceUtilities.VerifyResourceStringExists(resourceName); - - // PERF NOTE: check the condition here instead of pushing it into - // the ThrowInvalidOperation() method, because that method always - // allocates memory for its variable array of arguments - if (!condition) - { - ThrowInvalidOperation(resourceName, arg0, arg1, arg2, arg3); - } - } - - /// - /// Throws an ArgumentException that can include an inner exception. - /// - /// PERF WARNING: calling a method that takes a variable number of arguments - /// is expensive, because memory is allocated for the array of arguments -- do - /// not call this method repeatedly in performance-critical scenarios - /// - [DoesNotReturn] - internal static void ThrowArgument(string resourceName, params object?[]? args) - { - ThrowArgument(null, resourceName, args); - } - /// /// Throws an ArgumentException that can include an inner exception. /// @@ -366,14 +132,6 @@ internal static void ThrowArgument(Exception? innerException, string resourceNam throw new ArgumentException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword(resourceName, args), innerException); } - /// - /// Throws an ArgumentException if the given condition is false. - /// - internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, string resourceName) - { - VerifyThrowArgument(condition, null, resourceName); - } - /// /// Overload for one string format argument. /// @@ -382,46 +140,6 @@ internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition VerifyThrowArgument(condition, null, resourceName, arg0); } - /// - /// Overload for two string format arguments. - /// - internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, string resourceName, object arg0, object arg1) - { - VerifyThrowArgument(condition, null, resourceName, arg0, arg1); - } - - /// - /// Overload for three string format arguments. - /// - internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, string resourceName, object arg0, object arg1, object arg2) - { - VerifyThrowArgument(condition, null, resourceName, arg0, arg1, arg2); - } - - /// - /// Overload for four string format arguments. - /// - internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, string resourceName, object arg0, object arg1, object arg2, object arg3) - { - VerifyThrowArgument(condition, null, resourceName, arg0, arg1, arg2, arg3); - } - - /// - /// Throws an ArgumentException that includes an inner exception, if - /// the given condition is false. - /// - /// - /// Can be null. - /// - internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, Exception? innerException, string resourceName) - { - ResourceUtilities.VerifyResourceStringExists(resourceName); - if (!condition) - { - ThrowArgument(innerException, resourceName, null); - } - } - /// /// Overload for one string format argument. /// @@ -435,66 +153,6 @@ internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition } } - /// - /// Overload for two string format arguments. - /// - internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, Exception? innerException, string resourceName, object arg0, object arg1) - { - ResourceUtilities.VerifyResourceStringExists(resourceName); - - if (!condition) - { - ThrowArgument(innerException, resourceName, arg0, arg1); - } - } - - /// - /// Overload for three string format arguments. - /// - internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, Exception? innerException, string resourceName, object arg0, object arg1, object arg2) - { - ResourceUtilities.VerifyResourceStringExists(resourceName); - - if (!condition) - { - ThrowArgument(innerException, resourceName, arg0, arg1, arg2); - } - } - - /// - /// Overload for four string format arguments. - /// - internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, Exception? innerException, string resourceName, object arg0, object arg1, object arg2, object arg3) - { - ResourceUtilities.VerifyResourceStringExists(resourceName); - - if (!condition) - { - ThrowArgument(innerException, resourceName, arg0, arg1, arg2, arg3); - } - } - - /// - /// Throws an argument out of range exception. - /// - [DoesNotReturn] - internal static void ThrowArgumentOutOfRange(string? parameterName) - { - throw new ArgumentOutOfRangeException(parameterName); - } - - /// - /// Throws an ArgumentOutOfRangeException using the given parameter name - /// if the condition is false. - /// - internal static void VerifyThrowArgumentOutOfRange([DoesNotReturnIf(false)] bool condition, [CallerArgumentExpression(nameof(condition))] string? parameterName = null) - { - if (!condition) - { - ThrowArgumentOutOfRange(parameterName); - } - } - /// /// Throws an ArgumentNullException if the given string parameter is null /// and ArgumentException if it has zero length. @@ -515,32 +173,6 @@ private static void ThrowArgumentLength(string? parameterName) throw new ArgumentException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("Shared.ParameterCannotHaveZeroLength", parameterName)); } - /// - /// Throws an ArgumentNullException if the given string parameter is null - /// and ArgumentException if it has zero length. - /// - internal static void VerifyThrowArgumentInvalidPath([NotNull] string parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) - { - VerifyThrowArgumentNull(parameter, parameterName); - - if (FileUtilities.PathIsInvalid(parameter)) - { - ThrowArgument("Shared.ParameterCannotHaveInvalidPathChars", parameterName, parameter); - } - } - - /// - /// Throws an ArgumentException if the string has zero length, unless it is - /// null, in which case no exception is thrown. - /// - internal static void VerifyThrowArgumentLengthIfNotNull(string? parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) - { - if (parameter?.Length == 0) - { - ThrowArgumentLength(parameterName); - } - } - /// /// Throws an ArgumentNullException if the given parameter is null. /// @@ -567,45 +199,5 @@ internal static void ThrowArgumentNull(string? parameterName, string resourceNam // Most ArgumentNullException overloads append its own rather clunky multi-line message. So use the one overload that doesn't. throw new ArgumentNullException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword(resourceName, parameterName), (Exception?)null); } - - internal static void VerifyThrowObjectDisposed([DoesNotReturnIf(false)] bool condition, string objectName) - { - if (!condition) - { - ThrowObjectDisposed(objectName); - } - } - - [DoesNotReturn] - internal static void ThrowObjectDisposed(string objectName) - { - throw new ObjectDisposedException(objectName); - } - - /// - /// A utility that verifies the parameters provided to a standard ICollection.CopyTo call. - /// - /// If is null. - /// If falls outside of the bounds . - /// If there is insufficient capacity to copy the collection contents into - /// when starting at . - internal static void VerifyCollectionCopyToArguments( - [NotNull] T[]? array, - string arrayParameterName, - int arrayIndex, - string arrayIndexParameterName, - int requiredCapacity) - { - VerifyThrowArgumentNull(array, arrayParameterName); - VerifyThrowArgumentOutOfRange(arrayIndex >= 0 && arrayIndex < array.Length, arrayIndexParameterName); - - int arrayCapacity = array.Length - arrayIndex; - if (requiredCapacity > arrayCapacity) - { - throw new ArgumentException( - ResourceUtilities.GetResourceString("Shared.CollectionCopyToFailureProvidedArrayIsTooSmall"), - arrayParameterName); - } - } } } From 1cd4cc034da0e07f048d7f0467a75651fcb035f9 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 13:11:46 -0800 Subject: [PATCH 048/136] MSBuildTaskHost: Remove AssemblyUtilities.cs The helper functions in AssemblyUtilities.cs are generally not useful on .NET 3.5. --- src/MSBuildTaskHost/AssemblyNameExtension.cs | 2 +- .../CommunicationsUtilities.cs | 2 +- .../Framework/AssemblyUtilities.cs | 37 ------------------- src/MSBuildTaskHost/LoadedType.cs | 6 +-- .../MSBuild/OutOfProcTaskHostNode.cs | 2 +- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - src/MSBuildTaskHost/TaskLoader.cs | 10 ++--- src/MSBuildTaskHost/TaskParameter.cs | 6 +-- .../TaskParameterTypeVerifier.cs | 16 ++++---- 9 files changed, 22 insertions(+), 60 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/AssemblyUtilities.cs diff --git a/src/MSBuildTaskHost/AssemblyNameExtension.cs b/src/MSBuildTaskHost/AssemblyNameExtension.cs index 689932bdc9d..aaa2072ef91 100644 --- a/src/MSBuildTaskHost/AssemblyNameExtension.cs +++ b/src/MSBuildTaskHost/AssemblyNameExtension.cs @@ -598,7 +598,7 @@ internal AssemblyNameExtension Clone() if (asAssemblyName != null) { - newExtension.asAssemblyName = asAssemblyName.CloneIfPossible(); + newExtension.asAssemblyName = (AssemblyName)asAssemblyName.Clone(); } newExtension.asString = asString; diff --git a/src/MSBuildTaskHost/CommunicationsUtilities.cs b/src/MSBuildTaskHost/CommunicationsUtilities.cs index 003a4c657e4..4c4aa19274e 100644 --- a/src/MSBuildTaskHost/CommunicationsUtilities.cs +++ b/src/MSBuildTaskHost/CommunicationsUtilities.cs @@ -749,7 +749,7 @@ internal static HandshakeOptions GetHandshakeOptions( // No parameters given, default to current if (taskHostParameters.IsEmpty) { - clrVersion = typeof(bool).GetTypeInfo().Assembly.GetName().Version.Major; + clrVersion = typeof(bool).Assembly.GetName().Version.Major; architectureFlagToSet = XMakeAttributes.GetCurrentMSBuildArchitecture(); } else // Figure out flags based on parameters given diff --git a/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs b/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs deleted file mode 100644 index 64937accaf3..00000000000 --- a/src/MSBuildTaskHost/Framework/AssemblyUtilities.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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.Globalization; -using System.Reflection; - -#nullable disable - -namespace Microsoft.Build.Shared -{ - /// - /// This class contains common reflection tasks - /// - internal static class AssemblyUtilities - { - public static Assembly EntryAssembly = GetEntryAssembly(); - - /// - /// Shim for the lack of in .NET 3.5. - /// - public static Type GetTypeInfo(this Type t) - { - return t; - } - - public static AssemblyName CloneIfPossible(this AssemblyName assemblyNameToClone) - { - return (AssemblyName)assemblyNameToClone.Clone(); - } - - private static Assembly GetEntryAssembly() - { - return System.Reflection.Assembly.GetEntryAssembly(); - } - } -} diff --git a/src/MSBuildTaskHost/LoadedType.cs b/src/MSBuildTaskHost/LoadedType.cs index 40f777459da..4c5dd6e0538 100644 --- a/src/MSBuildTaskHost/LoadedType.cs +++ b/src/MSBuildTaskHost/LoadedType.cs @@ -55,8 +55,8 @@ internal LoadedType( : loadedAssembly.Location; LoadedAssembly = loadedAssembly; - HasLoadInSeparateAppDomainAttribute = Type.GetTypeInfo().IsDefined(typeof(LoadInSeparateAppDomainAttribute), true /* inherited */); - HasSTAThreadAttribute = Type.GetTypeInfo().IsDefined(typeof(RunInSTAAttribute), true /* inherited */); + HasLoadInSeparateAppDomainAttribute = Type.IsDefined(typeof(LoadInSeparateAppDomainAttribute), true /* inherited */); + HasSTAThreadAttribute = Type.IsDefined(typeof(RunInSTAAttribute), true /* inherited */); IsMarshalByRef = Type.IsMarshalByRef; } @@ -91,7 +91,7 @@ private bool CheckForHardcodedSTARequirement() // we changed to running all tasks in MTA. if (String.Equals("Microsoft.Build.Tasks.Xaml.PartialClassGenerationTask", Type.FullName, StringComparison.OrdinalIgnoreCase)) { - AssemblyName assemblyName = Type.GetTypeInfo().Assembly.GetName(); + AssemblyName assemblyName = Type.Assembly.GetName(); Version lastVersionToForce = new Version(3, 5); if (assemblyName.Version?.CompareTo(lastVersionToForce) > 0) { diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs index 2a760dffe94..b85f181bf54 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs @@ -975,7 +975,7 @@ private void SendBuildEvent(BuildEventArgs e) #pragma warning disable SYSLIB0050 // Types which are not serializable and are not IExtendedBuildEventArgs as // those always implement custom serialization by WriteToStream and CreateFromStream. - if (!e.GetType().GetTypeInfo().IsSerializable) + if (!e.GetType().IsSerializable) #pragma warning disable SYSLIB0050 { // log a warning and bail. This will end up re-calling SendBuildEvent, but we know for a fact diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index cffaf90546f..8586a3fc098 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -59,7 +59,6 @@ - diff --git a/src/MSBuildTaskHost/TaskLoader.cs b/src/MSBuildTaskHost/TaskLoader.cs index 6aa105b557d..585154a6ed8 100644 --- a/src/MSBuildTaskHost/TaskLoader.cs +++ b/src/MSBuildTaskHost/TaskLoader.cs @@ -30,8 +30,8 @@ internal static class TaskLoader /// true, if specified type is a task internal static bool IsTaskClass(Type type, object unused) { - return type.GetTypeInfo().IsClass && !type.GetTypeInfo().IsAbstract && ( - type.GetTypeInfo().GetInterface("Microsoft.Build.Framework.ITask") != null); + return type.IsClass && !type.IsAbstract && ( + type.GetInterface("Microsoft.Build.Framework.ITask") != null); } /// @@ -135,17 +135,17 @@ out AppDomain? taskAppDomain taskColumn, "ConflictingTaskAssembly", loadedType.Assembly.AssemblyFile, - loadedType.Type.GetTypeInfo().Assembly.Location); + loadedType.Type.Assembly.Location); taskInstanceInOtherAppDomain = null; } } else { - taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceAndUnwrap(loadedType.Type.GetTypeInfo().Assembly.FullName, loadedType.Type.FullName); + taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceAndUnwrap(loadedType.Type.Assembly.FullName, loadedType.Type.FullName); } - return taskInstanceInOtherAppDomain; + return taskInstanceInOtherAppDomain; } finally { diff --git a/src/MSBuildTaskHost/TaskParameter.cs b/src/MSBuildTaskHost/TaskParameter.cs index 2f07bfb5041..53f160099ef 100644 --- a/src/MSBuildTaskHost/TaskParameter.cs +++ b/src/MSBuildTaskHost/TaskParameter.cs @@ -122,7 +122,7 @@ public TaskParameter(object wrappedParameter) _parameterTypeCode = typeCode; _wrappedParameter = wrappedParameter; } - else if (typeof(ITaskItem[]).GetTypeInfo().IsAssignableFrom(wrappedParameterType.GetTypeInfo())) + else if (typeof(ITaskItem[]).IsAssignableFrom(wrappedParameterType)) { _parameterType = TaskParameterType.ITaskItemArray; ITaskItem[] inputAsITaskItemArray = (ITaskItem[])wrappedParameter; @@ -138,7 +138,7 @@ public TaskParameter(object wrappedParameter) _wrappedParameter = taskItemArrayParameter; } - else if (wrappedParameterType.GetElementType().GetTypeInfo().IsValueType) + else if (wrappedParameterType.GetElementType().IsValueType) { _parameterType = TaskParameterType.ValueTypeArray; _wrappedParameter = wrappedParameter; @@ -173,7 +173,7 @@ public TaskParameter(object wrappedParameter) _parameterType = TaskParameterType.ITaskItem; _wrappedParameter = new TaskParameterTaskItem((ITaskItem)wrappedParameter); } - else if (wrappedParameterType.GetTypeInfo().IsValueType) + else if (wrappedParameterType.IsValueType) { _parameterType = TaskParameterType.ValueType; _wrappedParameter = wrappedParameter; diff --git a/src/MSBuildTaskHost/TaskParameterTypeVerifier.cs b/src/MSBuildTaskHost/TaskParameterTypeVerifier.cs index 9e0bdce6333..0f971502be3 100644 --- a/src/MSBuildTaskHost/TaskParameterTypeVerifier.cs +++ b/src/MSBuildTaskHost/TaskParameterTypeVerifier.cs @@ -19,14 +19,14 @@ internal static class TaskParameterTypeVerifier /// Is the parameter type a valid scalar input value /// internal static bool IsValidScalarInputParameter(Type parameterType) => - parameterType.GetTypeInfo().IsValueType || parameterType == typeof(string) || parameterType == typeof(ITaskItem); + parameterType.IsValueType || parameterType == typeof(string) || parameterType == typeof(ITaskItem); /// /// Is the passed in parameterType a valid vector input parameter /// internal static bool IsValidVectorInputParameter(Type parameterType) { - bool result = (parameterType.IsArray && parameterType.GetElementType().GetTypeInfo().IsValueType) || + bool result = (parameterType.IsArray && parameterType.GetElementType().IsValueType) || parameterType == typeof(string[]) || parameterType == typeof(ITaskItem[]); return result; @@ -37,8 +37,8 @@ internal static bool IsValidVectorInputParameter(Type parameterType) /// internal static bool IsAssignableToITask(Type parameterType) { - bool result = typeof(ITaskItem[]).GetTypeInfo().IsAssignableFrom(parameterType.GetTypeInfo()) || /* ITaskItem array or derived type, or */ - typeof(ITaskItem).IsAssignableFrom(parameterType); /* ITaskItem or derived type */ + bool result = typeof(ITaskItem[]).IsAssignableFrom(parameterType) || /* ITaskItem array or derived type, or */ + typeof(ITaskItem).IsAssignableFrom(parameterType); /* ITaskItem or derived type */ return result; } @@ -47,10 +47,10 @@ internal static bool IsAssignableToITask(Type parameterType) /// internal static bool IsValueTypeOutputParameter(Type parameterType) { - bool result = (parameterType.IsArray && parameterType.GetElementType().GetTypeInfo().IsValueType) || /* array of value types, or */ - parameterType == typeof(string[]) || /* string array, or */ - parameterType.GetTypeInfo().IsValueType || /* value type, or */ - parameterType == typeof(string); /* string */ + bool result = (parameterType.IsArray && parameterType.GetElementType().IsValueType) || /* array of value types, or */ + parameterType == typeof(string[]) || /* string array, or */ + parameterType.IsValueType || /* value type, or */ + parameterType == typeof(string); /* string */ return result; } From f93b9e0912a5c3aef00a144c88e8ed2289e90204 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 13:41:02 -0800 Subject: [PATCH 049/136] MSBuildTaskHost: Remove uncalled members in NativeMethods.cs --- .../Framework/NativeMethods.cs | 1118 +---------------- 1 file changed, 40 insertions(+), 1078 deletions(-) diff --git a/src/MSBuildTaskHost/Framework/NativeMethods.cs b/src/MSBuildTaskHost/Framework/NativeMethods.cs index a649d4d83a4..b74cf9c9ea5 100644 --- a/src/MSBuildTaskHost/Framework/NativeMethods.cs +++ b/src/MSBuildTaskHost/Framework/NativeMethods.cs @@ -2,19 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Reflection; using System.Runtime.InteropServices; -using System.Runtime.Versioning; -using Microsoft.Build.Shared; using Microsoft.Win32; -using Microsoft.Win32.SafeHandles; - -using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; #nullable disable @@ -22,121 +12,24 @@ namespace Microsoft.Build.Framework; internal static class NativeMethods { - #region Constants - - internal const uint ERROR_INSUFFICIENT_BUFFER = 0x8007007A; - internal const uint STARTUP_LOADER_SAFEMODE = 0x10; - internal const uint S_OK = 0x0; - internal const uint S_FALSE = 0x1; - internal const uint ERROR_ACCESS_DENIED = 0x5; - internal const uint ERROR_FILE_NOT_FOUND = 0x80070002; - internal const uint FUSION_E_PRIVATE_ASM_DISALLOWED = 0x80131044; // Tried to find unsigned assembly in GAC - internal const uint RUNTIME_INFO_DONT_SHOW_ERROR_DIALOG = 0x40; - internal const uint FILE_TYPE_CHAR = 0x0002; - internal const Int32 STD_OUTPUT_HANDLE = -11; - internal const Int32 STD_ERROR_HANDLE = -12; - internal const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004; - internal const uint RPC_S_CALLPENDING = 0x80010115; - internal const uint E_ABORT = (uint)0x80004004; - - internal const int FILE_ATTRIBUTE_READONLY = 0x00000001; - internal const int FILE_ATTRIBUTE_DIRECTORY = 0x00000010; - internal const int FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400; + private const int FILE_ATTRIBUTE_DIRECTORY = 0x00000010; /// /// Default buffer size to use when dealing with the Windows API. /// - internal const int MAX_PATH = 260; - - private const string kernel32Dll = "kernel32.dll"; + private const int MAX_PATH = 260; private const string WINDOWS_FILE_SYSTEM_REGISTRY_KEY = @"SYSTEM\CurrentControlSet\Control\FileSystem"; private const string WINDOWS_LONG_PATHS_ENABLED_VALUE_NAME = "LongPathsEnabled"; - private const string WINDOWS_SAC_REGISTRY_KEY = @"SYSTEM\CurrentControlSet\Control\CI\Policy"; - private const string WINDOWS_SAC_VALUE_NAME = "VerifiedAndReputablePolicyState"; - - internal static DateTime MinFileDate { get; } = DateTime.FromFileTimeUtc(0); - - internal static HandleRef NullHandleRef = new HandleRef(null, IntPtr.Zero); - - internal static IntPtr NullIntPtr = new IntPtr(0); - internal static IntPtr InvalidHandle = new IntPtr(-1); - // As defined in winnt.h: - internal const ushort PROCESSOR_ARCHITECTURE_INTEL = 0; - internal const ushort PROCESSOR_ARCHITECTURE_ARM = 5; - internal const ushort PROCESSOR_ARCHITECTURE_IA64 = 6; - internal const ushort PROCESSOR_ARCHITECTURE_AMD64 = 9; - internal const ushort PROCESSOR_ARCHITECTURE_ARM64 = 12; - - internal const uint INFINITE = 0xFFFFFFFF; - internal const uint WAIT_ABANDONED_0 = 0x00000080; - internal const uint WAIT_OBJECT_0 = 0x00000000; - internal const uint WAIT_TIMEOUT = 0x00000102; - - #endregion + private const ushort PROCESSOR_ARCHITECTURE_INTEL = 0; + private const ushort PROCESSOR_ARCHITECTURE_ARM = 5; + private const ushort PROCESSOR_ARCHITECTURE_IA64 = 6; + private const ushort PROCESSOR_ARCHITECTURE_AMD64 = 9; + private const ushort PROCESSOR_ARCHITECTURE_ARM64 = 12; - #region Enums - - internal enum StreamHandleType - { - StdOut = STD_OUTPUT_HANDLE, - StdErr = STD_ERROR_HANDLE, - }; - - private enum PROCESSINFOCLASS : int - { - ProcessBasicInformation = 0, - ProcessQuotaLimits, - ProcessIoCounters, - ProcessVmCounters, - ProcessTimes, - ProcessBasePriority, - ProcessRaisePriority, - ProcessDebugPort, - ProcessExceptionPort, - ProcessAccessToken, - ProcessLdtInformation, - ProcessLdtSize, - ProcessDefaultHardErrorMode, - ProcessIoPortHandlers, // Note: this is kernel mode only - ProcessPooledUsageAndLimits, - ProcessWorkingSetWatch, - ProcessUserModeIOPL, - ProcessEnableAlignmentFaultFixup, - ProcessPriorityClass, - ProcessWx86Information, - ProcessHandleCount, - ProcessAffinityMask, - ProcessPriorityBoost, - MaxProcessInfoClass - }; - - private enum eDesiredAccess : int - { - DELETE = 0x00010000, - READ_CONTROL = 0x00020000, - WRITE_DAC = 0x00040000, - WRITE_OWNER = 0x00080000, - SYNCHRONIZE = 0x00100000, - STANDARD_RIGHTS_ALL = 0x001F0000, - - PROCESS_TERMINATE = 0x0001, - PROCESS_CREATE_THREAD = 0x0002, - PROCESS_SET_SESSIONID = 0x0004, - PROCESS_VM_OPERATION = 0x0008, - PROCESS_VM_READ = 0x0010, - PROCESS_VM_WRITE = 0x0020, - PROCESS_DUP_HANDLE = 0x0040, - PROCESS_CREATE_PROCESS = 0x0080, - PROCESS_SET_QUOTA = 0x0100, - PROCESS_SET_INFORMATION = 0x0200, - PROCESS_QUERY_INFORMATION = 0x0400, - PROCESS_ALL_ACCESS = SYNCHRONIZE | 0xFFF - } -#pragma warning disable 0649, 0169 - internal enum LOGICAL_PROCESSOR_RELATIONSHIP + private enum LOGICAL_PROCESSOR_RELATIONSHIP { RelationProcessorCore, RelationNumaNode, @@ -145,14 +38,23 @@ internal enum LOGICAL_PROCESSOR_RELATIONSHIP RelationGroup, RelationAll = 0xffff } - internal struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX + + private struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX { public LOGICAL_PROCESSOR_RELATIONSHIP Relationship; public uint Size; public PROCESSOR_RELATIONSHIP Processor; + + public SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX() + { + Relationship = default; + Size = default; + Processor = default; + } } + [StructLayout(LayoutKind.Sequential)] - internal unsafe struct PROCESSOR_RELATIONSHIP + private unsafe struct PROCESSOR_RELATIONSHIP { public byte Flags; private byte EfficiencyClass; @@ -160,34 +62,11 @@ internal unsafe struct PROCESSOR_RELATIONSHIP public ushort GroupCount; public IntPtr GroupInfo; } -#pragma warning restore 0169, 0149 - - /// - /// Flags for CoWaitForMultipleHandles - /// - [Flags] - public enum COWAIT_FLAGS : int - { - /// - /// Exit when a handle is signaled. - /// - COWAIT_NONE = 0, - - /// - /// Exit when all handles are signaled AND a message is received. - /// - COWAIT_WAITALL = 0x00000001, - - /// - /// Exit when an RPC call is serviced. - /// - COWAIT_ALERTABLE = 0x00000002 - } /// /// Processor architecture values /// - internal enum ProcessorArchitectures + public enum ProcessorArchitectures { // Intel 32 bit X86, @@ -223,22 +102,11 @@ internal enum ProcessorArchitectures Unknown } - internal enum SymbolicLink - { - File = 0, - Directory = 1, - AllowUnprivilegedCreate = 2, - } - - #endregion - - #region Structs - /// /// Structure that contain information about the system on which we are running /// [StructLayout(LayoutKind.Sequential)] - internal struct SYSTEM_INFO + private struct SYSTEM_INFO { // This is a union of a DWORD and a struct containing 2 WORDs. internal ushort wProcessorArchitecture; @@ -255,118 +123,11 @@ internal struct SYSTEM_INFO internal ushort wProcessorRevision; } - /// - /// Wrap the intptr returned by OpenProcess in a safe handle. - /// - internal class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid - { - // Create a SafeHandle, informing the base class - // that this SafeHandle instance "owns" the handle, - // and therefore SafeHandle should call - // our ReleaseHandle method when the SafeHandle - // is no longer in use - private SafeProcessHandle() : base(true) - { - } - - [SupportedOSPlatform("windows")] - protected override bool ReleaseHandle() - { - return CloseHandle(handle); - } - } - - /// - /// Contains information about the current state of both physical and virtual memory, including extended memory - /// - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - internal class MemoryStatus - { - /// - /// Initializes a new instance of the class. - /// - public MemoryStatus() - { - _length = (uint)Marshal.SizeOf(typeof(MemoryStatus)); - } - - /// - /// Size of the structure, in bytes. You must set this member before calling GlobalMemoryStatusEx. - /// - private uint _length; - - /// - /// Number between 0 and 100 that specifies the approximate percentage of physical - /// memory that is in use (0 indicates no memory use and 100 indicates full memory use). - /// - public uint MemoryLoad; - - /// - /// Total size of physical memory, in bytes. - /// - public ulong TotalPhysical; - - /// - /// Size of physical memory available, in bytes. - /// - public ulong AvailablePhysical; - - /// - /// Size of the committed memory limit, in bytes. This is physical memory plus the - /// size of the page file, minus a small overhead. - /// - public ulong TotalPageFile; - - /// - /// Size of available memory to commit, in bytes. The limit is ullTotalPageFile. - /// - public ulong AvailablePageFile; - - /// - /// Total size of the user mode portion of the virtual address space of the calling process, in bytes. - /// - public ulong TotalVirtual; - - /// - /// Size of unreserved and uncommitted memory in the user mode portion of the virtual - /// address space of the calling process, in bytes. - /// - public ulong AvailableVirtual; - - /// - /// Size of unreserved and uncommitted memory in the extended portion of the virtual - /// address space of the calling process, in bytes. - /// - public ulong AvailableExtendedVirtual; - } - - [StructLayout(LayoutKind.Sequential)] - private struct PROCESS_BASIC_INFORMATION - { - public uint ExitStatus; - public IntPtr PebBaseAddress; - public UIntPtr AffinityMask; - public int BasePriority; - public UIntPtr UniqueProcessId; - public UIntPtr InheritedFromUniqueProcessId; - - public readonly uint Size - { - get - { - unsafe - { - return (uint)sizeof(PROCESS_BASIC_INFORMATION); - } - } - } - }; - /// /// Contains information about a file or directory; used by GetFileAttributesEx. /// [StructLayout(LayoutKind.Sequential)] - public struct WIN32_FILE_ATTRIBUTE_DATA + private struct WIN32_FILE_ATTRIBUTE_DATA { internal int fileAttributes; internal uint ftCreationTimeLow; @@ -379,25 +140,6 @@ public struct WIN32_FILE_ATTRIBUTE_DATA internal uint fileSizeLow; } - /// - /// Contains the security descriptor for an object and specifies whether - /// the handle retrieved by specifying this structure is inheritable. - /// - [StructLayout(LayoutKind.Sequential)] - internal class SecurityAttributes - { - public SecurityAttributes() - { - _nLength = (uint)Marshal.SizeOf(typeof(SecurityAttributes)); - } - - private uint _nLength; - - public IntPtr lpSecurityDescriptor; - - public bool bInheritHandle; - } - private class SystemInformationData { /// @@ -407,11 +149,6 @@ private class SystemInformationData /// public readonly ProcessorArchitectures ProcessorArchitectureType; - /// - /// Actual architecture of the system. - /// - public readonly ProcessorArchitectures ProcessorArchitectureTypeNative; - /// /// Convert SYSTEM_INFO architecture values to the internal enum /// @@ -435,16 +172,10 @@ private static ProcessorArchitectures ConvertSystemArchitecture(ushort arch) /// public SystemInformationData() { - ProcessorArchitectureType = ProcessorArchitectures.Unknown; - ProcessorArchitectureTypeNative = ProcessorArchitectures.Unknown; - var systemInfo = new SYSTEM_INFO(); GetSystemInfo(ref systemInfo); ProcessorArchitectureType = ConvertSystemArchitecture(systemInfo.wProcessorArchitecture); - - GetNativeSystemInfo(ref systemInfo); - ProcessorArchitectureTypeNative = ConvertSystemArchitecture(systemInfo.wProcessorArchitecture); } } @@ -470,7 +201,6 @@ public static int GetLogicalCoreCount() /// as Environment.ProcessorCount has a 32-core limit in that case. /// https://github.com/dotnet/runtime/blob/221ad5b728f93489655df290c1ea52956ad8f51c/src/libraries/System.Runtime.Extensions/src/System/Environment.Windows.cs#L171-L210 /// - [SupportedOSPlatform("windows")] private static unsafe int GetLogicalCoreCountOnWindows() { uint len = 0; @@ -500,8 +230,10 @@ private static unsafe int GetLogicalCoreCountOnWindows() // for now, assume "more than 1" == 2, as it has historically been for hyperthreading processorCount += (current->Processor.Flags == 0) ? 1 : 2; } + ptr += current->Size; } + return processorCount; } } @@ -510,10 +242,6 @@ private static unsafe int GetLogicalCoreCountOnWindows() return -1; } - #endregion - - #region Member data - internal static bool HasMaxPath => MaxPath == MAX_PATH; /// @@ -527,6 +255,7 @@ internal static int MaxPath { SetMaxPath(); } + return _maxPath; } } @@ -553,7 +282,7 @@ private static void SetMaxPath() } } - internal enum LongPathsStatus + private enum LongPathsStatus { /// /// The registry key is set to 0 or does not exist. @@ -576,7 +305,7 @@ internal enum LongPathsStatus NotApplicable, } - internal static LongPathsStatus IsLongPathsEnabled() + private static LongPathsStatus IsLongPathsEnabled() { try { @@ -588,13 +317,12 @@ internal static LongPathsStatus IsLongPathsEnabled() } } - internal static bool IsMaxPathLegacyWindows() + private static bool IsMaxPathLegacyWindows() { var longPathsStatus = IsLongPathsEnabled(); return longPathsStatus == LongPathsStatus.Disabled || longPathsStatus == LongPathsStatus.Missing; } - [SupportedOSPlatform("windows")] private static LongPathsStatus IsLongPathsEnabledRegistry() { using (RegistryKey fileSystemKey = Registry.LocalMachine.OpenSubKey(WINDOWS_FILE_SYSTEM_REGISTRY_KEY)) @@ -615,194 +343,6 @@ private static LongPathsStatus IsLongPathsEnabledRegistry() } } - private static SAC_State? s_sacState; - - /// - /// Get from registry state of the Smart App Control (SAC) on the system. - /// - /// State of SAC - internal static SAC_State GetSACState() - { - s_sacState ??= GetSACStateInternal(); - - return s_sacState.Value; - } - - internal static SAC_State GetSACStateInternal() - { - try - { - return GetSACStateRegistry(); - } - catch - { - return SAC_State.Missing; - } - } - - [SupportedOSPlatform("windows")] - private static SAC_State GetSACStateRegistry() - { - SAC_State SACState = SAC_State.Missing; - - using (RegistryKey policyKey = Registry.LocalMachine.OpenSubKey(WINDOWS_SAC_REGISTRY_KEY)) - { - if (policyKey != null) - { - object sacValue = policyKey.GetValue(WINDOWS_SAC_VALUE_NAME, -1); - SACState = Convert.ToInt32(sacValue) switch - { - 0 => SAC_State.Off, - 1 => SAC_State.Enforcement, - 2 => SAC_State.Evaluation, - _ => SAC_State.Missing, - }; - } - } - - return SACState; - } - - /// - /// State of Smart App Control (SAC) on the system. - /// - internal enum SAC_State - { - /// - /// 1: SAC is on and enforcing. - /// - Enforcement, - /// - /// 2: SAC is on and in evaluation mode. - /// - Evaluation, - /// - /// 0: SAC is off. - /// - Off, - /// - /// The registry key is missing. - /// - Missing, - /// - /// Not on Windows. - /// - NotApplicable - } - - /// - /// Cached value for IsUnixLike (this method is called frequently during evaluation). - /// - private static readonly bool s_isUnixLike = IsLinux || IsOSX || IsBSD; - - /// - /// Gets a flag indicating if we are running under a Unix-like system (Mac, Linux, etc.) - /// - internal static bool IsUnixLike - { - get { return s_isUnixLike; } - } - - /// - /// Gets a flag indicating if we are running under Linux - /// - [SupportedOSPlatformGuard("linux")] - internal static bool IsLinux - { - get { return false; } - } - - /// - /// Gets a flag indicating if we are running under flavor of BSD (NetBSD, OpenBSD, FreeBSD) - /// - internal static bool IsBSD - { - get { return false; } - } - - /// - /// Gets a flag indicating if we are running under some version of Windows - /// - [SupportedOSPlatformGuard("windows")] - internal static bool IsWindows - { - get { return true; } - } - - /// - /// Gets a flag indicating if we are running under Mac OSX - /// - internal static bool IsOSX - { - get { return false; } - } - - /// - /// Gets a string for the current OS. This matches the OS env variable - /// for Windows (Windows_NT). - /// - internal static string OSName - { - get { return IsWindows ? "Windows_NT" : "Unix"; } - } - - /// - /// Framework named as presented to users (for example in version info). - /// - internal static string FrameworkName => ".NET Framework"; - - /// - /// OS name that can be used for the msbuildExtensionsPathSearchPaths element - /// for a toolset - /// - internal static string GetOSNameForExtensionsPath() - { - return IsOSX ? "osx" : IsUnixLike ? "unix" : "windows"; - } - - internal static bool OSUsesCaseSensitivePaths - { - get { return IsLinux; } - } - - /// - /// The base directory for all framework paths in Mono - /// - private static string s_frameworkBasePath; - - /// - /// The directory of the current framework - /// - private static string s_frameworkCurrentPath; - - /// - /// Gets the currently running framework path. - /// - internal static string FrameworkCurrentPath - => s_frameworkCurrentPath ??= Path.GetDirectoryName(typeof(string).Assembly.Location) ?? string.Empty; - - /// - /// Gets the base directory of all Mono frameworks - /// - internal static string FrameworkBasePath - { - get - { - if (s_frameworkBasePath == null) - { - var dir = FrameworkCurrentPath; - if (dir != string.Empty) - { - dir = Path.GetDirectoryName(dir); - } - - s_frameworkBasePath = dir ?? string.Empty; - } - - return s_frameworkBasePath; - } - } - /// /// System information, initialized when required. /// @@ -838,268 +378,17 @@ private static SystemInformationData SystemInformation /// internal static ProcessorArchitectures ProcessorArchitecture => SystemInformation.ProcessorArchitectureType; - /// - /// Native architecture getter - /// - internal static ProcessorArchitectures ProcessorArchitectureNative => SystemInformation.ProcessorArchitectureTypeNative; - - #endregion - - #region Wrapper methods - - - [DllImport("kernel32.dll", SetLastError = true)] - [SupportedOSPlatform("windows")] - internal static extern void GetSystemInfo(ref SYSTEM_INFO lpSystemInfo); - [DllImport("kernel32.dll", SetLastError = true)] - [SupportedOSPlatform("windows")] - internal static extern void GetNativeSystemInfo(ref SYSTEM_INFO lpSystemInfo); + private static extern void GetSystemInfo(ref SYSTEM_INFO lpSystemInfo); [DllImport("kernel32.dll", SetLastError = true)] - [SupportedOSPlatform("windows")] - internal static extern bool GetLogicalProcessorInformationEx(LOGICAL_PROCESSOR_RELATIONSHIP RelationshipType, IntPtr Buffer, ref uint ReturnedLength); - - /// - /// Get the last write time of the fullpath to a directory. If the pointed path is not a directory, or - /// if the directory does not exist, then false is returned and fileModifiedTimeUtc is set DateTime.MinValue. - /// - /// Full path to the file in the filesystem - /// The UTC last write time for the directory - internal static bool GetLastWriteDirectoryUtcTime(string fullPath, out DateTime fileModifiedTimeUtc) - { - // This code was copied from the reference manager, if there is a bug fix in that code, see if the same fix should also be made - // there - fileModifiedTimeUtc = DateTime.MinValue; - - WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); - bool success = GetFileAttributesEx(fullPath, 0, ref data); - if (success) - { - if ((data.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) - { - long dt = ((long)(data.ftLastWriteTimeHigh) << 32) | ((long)data.ftLastWriteTimeLow); - fileModifiedTimeUtc = DateTime.FromFileTimeUtc(dt); - } - else - { - // Path does not point to a directory - success = false; - } - } - - return success; - } - - /// - /// Takes the path and returns the short path - /// - internal static string GetShortFilePath(string path) - { - if (path != null) - { - int length = GetShortPathName(path, null, 0); - int errorCode = Marshal.GetLastWin32Error(); - - if (length > 0) - { - char[] fullPathBuffer = new char[length]; - length = GetShortPathName(path, fullPathBuffer, length); - errorCode = Marshal.GetLastWin32Error(); - - if (length > 0) - { - string fullPath = new(fullPathBuffer, 0, length); - path = fullPath; - } - } - - if (length == 0 && errorCode != 0) - { - ThrowExceptionForErrorCode(errorCode); - } - } - - return path; - } - - /// - /// Takes the path and returns a full path - /// - /// - /// - [SupportedOSPlatform("windows")] - internal static string GetLongFilePath(string path) - { - if (path != null) - { - int length = GetLongPathName(path, null, 0); - int errorCode = Marshal.GetLastWin32Error(); - - if (length > 0) - { - char[] fullPathBuffer = new char[length]; - length = GetLongPathName(path, fullPathBuffer, length); - errorCode = Marshal.GetLastWin32Error(); - - if (length > 0) - { - string fullPath = new(fullPathBuffer, 0, length); - path = fullPath; - } - } - - if (length == 0 && errorCode != 0) - { - ThrowExceptionForErrorCode(errorCode); - } - } - - return path; - } - - /// - /// Retrieves the current global memory status. - /// - internal static MemoryStatus GetMemoryStatus() - { - MemoryStatus status = new MemoryStatus(); - bool returnValue = GlobalMemoryStatusEx(status); - if (!returnValue) - { - return null; - } - - return status; - } - - internal static bool MakeSymbolicLink(string newFileName, string existingFileName, ref string errorMessage) - { - Version osVersion = Environment.OSVersion.Version; - SymbolicLink flags = SymbolicLink.File; - if (osVersion.Major >= 11 || (osVersion.Major == 10 && osVersion.Build >= 14972)) - { - flags |= SymbolicLink.AllowUnprivilegedCreate; - } - - bool symbolicLinkCreated = CreateSymbolicLink(newFileName, existingFileName, flags); - errorMessage = symbolicLinkCreated ? null : Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()).Message; - - return symbolicLinkCreated; - } - - /// - /// Get the last write time of the fullpath to the file. - /// - /// Full path to the file in the filesystem - /// The last write time of the file, or DateTime.MinValue if the file does not exist. - /// - /// This method should be accurate for regular files and symlinks, but can report incorrect data - /// if the file's content was modified by writing to it through a different link, unless - /// MSBUILDALWAYSCHECKCONTENTTIMESTAMP=1. - /// - internal static DateTime GetLastWriteFileUtcTime(string fullPath) - { - return LastWriteFileUtcTime(fullPath); - - DateTime LastWriteFileUtcTime(string path) - { - DateTime fileModifiedTime = DateTime.MinValue; - - if (Traits.Instance.EscapeHatches.AlwaysUseContentTimestamp) - { - return GetContentLastWriteFileUtcTime(path); - } - - WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); - bool success = NativeMethods.GetFileAttributesEx(path, 0, ref data); - - if (success && (data.fileAttributes & NativeMethods.FILE_ATTRIBUTE_DIRECTORY) == 0) - { - long dt = ((long)(data.ftLastWriteTimeHigh) << 32) | ((long)data.ftLastWriteTimeLow); - fileModifiedTime = DateTime.FromFileTimeUtc(dt); - - // If file is a symlink _and_ we're not instructed to do the wrong thing, get a more accurate timestamp. - if ((data.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT && !Traits.Instance.EscapeHatches.UseSymlinkTimeInsteadOfTargetTime) - { - fileModifiedTime = GetContentLastWriteFileUtcTime(path); - } - } - - return fileModifiedTime; - } - } - - /// - /// Get the SafeFileHandle for a file, while skipping reparse points (going directly to target file). - /// - /// Full path to the file in the filesystem - /// the SafeFileHandle for a file (target file in case of symlinks) - [SupportedOSPlatform("windows")] - private static SafeFileHandle OpenFileThroughSymlinks(string fullPath) - { - return CreateFile(fullPath, - GENERIC_READ, - FILE_SHARE_READ, - IntPtr.Zero, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, /* No FILE_FLAG_OPEN_REPARSE_POINT; read through to content */ - IntPtr.Zero); - } - - /// - /// Get the last write time of the content pointed to by a file path. - /// - /// Full path to the file in the filesystem - /// The last write time of the file, or DateTime.MinValue if the file does not exist. - /// - /// This is the most accurate timestamp-extraction mechanism, but it is too slow to use all the time. - /// See https://github.com/dotnet/msbuild/issues/2052. - /// - [SupportedOSPlatform("windows")] - private static DateTime GetContentLastWriteFileUtcTime(string fullPath) - { - DateTime fileModifiedTime = DateTime.MinValue; - - using (SafeFileHandle handle = OpenFileThroughSymlinks(fullPath)) - { - if (!handle.IsInvalid) - { - FILETIME ftCreationTime, ftLastAccessTime, ftLastWriteTime; - if (GetFileTime(handle, out ftCreationTime, out ftLastAccessTime, out ftLastWriteTime)) - { - long fileTime = ((long)(uint)ftLastWriteTime.dwHighDateTime) << 32 | - (long)(uint)ftLastWriteTime.dwLowDateTime; - fileModifiedTime = - DateTime.FromFileTimeUtc(fileTime); - } - } - } - - return fileModifiedTime; - } - - /// - /// Did the HRESULT succeed - /// - public static bool HResultSucceeded(int hr) - { - return hr >= 0; - } - - /// - /// Did the HRESULT Fail - /// - public static bool HResultFailed(int hr) - { - return hr < 0; - } + private static extern bool GetLogicalProcessorInformationEx(LOGICAL_PROCESSOR_RELATIONSHIP RelationshipType, IntPtr Buffer, ref uint ReturnedLength); /// /// Given an error code, converts it to an HRESULT and throws the appropriate exception. /// /// - public static void ThrowExceptionForErrorCode(int errorCode) + private static void ThrowExceptionForErrorCode(int errorCode) { // See ndp\clr\src\bcl\system\io\__error.cs for this code as it appears in the CLR. @@ -1114,160 +403,6 @@ public static void ThrowExceptionForErrorCode(int errorCode) Marshal.ThrowExceptionForHR(errorCode); } - /// - /// Kills the specified process by id and all of its children recursively. - /// - [SupportedOSPlatform("windows")] - internal static void KillTree(int processIdToKill) - { - // Note that GetProcessById does *NOT* internally hold on to the process handle. - // Only when you create the process using the Process object - // does the Process object retain the original handle. - - Process thisProcess; - try - { - thisProcess = Process.GetProcessById(processIdToKill); - } - catch (ArgumentException) - { - // The process has already died for some reason. So shrug and assume that any child processes - // have all also either died or are in the process of doing so. - return; - } - - try - { - DateTime myStartTime = thisProcess.StartTime; - - // Grab the process handle. We want to keep this open for the duration of the function so that - // it cannot be reused while we are running. - using (SafeProcessHandle hProcess = OpenProcess(eDesiredAccess.PROCESS_QUERY_INFORMATION, false, processIdToKill)) - { - if (hProcess.IsInvalid) - { - return; - } - - try - { - // Kill this process, so that no further children can be created. - thisProcess.Kill(); - } - catch (Win32Exception e) when (e.NativeErrorCode == ERROR_ACCESS_DENIED) - { - // Access denied is potentially expected -- it happens when the process that - // we're attempting to kill is already dead. So just ignore in that case. - } - - // Now enumerate our children. Children of this process are any process which has this process id as its parent - // and which also started after this process did. - List> children = GetChildProcessIds(processIdToKill, myStartTime); - - try - { - foreach (KeyValuePair childProcessInfo in children) - { - KillTree(childProcessInfo.Key); - } - } - finally - { - foreach (KeyValuePair childProcessInfo in children) - { - childProcessInfo.Value.Dispose(); - } - } - } - } - finally - { - thisProcess.Dispose(); - } - } - - /// - /// Returns the parent process id for the specified process. - /// Returns zero if it cannot be gotten for some reason. - /// - [SupportedOSPlatform("windows")] - internal static int GetParentProcessId(int processId) - { - int ParentID = 0; - using SafeProcessHandle hProcess = OpenProcess(eDesiredAccess.PROCESS_QUERY_INFORMATION, false, processId); - { - if (!hProcess.IsInvalid) - { - // UNDONE: NtQueryInformationProcess will fail if we are not elevated and other process is. Advice is to change to use ToolHelp32 API's - // For now just return zero and worst case we will not kill some children. - PROCESS_BASIC_INFORMATION pbi = new PROCESS_BASIC_INFORMATION(); - int pSize = 0; - - if (0 == NtQueryInformationProcess(hProcess, PROCESSINFOCLASS.ProcessBasicInformation, ref pbi, pbi.Size, ref pSize)) - { - ParentID = (int)pbi.InheritedFromUniqueProcessId; - } - } - } - - return ParentID; - } - - /// - /// Returns an array of all the immediate child processes by id. - /// NOTE: The IntPtr in the tuple is the handle of the child process. CloseHandle MUST be called on this. - /// - [SupportedOSPlatform("windows")] - internal static List> GetChildProcessIds(int parentProcessId, DateTime parentStartTime) - { - List> myChildren = new List>(); - - foreach (Process possibleChildProcess in Process.GetProcesses()) - { - using (possibleChildProcess) - { - // Hold the child process handle open so that children cannot die and restart with a different parent after we've started looking at it. - // This way, any handle we pass back is guaranteed to be one of our actual children. -#pragma warning disable CA2000 // Dispose objects before losing scope - caller must dispose returned handles - SafeProcessHandle childHandle = OpenProcess(eDesiredAccess.PROCESS_QUERY_INFORMATION, false, possibleChildProcess.Id); -#pragma warning restore CA2000 // Dispose objects before losing scope - { - if (childHandle.IsInvalid) - { - continue; - } - - bool keepHandle = false; - try - { - if (possibleChildProcess.StartTime > parentStartTime) - { - int childParentProcessId = GetParentProcessId(possibleChildProcess.Id); - if (childParentProcessId != 0) - { - if (parentProcessId == childParentProcessId) - { - // Add this one - myChildren.Add(new KeyValuePair(possibleChildProcess.Id, childHandle)); - keepHandle = true; - } - } - } - } - finally - { - if (!keepHandle) - { - childHandle.Dispose(); - } - } - } - } - } - - return myChildren; - } - /// /// Internal, optimized GetCurrentDirectory implementation that simply delegates to the native method. /// @@ -1281,7 +416,6 @@ internal static unsafe string GetCurrentDirectory() return new string(buffer, startIndex: 0, length); } - [SupportedOSPlatform("windows")] private static unsafe int GetCurrentDirectoryWin32(int nBufferLength, char* lpBuffer) { int pathLength = GetCurrentDirectory(nBufferLength, lpBuffer); @@ -1289,7 +423,6 @@ private static unsafe int GetCurrentDirectoryWin32(int nBufferLength, char* lpBu return pathLength; } - [SupportedOSPlatform("windows")] internal static unsafe string GetFullPath(string path) { char* buffer = stackalloc char[MAX_PATH]; @@ -1308,7 +441,6 @@ internal static unsafe string GetFullPath(string path) return AreStringsEqual(buffer, fullPathLength, path) ? path : new string(buffer, startIndex: 0, length: fullPathLength); } - [SupportedOSPlatform("windows")] private static unsafe int GetFullPathWin32(string target, int bufferLength, char* buffer, IntPtr mustBeZero) { int pathLength = GetFullPathName(target, bufferLength, buffer, mustBeZero); @@ -1341,7 +473,7 @@ private static unsafe bool AreStringsEqual(char* buffer, int len, string s) return true; } - internal static void VerifyThrowWin32Result(int result) + private static void VerifyThrowWin32Result(int result) { bool isError = result == 0; if (isError) @@ -1351,212 +483,42 @@ internal static void VerifyThrowWin32Result(int result) } } - #endregion - - #region PInvoke - [SupportedOSPlatform("linux")] - [DllImport("libc", SetLastError = true)] - internal static extern int chmod(string pathname, int mode); - - [SupportedOSPlatform("linux")] - [DllImport("libc", SetLastError = true)] - internal static extern int mkdir(string path, int mode); - - /// - /// Gets the current OEM code page which is used by console apps - /// (as opposed to the Windows/ANSI code page) - /// Basically for each ANSI code page (set in Regional settings) there's a corresponding OEM code page - /// that needs to be used for instance when writing to batch files - /// - [DllImport(kernel32Dll)] - [SupportedOSPlatform("windows")] - internal static extern int GetOEMCP(); - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] - [SupportedOSPlatform("windows")] - internal static extern bool GetFileAttributesEx(String name, int fileInfoLevel, ref WIN32_FILE_ATTRIBUTE_DATA lpFileInformation); - - [DllImport("kernel32.dll", PreserveSig = true, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - [SupportedOSPlatform("windows")] - internal static extern bool FreeLibrary([In] IntPtr module); - - [DllImport("kernel32.dll", PreserveSig = true, BestFitMapping = false, ThrowOnUnmappableChar = true, CharSet = CharSet.Ansi, SetLastError = true)] - [SupportedOSPlatform("windows")] - internal static extern IntPtr GetProcAddress(IntPtr module, string procName); - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, PreserveSig = true, SetLastError = true)] - [SupportedOSPlatform("windows")] - internal static extern IntPtr LoadLibrary(string fileName); - - /// - /// Gets the fully qualified filename of the currently executing .exe. - /// - /// of the module for which we are finding the file name. - /// The character buffer used to return the file name. - /// The length of the buffer. - [DllImport(kernel32Dll, SetLastError = true, CharSet = CharSet.Unicode)] - [SupportedOSPlatform("windows")] - internal static extern int GetModuleFileName(HandleRef hModule, [Out] char[] buffer, int length); - - [DllImport("kernel32.dll")] - [SupportedOSPlatform("windows")] - internal static extern IntPtr GetStdHandle(int nStdHandle); - - [DllImport("kernel32.dll")] - [SupportedOSPlatform("windows")] - internal static extern uint GetFileType(IntPtr hFile); - - [DllImport("kernel32.dll")] - internal static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); - - [DllImport("kernel32.dll")] - internal static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); + private static extern bool GetFileAttributesEx(String name, int fileInfoLevel, ref WIN32_FILE_ATTRIBUTE_DATA lpFileInformation); [SuppressMessage("Microsoft.Usage", "CA2205:UseManagedEquivalentsOfWin32Api", Justification = "Using unmanaged equivalent for performance reasons")] [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - [SupportedOSPlatform("windows")] - internal static extern unsafe int GetCurrentDirectory(int nBufferLength, char* lpBuffer); + private static extern unsafe int GetCurrentDirectory(int nBufferLength, char* lpBuffer); [SuppressMessage("Microsoft.Usage", "CA2205:UseManagedEquivalentsOfWin32Api", Justification = "Using unmanaged equivalent for performance reasons")] [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetCurrentDirectory")] [return: MarshalAs(UnmanagedType.Bool)] - [SupportedOSPlatform("windows")] - internal static extern bool SetCurrentDirectoryWindows(string path); + private static extern bool SetCurrentDirectoryWindows(string path); internal static bool SetCurrentDirectory(string path) - { - return SetCurrentDirectoryWindows(path); - } + => SetCurrentDirectoryWindows(path); [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - [SupportedOSPlatform("windows")] - internal static extern unsafe int GetFullPathName(string target, int bufferLength, char* buffer, IntPtr mustBeZero); - - [DllImport("KERNEL32.DLL")] - [SupportedOSPlatform("windows")] - private static extern SafeProcessHandle OpenProcess(eDesiredAccess dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId); - - [DllImport("NTDLL.DLL")] - [SupportedOSPlatform("windows")] - private static extern int NtQueryInformationProcess(SafeProcessHandle hProcess, PROCESSINFOCLASS pic, ref PROCESS_BASIC_INFORMATION pbi, uint cb, ref int pSize); - - [return: MarshalAs(UnmanagedType.Bool)] - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - [SupportedOSPlatform("windows")] - private static extern bool GlobalMemoryStatusEx([In, Out] MemoryStatus lpBuffer); - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, BestFitMapping = false)] - [SupportedOSPlatform("windows")] - internal static extern int GetShortPathName(string path, [Out] char[] fullpath, [In] int length); + private static extern unsafe int GetFullPathName(string target, int bufferLength, char* buffer, IntPtr mustBeZero); - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, BestFitMapping = false)] - [SupportedOSPlatform("windows")] - internal static extern int GetLongPathName([In] string path, [Out] char[] fullpath, [In] int length); - - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - [SupportedOSPlatform("windows")] - internal static extern bool CreatePipe(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, SecurityAttributes lpPipeAttributes, int nSize); - - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - [SupportedOSPlatform("windows")] - internal static extern bool ReadFile(SafeFileHandle hFile, byte[] lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped); - - /// - /// CoWaitForMultipleHandles allows us to wait in an STA apartment and still service RPC requests from other threads. - /// VS needs this in order to allow the in-proc compilers to properly initialize, since they will make calls from the - /// build thread which the main thread (blocked on BuildSubmission.Execute) must service. - /// - [DllImport("ole32.dll")] - [SupportedOSPlatform("windows")] - public static extern int CoWaitForMultipleHandles(COWAIT_FLAGS dwFlags, int dwTimeout, int cHandles, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] pHandles, out int pdwIndex); - - internal const uint GENERIC_READ = 0x80000000; - internal const uint FILE_SHARE_READ = 0x1; - internal const uint FILE_ATTRIBUTE_NORMAL = 0x80; - internal const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000; - internal const uint OPEN_EXISTING = 3; - - [DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, - SetLastError = true)] - [SupportedOSPlatform("windows")] - internal static extern SafeFileHandle CreateFile( - string lpFileName, - uint dwDesiredAccess, - uint dwShareMode, - IntPtr lpSecurityAttributes, - uint dwCreationDisposition, - uint dwFlagsAndAttributes, - IntPtr hTemplateFile); - - [DllImport("kernel32.dll", SetLastError = true)] - [SupportedOSPlatform("windows")] - internal static extern bool GetFileTime( - SafeFileHandle hFile, - out FILETIME lpCreationTime, - out FILETIME lpLastAccessTime, - out FILETIME lpLastWriteTime); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - [SupportedOSPlatform("windows")] - internal static extern bool CloseHandle(IntPtr hObject); - - [DllImport("kernel32.dll", SetLastError = true)] - [SupportedOSPlatform("windows")] - internal static extern bool SetThreadErrorMode(int newMode, out int oldMode); - - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - [return: MarshalAs(UnmanagedType.I1)] - [SupportedOSPlatform("windows")] - internal static extern bool CreateSymbolicLink(string symLinkFileName, string targetFileName, SymbolicLink dwFlags); - - [DllImport("libc", SetLastError = true)] - internal static extern int symlink(string oldpath, string newpath); - - #endregion - - #region helper methods - - internal static bool DirectoryExists(string fullPath) - { - return DirectoryExistsWindows(fullPath); - } - - [SupportedOSPlatform("windows")] - internal static bool DirectoryExistsWindows(string fullPath) + public static bool DirectoryExists(string fullPath) { WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); bool success = GetFileAttributesEx(fullPath, 0, ref data); return success && (data.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } - internal static bool FileExists(string fullPath) - { - return FileExistsWindows(fullPath); - } - - [SupportedOSPlatform("windows")] - internal static bool FileExistsWindows(string fullPath) + public static bool FileExists(string fullPath) { WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); bool success = GetFileAttributesEx(fullPath, 0, ref data); return success && (data.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0; } - internal static bool FileOrDirectoryExists(string path) - { - return FileOrDirectoryExistsWindows(path); - } - - [SupportedOSPlatform("windows")] - internal static bool FileOrDirectoryExistsWindows(string path) + public static bool FileOrDirectoryExists(string path) { WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); return GetFileAttributesEx(path, 0, ref data); } - - #endregion - } From 9cb0e1e1d440c743bdcf9473fd1d0ec157d3bac7 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 13:47:19 -0800 Subject: [PATCH 050/136] MSBuildTaskHost: Remove MSBuildNameIgnoringCaseComparer MSBuildTaskHost doesn't get much benefit from MSBuildNameIgnoreCaseComparer over using StringComparer.OrdinalIgnoreCase. So, this change removes MSBuildNameIgnoreCaseComparer.cs and IConstrainedEqualityComparer.cs from MSBuildTaskHost rather than include the extra complexity. --- .../Framework/IConstrainedEqualityComparer.cs | 26 --- .../MSBuildNameIgnoreCaseComparer.cs | 190 ------------------ src/MSBuildTaskHost/MSBuildTaskHost.csproj | 2 - src/MSBuildTaskHost/TaskParameter.cs | 15 +- 4 files changed, 5 insertions(+), 228 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/IConstrainedEqualityComparer.cs delete mode 100644 src/MSBuildTaskHost/Framework/MSBuildNameIgnoreCaseComparer.cs diff --git a/src/MSBuildTaskHost/Framework/IConstrainedEqualityComparer.cs b/src/MSBuildTaskHost/Framework/IConstrainedEqualityComparer.cs deleted file mode 100644 index 1ef95844f9a..00000000000 --- a/src/MSBuildTaskHost/Framework/IConstrainedEqualityComparer.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; - -#nullable disable - -namespace Microsoft.Build.Collections -{ - /// - /// Defines methods to support the comparison of objects for - /// equality over constrained inputs. - /// - internal interface IConstrainedEqualityComparer : IEqualityComparer - { - /// - /// Determines whether the specified objects are equal, factoring in the specified bounds when comparing . - /// - bool Equals(T x, T y, int indexY, int length); - - /// - /// Returns a hash code for the specified object factoring in the specified bounds. - /// - int GetHashCode(T obj, int index, int length); - } -} diff --git a/src/MSBuildTaskHost/Framework/MSBuildNameIgnoreCaseComparer.cs b/src/MSBuildTaskHost/Framework/MSBuildNameIgnoreCaseComparer.cs deleted file mode 100644 index 21fc4f862af..00000000000 --- a/src/MSBuildTaskHost/Framework/MSBuildNameIgnoreCaseComparer.cs +++ /dev/null @@ -1,190 +0,0 @@ -// 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; -using Microsoft.Build.Framework; - -#nullable disable - -namespace Microsoft.Build.Collections -{ - /// - /// This is a custom string comparer that has three advantages over the regular - /// string comparer: - /// 1) It can generate hash codes and perform equivalence operations on parts of a string rather than a whole - /// 2) It uses "unsafe" pointers to maximize performance of those operations - /// 3) It takes advantage of limitations on MSBuild Property/Item names to cheaply do case insensitive comparison. - /// - [Serializable] - internal class MSBuildNameIgnoreCaseComparer : IConstrainedEqualityComparer, IEqualityComparer - { - /// - /// The processor architecture on which we are running, but default it will be x86 - /// - private static readonly NativeMethods.ProcessorArchitectures s_runningProcessorArchitecture = NativeMethods.ProcessorArchitecture; - - /// - /// The default immutable comparer instance. - /// - internal static MSBuildNameIgnoreCaseComparer Default { get; } = new MSBuildNameIgnoreCaseComparer(); - - public bool Equals(string x, string y) - { - return Equals(x, y, 0, y?.Length ?? 0); - } - - public int GetHashCode(string obj) - { - return GetHashCode(obj, 0, obj?.Length ?? 0); - } - - /// - /// Performs the "Equals" operation on two MSBuild property, item or metadata names - /// - public bool Equals(string compareToString, string constrainedString, int start, int lengthToCompare) - { - if (lengthToCompare < 0) - { - EscapeHatches.ThrowInternalError("Invalid lengthToCompare '{0}' {1} {2}", constrainedString, start, lengthToCompare); - } - - if (start < 0 || start > (constrainedString?.Length ?? 0) - lengthToCompare) - { - EscapeHatches.ThrowInternalError("Invalid start '{0}' {1} {2}", constrainedString, start, lengthToCompare); - } - - if (ReferenceEquals(compareToString, constrainedString)) - { - return true; - } - - if (compareToString == null || constrainedString == null) - { - return false; - } - - if (lengthToCompare != compareToString.Length) - { - return false; - } - - if ((s_runningProcessorArchitecture != NativeMethods.ProcessorArchitectures.IA64) - && (s_runningProcessorArchitecture != NativeMethods.ProcessorArchitectures.ARM)) - { - // The use of unsafe here is quite a bit faster than the regular - // mechanism in the BCL. This is because we can make assumptions - // about the characters that are within the strings being compared - // i.e. they are valid MSBuild property, item and metadata names - unsafe - { - fixed (char* px = compareToString) - { - fixed (char* py = constrainedString) - { - for (int i = 0; i < compareToString.Length; i++) - { - int chx = px[i]; - int chy = py[i + start]; - chx &= 0x00DF; // Extract the uppercase character - chy &= 0x00DF; // Extract the uppercase character - - if (chx != chy) - { - return false; - } - } - } - } - } - } - else - { - return String.Compare(compareToString, 0, constrainedString, start, lengthToCompare, StringComparison.OrdinalIgnoreCase) == 0; - } - - return true; - } - - /// - /// Getting a case insensitive hash code for the msbuild property, item or metadata name - /// - public int GetHashCode(string obj, int start, int length) - { - if (obj == null) - { - return 0; // per BCL convention - } - - if ((s_runningProcessorArchitecture != NativeMethods.ProcessorArchitectures.IA64) - && (s_runningProcessorArchitecture != NativeMethods.ProcessorArchitectures.ARM)) - { - unsafe - { - // This algorithm is based on the 32bit version from the CLR's string::GetHashCode - fixed (char* src = obj) - { - int hash1 = (5381 << 16) + 5381; - - int hash2 = hash1; - - char* src2 = src + start; - var pint = (int*)src2; - - while (length > 0) - { - // We're only interested in uppercase ASCII characters - int val = pint[0] & 0x00DF00DF; - - // When we reach the end of the string, we need to - // stop short when gathering our data to compute the - // hash code - we are only interested in the data within - // the string, and not the null terminator etc. - if (length == 1) - { - if (BitConverter.IsLittleEndian) - { - val &= 0xFFFF; - } - else - { - val &= unchecked((int)0xFFFF0000); - } - } - - hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ val; - if (length <= 2) - { - break; - } - - // Once again we're only interested in the uppercase ASCII characters - val = pint[1] & 0x00DF00DF; - if (length == 3) - { - if (BitConverter.IsLittleEndian) - { - val &= 0xFFFF; - } - else - { - val &= unchecked((int)0xFFFF0000); - } - } - - hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ val; - pint += 2; - length -= 4; - } - - return hash1 + (hash2 * 1566083941); - } - } - } - else - { - return StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Substring(start, length)); - } - } - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 8586a3fc098..e8ff91ba6b1 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -68,7 +68,6 @@ - @@ -77,7 +76,6 @@ - diff --git a/src/MSBuildTaskHost/TaskParameter.cs b/src/MSBuildTaskHost/TaskParameter.cs index 53f160099ef..8785feab094 100644 --- a/src/MSBuildTaskHost/TaskParameter.cs +++ b/src/MSBuildTaskHost/TaskParameter.cs @@ -5,11 +5,6 @@ using System.Collections; using System.Collections.Generic; using System.Globalization; -using System.Linq; -using System.Reflection; -using Microsoft.Build.Collections; - -using System.Runtime.Remoting; using System.Security; using Microsoft.Build.Framework; using Microsoft.Build.Shared; @@ -564,7 +559,7 @@ internal TaskParameterTaskItem(ITaskItem copyFrom) if (_customEscapedMetadata is null) { - _customEscapedMetadata = new Dictionary(MSBuildNameIgnoreCaseComparer.Default); + _customEscapedMetadata = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (DictionaryEntry entry in nonGenericEscapedMetadata) { _customEscapedMetadata[(string)entry.Key] = (string)entry.Value ?? string.Empty; @@ -581,7 +576,7 @@ internal TaskParameterTaskItem(ITaskItem copyFrom) _escapedDefiningProject = EscapingUtilities.EscapeWithCaching(copyFrom.GetMetadata(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath)); IDictionary customMetadata = copyFrom.CloneCustomMetadata(); - _customEscapedMetadata = new Dictionary(MSBuildNameIgnoreCaseComparer.Default); + _customEscapedMetadata = new Dictionary(StringComparer.OrdinalIgnoreCase); if (customMetadata?.Count > 0) { @@ -689,7 +684,7 @@ public void SetMetadata(string metadataName, string metadataValue) // That's why this is IsItemSpecModifier and not IsDerivableItemSpecModifier. ErrorUtilities.VerifyThrowArgument(!FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(metadataName), "Shared.CannotChangeItemSpecModifiers", metadataName); - _customEscapedMetadata ??= new Dictionary(MSBuildNameIgnoreCaseComparer.Default); + _customEscapedMetadata ??= new Dictionary(StringComparer.OrdinalIgnoreCase); _customEscapedMetadata[metadataName] = metadataValue ?? String.Empty; } @@ -759,7 +754,7 @@ public void CopyMetadataTo(ITaskItem destinationItem) /// Dictionary of cloned metadata public IDictionary CloneCustomMetadata() { - IDictionary clonedMetadata = new Dictionary(MSBuildNameIgnoreCaseComparer.Default); + IDictionary clonedMetadata = new Dictionary(StringComparer.OrdinalIgnoreCase); if (_customEscapedMetadata != null) { @@ -885,7 +880,7 @@ public void Translate(ITranslator translator) { translator.Translate(ref _escapedItemSpec); translator.Translate(ref _escapedDefiningProject); - translator.TranslateDictionary(ref _customEscapedMetadata, MSBuildNameIgnoreCaseComparer.Default); + translator.TranslateDictionary(ref _customEscapedMetadata, StringComparer.OrdinalIgnoreCase); ErrorUtilities.VerifyThrowInternalNull(_escapedItemSpec); ErrorUtilities.VerifyThrowInternalNull(_customEscapedMetadata); From b3f7c2530cad54b007c40fb87d7bb0e6b4acd6e5 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 14:19:58 -0800 Subject: [PATCH 051/136] MSBuildTaskHost: Replace ProcessorArchitectures with simple Is64Bit test There's a fair amount of code in BuildEnvironmentHelper that handles the case where the code is running on ARM64. However, MSBuildTaskHost.exe doesn't support ARM64 because it runs on .NET 3.5. All of this code can be removed and replaced with a simple NativeMethod.Is64Bit property. --- src/MSBuildTaskHost/BuildEnvironmentHelper.cs | 31 +--- .../Framework/NativeMethods.cs | 146 +----------------- 2 files changed, 9 insertions(+), 168 deletions(-) diff --git a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs index f2aeaad9a5c..151bc3e8bef 100644 --- a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs +++ b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs @@ -291,23 +291,19 @@ private static string GetVsRootFromMSBuildAssembly(string msBuildAssembly) { string directory = Path.GetDirectoryName(msBuildAssembly); return FileUtilities.GetFolderAbove(msBuildAssembly, - directory.EndsWith(@"\amd64", StringComparison.OrdinalIgnoreCase) || - directory.EndsWith(@"\arm64", StringComparison.OrdinalIgnoreCase) + directory.EndsWith(@"\amd64", StringComparison.OrdinalIgnoreCase) ? 5 : 4); } private static string GetMSBuildExeFromVsRoot(string visualStudioRoot) - { - return FileUtilities.CombinePaths( + => FileUtilities.CombinePaths( visualStudioRoot, "MSBuild", CurrentToolsVersion, "Bin", - NativeMethodsShared.ProcessorArchitecture == Framework.NativeMethods.ProcessorArchitectures.X64 ? "amd64" : - NativeMethodsShared.ProcessorArchitecture == Framework.NativeMethods.ProcessorArchitectures.ARM64 ? "arm64" : string.Empty, + NativeMethodsShared.Is64Bit ? "amd64" : string.Empty, "MSBuild.exe"); - } private static bool? _runningTests; private static readonly LockType _runningTestsLock = new LockType(); @@ -482,13 +478,9 @@ public BuildEnvironment(BuildEnvironmentMode mode, string currentMSBuildExePath, if (mode == BuildEnvironmentMode.VisualStudio) { // In Visual Studio, the entry-point MSBuild.exe is often from an arch-specific subfolder - MSBuildToolsDirectoryRoot = NativeMethodsShared.ProcessorArchitecture switch - { - NativeMethodsShared.ProcessorArchitectures.X86 => CurrentMSBuildToolsDirectory, - NativeMethodsShared.ProcessorArchitectures.X64 or NativeMethodsShared.ProcessorArchitectures.ARM64 - => currentToolsDirectory.Parent?.FullName, - _ => throw new InternalErrorException("Unknown processor architecture " + NativeMethodsShared.ProcessorArchitecture), - }; + MSBuildToolsDirectoryRoot = !NativeMethodsShared.Is64Bit + ? CurrentMSBuildToolsDirectory + : currentToolsDirectory.Parent?.FullName; } else { @@ -496,8 +488,7 @@ NativeMethodsShared.ProcessorArchitectures.X64 or NativeMethodsShared.ProcessorA MSBuildToolsDirectoryRoot = CurrentMSBuildToolsDirectory; // If we're standalone, we might not be in the SDK. Rely on folder paths at this point. - if (string.Equals(currentToolsDirectory.Name, "amd64", StringComparison.OrdinalIgnoreCase) || - string.Equals(currentToolsDirectory.Name, "arm64", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(currentToolsDirectory.Name, "amd64", StringComparison.OrdinalIgnoreCase)) { MSBuildToolsDirectoryRoot = currentToolsDirectory.Parent?.FullName; } @@ -507,7 +498,6 @@ NativeMethodsShared.ProcessorArchitectures.X64 or NativeMethodsShared.ProcessorA { // Calculate potential paths to other architecture MSBuild.exe var potentialAmd64FromX86 = FileUtilities.CombinePaths(MSBuildToolsDirectoryRoot, "amd64", msBuildExeName); - var potentialARM64FromX86 = FileUtilities.CombinePaths(MSBuildToolsDirectoryRoot, "arm64", msBuildExeName); // Check for existence of an MSBuild file. Note this is not necessary in a VS installation where we always want to // assume the correct layout. @@ -515,7 +505,6 @@ NativeMethodsShared.ProcessorArchitectures.X64 or NativeMethodsShared.ProcessorA MSBuildToolsDirectory32 = MSBuildToolsDirectoryRoot; MSBuildToolsDirectory64 = existsCheck(potentialAmd64FromX86) ? Path.Combine(MSBuildToolsDirectoryRoot, "amd64") : CurrentMSBuildToolsDirectory; - MSBuildToolsDirectoryArm64 = existsCheck(potentialARM64FromX86) ? Path.Combine(MSBuildToolsDirectoryRoot, "arm64") : null; } MSBuildExtensionsPath = mode == BuildEnvironmentMode.VisualStudio @@ -555,12 +544,6 @@ NativeMethodsShared.ProcessorArchitectures.X64 or NativeMethodsShared.ProcessorA /// internal string MSBuildToolsDirectory64 { get; } - /// - /// Path to the ARM64 tools directory. - /// if ARM64 tools are not installed. - /// - internal string MSBuildToolsDirectoryArm64 { get; } - /// /// Path to the Sdks folder for this MSBuild instance. /// diff --git a/src/MSBuildTaskHost/Framework/NativeMethods.cs b/src/MSBuildTaskHost/Framework/NativeMethods.cs index b74cf9c9ea5..c0355210591 100644 --- a/src/MSBuildTaskHost/Framework/NativeMethods.cs +++ b/src/MSBuildTaskHost/Framework/NativeMethods.cs @@ -12,6 +12,8 @@ namespace Microsoft.Build.Framework; internal static class NativeMethods { + public static bool Is64Bit => IntPtr.Size == 8; + private const int FILE_ATTRIBUTE_DIRECTORY = 0x00000010; /// @@ -22,13 +24,6 @@ internal static class NativeMethods private const string WINDOWS_FILE_SYSTEM_REGISTRY_KEY = @"SYSTEM\CurrentControlSet\Control\FileSystem"; private const string WINDOWS_LONG_PATHS_ENABLED_VALUE_NAME = "LongPathsEnabled"; - // As defined in winnt.h: - private const ushort PROCESSOR_ARCHITECTURE_INTEL = 0; - private const ushort PROCESSOR_ARCHITECTURE_ARM = 5; - private const ushort PROCESSOR_ARCHITECTURE_IA64 = 6; - private const ushort PROCESSOR_ARCHITECTURE_AMD64 = 9; - private const ushort PROCESSOR_ARCHITECTURE_ARM64 = 12; - private enum LOGICAL_PROCESSOR_RELATIONSHIP { RelationProcessorCore, @@ -63,66 +58,6 @@ private unsafe struct PROCESSOR_RELATIONSHIP public IntPtr GroupInfo; } - /// - /// Processor architecture values - /// - public enum ProcessorArchitectures - { - // Intel 32 bit - X86, - - // AMD64 64 bit - X64, - - // Itanium 64 - IA64, - - // ARM - ARM, - - // ARM64 - ARM64, - - // WebAssembly - WASM, - - // S390x - S390X, - - // LongAarch64 - LOONGARCH64, - - // 32-bit ARMv6 - ARMV6, - - // PowerPC 64-bit (little-endian) - PPC64LE, - - // Who knows - Unknown - } - - /// - /// Structure that contain information about the system on which we are running - /// - [StructLayout(LayoutKind.Sequential)] - private struct SYSTEM_INFO - { - // This is a union of a DWORD and a struct containing 2 WORDs. - internal ushort wProcessorArchitecture; - internal ushort wReserved; - - internal uint dwPageSize; - internal IntPtr lpMinimumApplicationAddress; - internal IntPtr lpMaximumApplicationAddress; - internal IntPtr dwActiveProcessorMask; - internal uint dwNumberOfProcessors; - internal uint dwProcessorType; - internal uint dwAllocationGranularity; - internal ushort wProcessorLevel; - internal ushort wProcessorRevision; - } - /// /// Contains information about a file or directory; used by GetFileAttributesEx. /// @@ -140,45 +75,6 @@ private struct WIN32_FILE_ATTRIBUTE_DATA internal uint fileSizeLow; } - private class SystemInformationData - { - /// - /// Architecture as far as the current process is concerned. - /// It's x86 in wow64 (native architecture is x64 in that case). - /// Otherwise it's the same as the native architecture. - /// - public readonly ProcessorArchitectures ProcessorArchitectureType; - - /// - /// Convert SYSTEM_INFO architecture values to the internal enum - /// - /// - /// - private static ProcessorArchitectures ConvertSystemArchitecture(ushort arch) - { - return arch switch - { - PROCESSOR_ARCHITECTURE_INTEL => ProcessorArchitectures.X86, - PROCESSOR_ARCHITECTURE_AMD64 => ProcessorArchitectures.X64, - PROCESSOR_ARCHITECTURE_ARM => ProcessorArchitectures.ARM, - PROCESSOR_ARCHITECTURE_IA64 => ProcessorArchitectures.IA64, - PROCESSOR_ARCHITECTURE_ARM64 => ProcessorArchitectures.ARM64, - _ => ProcessorArchitectures.Unknown, - }; - } - - /// - /// Read system info values - /// - public SystemInformationData() - { - var systemInfo = new SYSTEM_INFO(); - - GetSystemInfo(ref systemInfo); - ProcessorArchitectureType = ConvertSystemArchitecture(systemInfo.wProcessorArchitecture); - } - } - public static int GetLogicalCoreCount() { int numberOfCpus = Environment.ProcessorCount; @@ -343,44 +239,6 @@ private static LongPathsStatus IsLongPathsEnabledRegistry() } } - /// - /// System information, initialized when required. - /// - /// - /// Initially implemented as , but - /// that's .NET 4+, and this is used in MSBuildTaskHost. - /// - private static SystemInformationData SystemInformation - { - get - { - if (!_systemInformationInitialized) - { - lock (SystemInformationLock) - { - if (!_systemInformationInitialized) - { - _systemInformation = new SystemInformationData(); - _systemInformationInitialized = true; - } - } - } - return _systemInformation; - } - } - - private static SystemInformationData _systemInformation; - private static bool _systemInformationInitialized; - private static readonly LockType SystemInformationLock = new LockType(); - - /// - /// Architecture getter - /// - internal static ProcessorArchitectures ProcessorArchitecture => SystemInformation.ProcessorArchitectureType; - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern void GetSystemInfo(ref SYSTEM_INFO lpSystemInfo); - [DllImport("kernel32.dll", SetLastError = true)] private static extern bool GetLogicalProcessorInformationEx(LOGICAL_PROCESSOR_RELATIONSHIP RelationshipType, IntPtr Buffer, ref uint ReturnedLength); From 3f8adee762c43467eb02d4577541cb9e6f792d11 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 14:26:08 -0800 Subject: [PATCH 052/136] MSBuildTaskHost: Remove XMakeAttributes.cs The only bits left in XMakeAttributes are for checking MSBuild's architecture and runtime. This change formalizes that and removes XMakeAttributes.cs. --- .../CommunicationsUtilities.cs | 18 ++++---- src/MSBuildTaskHost/MSBuildArchitecture.cs | 22 +++++++++ src/MSBuildTaskHost/MSBuildRuntime.cs | 13 ++++++ src/MSBuildTaskHost/MSBuildTaskHost.csproj | 3 +- src/MSBuildTaskHost/XMakeAttributes.cs | 46 ------------------- 5 files changed, 46 insertions(+), 56 deletions(-) create mode 100644 src/MSBuildTaskHost/MSBuildArchitecture.cs create mode 100644 src/MSBuildTaskHost/MSBuildRuntime.cs delete mode 100644 src/MSBuildTaskHost/XMakeAttributes.cs diff --git a/src/MSBuildTaskHost/CommunicationsUtilities.cs b/src/MSBuildTaskHost/CommunicationsUtilities.cs index 4c4aa19274e..33d47355cf5 100644 --- a/src/MSBuildTaskHost/CommunicationsUtilities.cs +++ b/src/MSBuildTaskHost/CommunicationsUtilities.cs @@ -7,15 +7,15 @@ using System.Globalization; using System.IO; using System.IO.Pipes; -using System.Runtime.InteropServices; -using System.Security.Principal; using System.Reflection; +using System.Runtime.InteropServices; using System.Security.Cryptography; +using System.Security.Principal; using System.Text; using System.Threading; +using Microsoft.Build.BackEnd; using Microsoft.Build.Framework; using Microsoft.Build.Shared; -using Microsoft.Build.BackEnd; #nullable disable @@ -750,22 +750,22 @@ internal static HandshakeOptions GetHandshakeOptions( if (taskHostParameters.IsEmpty) { clrVersion = typeof(bool).Assembly.GetName().Version.Major; - architectureFlagToSet = XMakeAttributes.GetCurrentMSBuildArchitecture(); + architectureFlagToSet = MSBuildArchitecture.GetCurrent(); } else // Figure out flags based on parameters given { ErrorUtilities.VerifyThrow(taskHostParameters.Runtime != null, "Should always have an explicit runtime when we call this method."); ErrorUtilities.VerifyThrow(taskHostParameters.Architecture != null, "Should always have an explicit architecture when we call this method."); - if (taskHostParameters.Runtime.Equals(XMakeAttributes.MSBuildRuntimeValues.clr2, StringComparison.OrdinalIgnoreCase)) + if (taskHostParameters.Runtime.Equals(MSBuildRuntime.clr2, StringComparison.OrdinalIgnoreCase)) { clrVersion = 2; } - else if (taskHostParameters.Runtime.Equals(XMakeAttributes.MSBuildRuntimeValues.clr4, StringComparison.OrdinalIgnoreCase)) + else if (taskHostParameters.Runtime.Equals(MSBuildRuntime.clr4, StringComparison.OrdinalIgnoreCase)) { clrVersion = 4; } - else if (taskHostParameters.Runtime.Equals(XMakeAttributes.MSBuildRuntimeValues.net, StringComparison.OrdinalIgnoreCase)) + else if (taskHostParameters.Runtime.Equals(MSBuildRuntime.net, StringComparison.OrdinalIgnoreCase)) { clrVersion = 5; } @@ -780,11 +780,11 @@ internal static HandshakeOptions GetHandshakeOptions( if (!string.IsNullOrEmpty(architectureFlagToSet)) { - if (architectureFlagToSet.Equals(XMakeAttributes.MSBuildArchitectureValues.x64, StringComparison.OrdinalIgnoreCase)) + if (architectureFlagToSet.Equals(MSBuildArchitecture.x64, StringComparison.OrdinalIgnoreCase)) { context |= HandshakeOptions.X64; } - else if (architectureFlagToSet.Equals(XMakeAttributes.MSBuildArchitectureValues.arm64, StringComparison.OrdinalIgnoreCase)) + else if (architectureFlagToSet.Equals(MSBuildArchitecture.arm64, StringComparison.OrdinalIgnoreCase)) { context |= HandshakeOptions.Arm64; } diff --git a/src/MSBuildTaskHost/MSBuildArchitecture.cs b/src/MSBuildTaskHost/MSBuildArchitecture.cs new file mode 100644 index 00000000000..0dae1c1c5bf --- /dev/null +++ b/src/MSBuildTaskHost/MSBuildArchitecture.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Build.Shared; + +internal static class MSBuildArchitecture +{ + public const string x86 = "x86"; + public const string x64 = "x64"; + public const string arm64 = "arm64"; + public const string currentArchitecture = "CurrentArchitecture"; + public const string any = "*"; + + /// + /// Returns the MSBuildArchitecture value corresponding to the current process' architecture. + /// + /// + /// Revisit if we ever run on something other than Intel. + /// + public static string GetCurrent() + => NativeMethodsShared.Is64Bit ? x64 : x86; +} diff --git a/src/MSBuildTaskHost/MSBuildRuntime.cs b/src/MSBuildTaskHost/MSBuildRuntime.cs new file mode 100644 index 00000000000..2bca51f767f --- /dev/null +++ b/src/MSBuildTaskHost/MSBuildRuntime.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Build.Shared; + +public static class MSBuildRuntime +{ + public const string clr2 = "CLR2"; + public const string clr4 = "CLR4"; + public const string currentRuntime = "CurrentRuntime"; + public const string net = "NET"; + public const string any = "*"; +} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index e8ff91ba6b1..e60d44f526a 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -92,6 +92,8 @@ + + @@ -117,7 +119,6 @@ - diff --git a/src/MSBuildTaskHost/XMakeAttributes.cs b/src/MSBuildTaskHost/XMakeAttributes.cs deleted file mode 100644 index 70092a2b4d3..00000000000 --- a/src/MSBuildTaskHost/XMakeAttributes.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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; - -#nullable disable - -namespace Microsoft.Build.Shared -{ - /// - /// Contains the names of the known attributes in the XML project file. - /// - internal static class XMakeAttributes - { - 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 = "*"; - } - - /// - /// Returns the MSBuildArchitecture value corresponding to the current process' architecture. - /// - /// - /// Revisit if we ever run on something other than Intel. - /// - internal static string GetCurrentMSBuildArchitecture() - { - string currentArchitecture = (IntPtr.Size == sizeof(Int64)) ? MSBuildArchitectureValues.x64 : MSBuildArchitectureValues.x86; - return currentArchitecture; - } - } -} From fe0904553f25482ad4616755abc81339e19951e7 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 15:28:15 -0800 Subject: [PATCH 053/136] MSBuildTaskHost: Trim unnecessary code out of BuildEnvironmentHelper.cs Much of the code in BuildEnvironmentHelper.cs is unnecessary or doesn't function correctly in MSBuildTaskHost. --- src/MSBuildTaskHost/BuildEnvironmentHelper.cs | 275 ++---------------- src/MSBuildTaskHost/ExceptionHandling.cs | 36 +-- src/MSBuildTaskHost/TaskLoader.cs | 16 +- 3 files changed, 33 insertions(+), 294 deletions(-) diff --git a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs index 151bc3e8bef..6b6b3799791 100644 --- a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs +++ b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs @@ -2,11 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; -using Microsoft.Build.Framework; using Microsoft.Build.Shared.FileSystem; #nullable disable @@ -89,37 +87,18 @@ private static BuildEnvironment Initialize() } } - // If we can't find a suitable environment, continue in the 'None' mode. If not running tests, - // we will use the current running process for the CurrentMSBuildExePath value. This is likely + // If we can't find a suitable environment, continue in the 'None' mode. + // We will use the current running process for the CurrentMSBuildExePath value. This is likely // wrong, but many things use the CurrentMSBuildToolsDirectory value which must be set for basic // functionality to work. - // - // If we are running tests, then the current running process may be a test runner located in the - // NuGet packages folder. So in that case, we use the location of the current assembly, which - // will be in the output path of the test project, which is what we want. + string msbuildExePath = GetProcessFromRunningProcess(); - string msbuildExePath; - if (s_runningTests()) - { - msbuildExePath = typeof(BuildEnvironmentHelper).Assembly.Location; - } - else - { - msbuildExePath = s_getProcessFromRunningProcess(); - } - - return new BuildEnvironment( - BuildEnvironmentMode.None, - msbuildExePath, - runningTests: s_runningTests(), - runningInMSBuildExe: false, - runningInVisualStudio: false, - visualStudioPath: null); + return new BuildEnvironment(BuildEnvironmentMode.None, msbuildExePath); } private static BuildEnvironment TryFromEnvironmentVariable() { - var msBuildExePath = s_getEnvironmentVariable("MSBUILD_EXE_PATH"); + var msBuildExePath = GetEnvironmentVariable("MSBUILD_EXE_PATH"); return msBuildExePath == null ? null @@ -128,7 +107,7 @@ private static BuildEnvironment TryFromEnvironmentVariable() private static BuildEnvironment TryFromVisualStudioProcess() { - var vsProcess = s_getProcessFromRunningProcess(); + var vsProcess = GetProcessFromRunningProcess(); if (!IsProcessInList(vsProcess, s_visualStudioProcess)) { return null; @@ -137,18 +116,12 @@ private static BuildEnvironment TryFromVisualStudioProcess() var vsRoot = FileUtilities.GetFolderAbove(vsProcess, 3); string msBuildExe = GetMSBuildExeFromVsRoot(vsRoot); - return new BuildEnvironment( - BuildEnvironmentMode.VisualStudio, - msBuildExe, - runningTests: false, - runningInMSBuildExe: false, - runningInVisualStudio: true, - visualStudioPath: vsRoot); + return new BuildEnvironment(BuildEnvironmentMode.VisualStudio, msBuildExe); } private static BuildEnvironment TryFromMSBuildProcess() { - var msBuildExe = s_getProcessFromRunningProcess(); + var msBuildExe = GetProcessFromRunningProcess(); if (!IsProcessInList(msBuildExe, s_msBuildProcess)) { return null; @@ -157,28 +130,16 @@ private static BuildEnvironment TryFromMSBuildProcess() // First check if we're in a VS installation if (Regex.IsMatch(msBuildExe, $@".*\\MSBuild\\{CurrentToolsVersion}\\Bin\\.*MSBuild(?:TaskHost)?\.exe", RegexOptions.IgnoreCase)) { - return new BuildEnvironment( - BuildEnvironmentMode.VisualStudio, - msBuildExe, - runningTests: false, - runningInMSBuildExe: true, - runningInVisualStudio: false, - visualStudioPath: GetVsRootFromMSBuildAssembly(msBuildExe)); + return new BuildEnvironment(BuildEnvironmentMode.VisualStudio, msBuildExe); } // Standalone mode running in MSBuild.exe - return new BuildEnvironment( - BuildEnvironmentMode.Standalone, - msBuildExe, - runningTests: false, - runningInMSBuildExe: true, - runningInVisualStudio: false, - visualStudioPath: null); + return new BuildEnvironment(BuildEnvironmentMode.Standalone, msBuildExe); } private static BuildEnvironment TryFromMSBuildAssembly() { - var buildAssembly = s_getExecutingAssemblyPath(); + var buildAssembly = GetExecutingAssemblyPath(); if (buildAssembly == null) { return null; @@ -209,13 +170,7 @@ private static BuildEnvironment TryFromMSBuildAssembly() if (!string.IsNullOrEmpty(msBuildPath)) { // Standalone mode with toolset - return new BuildEnvironment( - BuildEnvironmentMode.Standalone, - msBuildPath, - runningTests: s_runningTests(), - runningInMSBuildExe: false, - runningInVisualStudio: false, - visualStudioPath: null); + return new BuildEnvironment(BuildEnvironmentMode.Standalone, msBuildPath); } return null; @@ -231,12 +186,8 @@ private static BuildEnvironment TryFromMSBuildExeUnderVisualStudio(string msbuil { string visualStudioRoot = GetVsRootFromMSBuildAssembly(msbuildExe); return new BuildEnvironment( - BuildEnvironmentMode.VisualStudio, - GetMSBuildExeFromVsRoot(visualStudioRoot), - runningTests: s_runningTests(), - runningInMSBuildExe: false, - runningInVisualStudio: false, - visualStudioPath: visualStudioRoot); + BuildEnvironmentMode.VisualStudio, + GetMSBuildExeFromVsRoot(visualStudioRoot)); } return null; @@ -244,16 +195,9 @@ private static BuildEnvironment TryFromMSBuildExeUnderVisualStudio(string msbuil private static BuildEnvironment TryFromDevConsole() { - if (s_runningTests()) - { - // If running unit tests, then don't try to get the build environment from MSBuild installed on the machine - // (we should be using the locally built MSBuild instead) - return null; - } - // VSINSTALLDIR and VisualStudioVersion are set from the Developer Command Prompt. - var vsInstallDir = s_getEnvironmentVariable("VSINSTALLDIR"); - var vsVersion = s_getEnvironmentVariable("VisualStudioVersion"); + var vsInstallDir = GetEnvironmentVariable("VSINSTALLDIR"); + var vsVersion = GetEnvironmentVariable("VisualStudioVersion"); if (string.IsNullOrEmpty(vsInstallDir) || string.IsNullOrEmpty(vsVersion) || vsVersion != CurrentVisualStudioVersion || !FileSystems.Default.DirectoryExists(vsInstallDir)) @@ -263,11 +207,7 @@ private static BuildEnvironment TryFromDevConsole() return new BuildEnvironment( BuildEnvironmentMode.VisualStudio, - GetMSBuildExeFromVsRoot(vsInstallDir), - runningTests: false, - runningInMSBuildExe: false, - runningInVisualStudio: false, - visualStudioPath: vsInstallDir); + GetMSBuildExeFromVsRoot(vsInstallDir)); } private static BuildEnvironment TryFromStandaloneMSBuildExe(string msBuildExePath) @@ -275,13 +215,7 @@ private static BuildEnvironment TryFromStandaloneMSBuildExe(string msBuildExePat if (!string.IsNullOrEmpty(msBuildExePath) && FileSystems.Default.FileExists(msBuildExePath)) { // MSBuild.exe was found outside of Visual Studio. Assume Standalone mode. - return new BuildEnvironment( - BuildEnvironmentMode.Standalone, - msBuildExePath, - runningTests: s_runningTests(), - runningInMSBuildExe: false, - runningInVisualStudio: false, - visualStudioPath: null); + return new BuildEnvironment(BuildEnvironmentMode.Standalone, msBuildExePath); } return null; @@ -305,35 +239,6 @@ private static string GetMSBuildExeFromVsRoot(string visualStudioRoot) NativeMethodsShared.Is64Bit ? "amd64" : string.Empty, "MSBuild.exe"); - private static bool? _runningTests; - private static readonly LockType _runningTestsLock = new LockType(); - - private static bool CheckIfRunningTests() - { - if (_runningTests != null) - { - return _runningTests.Value; - } - - lock (_runningTestsLock) - { - if (_runningTests != null) - { - return _runningTests.Value; - } - - // Check if running tests via the TestInfo class in Microsoft.Build.Framework. - // See the comments on the TestInfo class for an explanation of why it works this way. - var frameworkAssembly = typeof(Framework.ITask).Assembly; - var testInfoType = frameworkAssembly.GetType("Microsoft.Build.Framework.TestInfo"); - var runningTestsField = testInfoType.GetField("s_runningTests", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static); - - _runningTests = (bool)runningTestsField.GetValue(null); - - return _runningTests.Value; - } - } - /// /// Returns true if processName appears in the processList /// @@ -354,59 +259,21 @@ private static bool IsProcessInList(string processName, string[] processList) } private static string GetProcessFromRunningProcess() - { - return EnvironmentUtilities.ProcessPath; - } + => EnvironmentUtilities.ProcessPath; private static string GetExecutingAssemblyPath() - { - return FileUtilities.ExecutingAssemblyPath; - } + => FileUtilities.ExecutingAssemblyPath; private static string GetEnvironmentVariable(string variable) - { - return Environment.GetEnvironmentVariable(variable); - } - - /// - /// Resets the current singleton instance (for testing). - /// - internal static void ResetInstance_ForUnitTestsOnly(Func getProcessFromRunningProcess = null, - Func getExecutingAssemblyPath = null, - Func getEnvironmentVariable = null, - Func runningTests = null) - { - s_getProcessFromRunningProcess = getProcessFromRunningProcess ?? GetProcessFromRunningProcess; - s_getExecutingAssemblyPath = getExecutingAssemblyPath ?? GetExecutingAssemblyPath; - s_getEnvironmentVariable = getEnvironmentVariable ?? GetEnvironmentVariable; - - // Tests which specifically test the BuildEnvironmentHelper need it to be able to act as if it is not running tests - s_runningTests = runningTests ?? CheckIfRunningTests; - - _runningTests = null; - BuildEnvironmentHelperSingleton.s_instance = Initialize(); - } - - /// - /// Resets the current singleton instance (for testing). - /// - internal static void ResetInstance_ForUnitTestsOnly(BuildEnvironment buildEnvironment) - { - BuildEnvironmentHelperSingleton.s_instance = buildEnvironment; - _runningTests = buildEnvironment.RunningTests; - } - - private static Func s_getProcessFromRunningProcess = GetProcessFromRunningProcess; - private static Func s_getExecutingAssemblyPath = GetExecutingAssemblyPath; - private static Func s_getEnvironmentVariable = GetEnvironmentVariable; - private static Func s_runningTests = CheckIfRunningTests; + => Environment.GetEnvironmentVariable(variable); private static class BuildEnvironmentHelperSingleton { // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit static BuildEnvironmentHelperSingleton() - { } + { + } public static BuildEnvironment s_instance = Initialize(); } @@ -442,18 +309,12 @@ internal enum BuildEnvironmentMode /// internal sealed class BuildEnvironment { - public BuildEnvironment(BuildEnvironmentMode mode, string currentMSBuildExePath, bool runningTests, bool runningInMSBuildExe, bool runningInVisualStudio, - string visualStudioPath) + public BuildEnvironment(BuildEnvironmentMode mode, string currentMSBuildExePath) { FileInfo currentMSBuildExeFile = null; DirectoryInfo currentToolsDirectory = null; - Mode = mode; - RunningTests = runningTests; - RunningInMSBuildExe = runningInMSBuildExe; - RunningInVisualStudio = runningInVisualStudio; CurrentMSBuildExePath = currentMSBuildExePath; - VisualStudioInstallRootDirectory = visualStudioPath; if (!string.IsNullOrEmpty(currentMSBuildExePath)) { @@ -461,9 +322,6 @@ public BuildEnvironment(BuildEnvironmentMode mode, string currentMSBuildExePath, currentToolsDirectory = currentMSBuildExeFile.Directory; CurrentMSBuildToolsDirectory = currentMSBuildExeFile.DirectoryName; - CurrentMSBuildConfigurationFile = string.Concat(currentMSBuildExePath, ".config"); - MSBuildToolsDirectory32 = CurrentMSBuildToolsDirectory; - MSBuildToolsDirectory64 = CurrentMSBuildToolsDirectory; MSBuildToolsDirectoryRoot = CurrentMSBuildToolsDirectory; } @@ -493,86 +351,13 @@ public BuildEnvironment(BuildEnvironmentMode mode, string currentMSBuildExePath, MSBuildToolsDirectoryRoot = currentToolsDirectory.Parent?.FullName; } } - - if (MSBuildToolsDirectoryRoot != null) - { - // Calculate potential paths to other architecture MSBuild.exe - var potentialAmd64FromX86 = FileUtilities.CombinePaths(MSBuildToolsDirectoryRoot, "amd64", msBuildExeName); - - // Check for existence of an MSBuild file. Note this is not necessary in a VS installation where we always want to - // assume the correct layout. - var existsCheck = mode == BuildEnvironmentMode.VisualStudio ? new Func(_ => true) : FileSystems.Default.FileExists; - - MSBuildToolsDirectory32 = MSBuildToolsDirectoryRoot; - MSBuildToolsDirectory64 = existsCheck(potentialAmd64FromX86) ? Path.Combine(MSBuildToolsDirectoryRoot, "amd64") : CurrentMSBuildToolsDirectory; - } - - MSBuildExtensionsPath = mode == BuildEnvironmentMode.VisualStudio - ? Path.Combine(VisualStudioInstallRootDirectory, "MSBuild") - : MSBuildToolsDirectory32; } - internal BuildEnvironmentMode Mode { get; } - - /// - /// Gets the flag that indicates if we are running in a test harness. - /// - internal bool RunningTests { get; } - - /// - /// Returns true when the entry point application is MSBuild.exe. - /// - internal bool RunningInMSBuildExe { get; } - - /// - /// Returns true when the entry point application is Visual Studio. - /// - internal bool RunningInVisualStudio { get; } - /// /// Path to the root of the MSBuild folder (in VS scenarios, MSBuild\Current\bin). /// internal string MSBuildToolsDirectoryRoot { get; } - /// - /// Path to the MSBuild 32-bit tools directory. - /// - internal string MSBuildToolsDirectory32 { get; } - - /// - /// Path to the MSBuild 64-bit (AMD64) tools directory. - /// - internal string MSBuildToolsDirectory64 { get; } - - /// - /// Path to the Sdks folder for this MSBuild instance. - /// - internal string MSBuildSDKsPath - { - get - { - string defaultSdkPath; - - if (VisualStudioInstallRootDirectory != null) - { - // Can't use the N-argument form of Combine because it doesn't exist on .NET 3.5 - defaultSdkPath = FileUtilities.CombinePaths(VisualStudioInstallRootDirectory, "MSBuild", "Sdks"); - } - else - { - defaultSdkPath = Path.Combine(CurrentMSBuildToolsDirectory, "Sdks"); - } - - // Allow an environment-variable override of the default SDK location - return Environment.GetEnvironmentVariable("MSBuildSDKsPath") ?? defaultSdkPath; - } - } - - /// - /// Full path to the current MSBuild configuration file. - /// - internal string CurrentMSBuildConfigurationFile { get; } - /// /// Full path to current MSBuild.exe. /// @@ -588,17 +373,5 @@ internal string MSBuildSDKsPath /// we're executing from the 'AMD64' folder. /// internal string CurrentMSBuildToolsDirectory { get; } - - /// - /// Path to the root Visual Studio install directory - /// (e.g. 'C:\Program Files (x86)\Microsoft Visual Studio\Preview\Enterprise') - /// - internal string VisualStudioInstallRootDirectory { get; } - - /// - /// MSBuild extensions path. On Standalone this defaults to the MSBuild folder. In - /// VisualStudio mode this folder will be %VSINSTALLDIR%\MSBuild. - /// - internal string MSBuildExtensionsPath { get; set; } } } diff --git a/src/MSBuildTaskHost/ExceptionHandling.cs b/src/MSBuildTaskHost/ExceptionHandling.cs index b11c89f64ba..a24ba3c0e20 100644 --- a/src/MSBuildTaskHost/ExceptionHandling.cs +++ b/src/MSBuildTaskHost/ExceptionHandling.cs @@ -18,7 +18,10 @@ namespace Microsoft.Build.Shared /// internal static class ExceptionHandling { - private static readonly string s_debugDumpPath = GetDebugDumpPath(); + /// + /// The directory used for diagnostic log files. + /// + public static string DebugDumpPath { get; } = GetDebugDumpPath(); /// /// Gets the location of the directory used for diagnostic log files. @@ -29,35 +32,8 @@ private static string GetDebugDumpPath() string debugPath = Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); return !string.IsNullOrEmpty(debugPath) - ? debugPath - : FileUtilities.TempFileDirectory; - } - - private static string s_debugDumpPathInRunningTests = GetDebugDumpPath(); - internal static bool ResetDebugDumpPathInRunningTests = false; - - /// - /// The directory used for diagnostic log files. - /// - internal static string DebugDumpPath - { - get - { - if (BuildEnvironmentHelper.Instance.RunningTests) - { - if (ResetDebugDumpPathInRunningTests) - { - s_debugDumpPathInRunningTests = GetDebugDumpPath(); - // reset dump file name so new one is created in new path - s_dumpFileName = null; - ResetDebugDumpPathInRunningTests = false; - } - - return s_debugDumpPathInRunningTests; - } - - return s_debugDumpPath; - } + ? debugPath + : FileUtilities.TempFileDirectory; } /// diff --git a/src/MSBuildTaskHost/TaskLoader.cs b/src/MSBuildTaskHost/TaskLoader.cs index 585154a6ed8..ab0c5f418fb 100644 --- a/src/MSBuildTaskHost/TaskLoader.cs +++ b/src/MSBuildTaskHost/TaskLoader.cs @@ -37,7 +37,6 @@ internal static bool IsTaskClass(Type type, object unused) /// /// Creates an ITask instance and returns it. /// -#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter internal static ITask? CreateTask( LoadedType loadedType, string taskName, @@ -48,9 +47,7 @@ internal static bool IsTaskClass(Type type, object unused) AppDomainSetup appDomainSetup, Action appDomainCreated, bool isOutOfProc, - out AppDomain? taskAppDomain - ) -#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter + out AppDomain? taskAppDomain) { bool separateAppDomain = loadedType.HasLoadInSeparateAppDomainAttribute; s_resolverLoadedType = null; @@ -87,14 +84,6 @@ out AppDomain? taskAppDomain // Apply the appdomain settings to the new appdomain before creating it appDomainInfo.SetConfigurationBytes(currentAppdomainBytes); - if (BuildEnvironmentHelper.Instance.RunningTests) - { - // Prevent the new app domain from looking in the VS test runner location. If this - // is not done, we will not be able to find Microsoft.Build.* assemblies. - appDomainInfo.ApplicationBase = BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory; - appDomainInfo.ConfigurationFile = BuildEnvironmentHelper.Instance.CurrentMSBuildConfigurationFile; - } - AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolver; s_resolverLoadedType = loadedType; @@ -162,7 +151,7 @@ out AppDomain? taskAppDomain /// This is a resolver to help created AppDomains when they are unable to load an assembly into their domain we will help /// them succeed by providing the already loaded one in the currentdomain so that they can derive AssemblyName info from it /// - internal static Assembly? AssemblyResolver(object sender, ResolveEventArgs args) + private static Assembly? AssemblyResolver(object sender, ResolveEventArgs args) { if (args.Name.Equals(s_resolverLoadedType?.LoadedAssemblyName?.FullName, StringComparison.OrdinalIgnoreCase)) { @@ -170,6 +159,7 @@ out AppDomain? taskAppDomain { return null; } + return s_resolverLoadedType.LoadedAssembly ?? Assembly.Load(s_resolverLoadedType.Path); } From e86369b310ddccc833c85116e33d37bb9a6bacf3 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Feb 2026 15:54:43 -0800 Subject: [PATCH 054/136] MSBuildTaskHost: Remove more uncalled members from FileUtilities.cs --- src/MSBuildTaskHost/FileUtilities.cs | 47 ---------------------------- src/MSBuildTaskHost/Modifiers.cs | 5 +-- 2 files changed, 1 insertion(+), 51 deletions(-) diff --git a/src/MSBuildTaskHost/FileUtilities.cs b/src/MSBuildTaskHost/FileUtilities.cs index 3ca001e7f95..336d998ec53 100644 --- a/src/MSBuildTaskHost/FileUtilities.cs +++ b/src/MSBuildTaskHost/FileUtilities.cs @@ -18,41 +18,8 @@ namespace Microsoft.Build.Shared /// internal static partial class FileUtilities { - private static readonly char[] s_slashes = ['/', '\\']; - - // net35 taskhost does not support AsyncLocal, and the scenario is not relevant there. - internal static string CurrentThreadWorkingDirectory = null; - internal static string TempFileDirectory => Path.GetTempPath(); - /// - /// Copied from https://github.com/dotnet/corefx/blob/056715ff70e14712419d82d51c8c50c54b9ea795/src/Common/src/System/IO/PathInternal.Windows.cs#L61 - /// MSBuild should support the union of invalid path chars across the supported OSes, so builds can have the same behaviour crossplatform: https://github.com/dotnet/msbuild/issues/781#issuecomment-243942514 - /// - internal static readonly char[] InvalidPathChars = - [ - '|', '\0', - (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, - (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, - (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, - (char)31 - ]; - - /// - /// Copied from https://github.com/dotnet/corefx/blob/387cf98c410bdca8fd195b28cbe53af578698f94/src/System.Runtime.Extensions/src/System/IO/Path.Windows.cs#L18 - /// MSBuild should support the union of invalid path chars across the supported OSes, so builds can have the same behaviour crossplatform: https://github.com/dotnet/msbuild/issues/781#issuecomment-243942514 - /// - internal static readonly char[] InvalidFileNameCharsArray = - [ - '\"', '<', '>', '|', '\0', - (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, - (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, - (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, - (char)31, ':', '*', '?', '\\', '/' - ]; - - internal static char[] InvalidFileNameChars => InvalidFileNameCharsArray; - /// /// Indicates if the given character is a slash. /// @@ -235,20 +202,6 @@ private static string GetFullPathNoThrow(string path) return path; } - internal static bool PathIsInvalid(string path) - { - // Path.GetFileName does not react well to malformed filenames. - // For example, Path.GetFileName("a/b/foo:bar") returns bar instead of foo:bar - // It also throws exceptions on illegal path characters - if (path.IndexOfAny(InvalidPathChars) < 0) - { - int lastDirectorySeparator = path.LastIndexOfAny(s_slashes); - return path.IndexOfAny(InvalidFileNameChars, lastDirectorySeparator >= 0 ? lastDirectorySeparator + 1 : 0) >= 0; - } - - return true; - } - /// /// Gets a file info object for the specified file path. If the file path /// is invalid, or is a directory, or cannot be accessed, or does not exist, diff --git a/src/MSBuildTaskHost/Modifiers.cs b/src/MSBuildTaskHost/Modifiers.cs index d396dfddc2b..0db4aaa85a6 100644 --- a/src/MSBuildTaskHost/Modifiers.cs +++ b/src/MSBuildTaskHost/Modifiers.cs @@ -183,10 +183,7 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS return fullPath; } - if (currentDirectory == null) - { - currentDirectory = FileUtilities.CurrentThreadWorkingDirectory ?? String.Empty; - } + currentDirectory ??= string.Empty; modifiedItemSpec = GetFullPath(itemSpec, currentDirectory); fullPath = modifiedItemSpec; From 8866a4cddb1a17efd573ccaab98d27ed2b246e81 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 09:06:32 -0800 Subject: [PATCH 055/136] MSBuildTaskHost: Use NetFx implementation of Path.Combine Change FileUtilities.CombinePaths to use the NetFx implementation of Path.Combine(string[]) rather than LINQ. --- src/MSBuildTaskHost/FileUtilities.cs | 78 +++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 7 deletions(-) diff --git a/src/MSBuildTaskHost/FileUtilities.cs b/src/MSBuildTaskHost/FileUtilities.cs index 336d998ec53..a318f3aca68 100644 --- a/src/MSBuildTaskHost/FileUtilities.cs +++ b/src/MSBuildTaskHost/FileUtilities.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using System.Linq; using System.Text; using Microsoft.Build.Framework; @@ -310,19 +309,84 @@ internal static string GetFolderAbove(string path, int count = 1) /// /// Combine multiple paths. Should only be used when compiling against .NET 2.0. - /// - /// Only use in .NET 2.0. Otherwise, use System.IO.Path.Combine(...) - /// /// /// Root path. /// Paths to concatenate. /// Combined path. - internal static string CombinePaths(string root, params string[] paths) + /// + /// Only use in .NET 2.0. Otherwise, use System.IO.Path.Combine(...) + /// + internal static string CombinePaths(params string[] paths) { - ErrorUtilities.VerifyThrowArgumentNull(root); + // The code below is derived from .NET Framework reference source: + // https://github.com/microsoft/referencesource/blob/ec9fa9ae770d522a5b5f0607898044b7478574a3/mscorlib/system/io/path.cs#L1230-L1285 + ErrorUtilities.VerifyThrowArgumentNull(paths); - return paths.Aggregate(root, Path.Combine); + int firstComponent = 0; + int finalSize = paths.Length; + + for (int i = 0; i < paths.Length; i++) + { + string path = paths[i]; + ErrorUtilities.VerifyThrowArgumentNull(path, nameof(paths)); + + if (path.Length == 0) + { + continue; + } + + if (Path.IsPathRooted(path)) + { + firstComponent = i; + finalSize = path.Length; + } + else + { + finalSize += path.Length; + } + + char ch = path[path.Length - 1]; + if (!IsDirectorySeparator(ch)) + { + finalSize++; + } + } + + StringBuilder builder = StringBuilderCache.Acquire(finalSize); + try + { + for (int i = firstComponent; i < paths.Length; i++) + { + string path = paths[i]; + + if (path.Length == 0) + { + continue; + } + + if (builder.Length > 0) + { + char ch = builder[builder.Length - 1]; + + if (!IsDirectorySeparator(ch) && ch != Path.VolumeSeparatorChar) + { + builder.Append(Path.DirectorySeparatorChar); + } + } + + builder.Append(path); + } + + return builder.ToString(); + } + finally + { + StringBuilderCache.Release(builder); + } + + static bool IsDirectorySeparator(char ch) + => ch == Path.DirectorySeparatorChar || ch == Path.AltDirectorySeparatorChar; } internal static StreamWriter OpenWrite(string path, bool append, Encoding encoding = null) From b5b7d548dca7563978d83f2d0194e5e64ed077e1 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 09:25:43 -0800 Subject: [PATCH 056/136] MSBuildTaskHost: Remove uncalled members in Traits.cs --- src/MSBuildTaskHost/Framework/Traits.cs | 574 +----------------------- 1 file changed, 3 insertions(+), 571 deletions(-) diff --git a/src/MSBuildTaskHost/Framework/Traits.cs b/src/MSBuildTaskHost/Framework/Traits.cs index f14208e145a..1756e8b40b8 100644 --- a/src/MSBuildTaskHost/Framework/Traits.cs +++ b/src/MSBuildTaskHost/Framework/Traits.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Globalization; namespace Microsoft.Build.Framework { @@ -29,604 +28,37 @@ public Traits() { EscapeHatches = new EscapeHatches(); - DebugScheduler = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDDEBUGSCHEDULER")); + DebugEngine = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildDebugEngine")); DebugNodeCommunication = DebugEngine || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDDEBUGCOMM")); } public EscapeHatches EscapeHatches { get; } - internal readonly string? MSBuildDisableFeaturesFromVersion = Environment.GetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION"); - - // This will affect all tasks except for MSBuild and CallTarget. Those two have to run in-proc, as they depend on IBuildEngine callbacks. - public readonly bool ForceAllTasksOutOfProcToTaskHost = Environment.GetEnvironmentVariable("MSBUILDFORCEALLTASKSOUTOFPROC") == "1"; - - /// - /// Do not expand wildcards that match a certain pattern - /// - public readonly bool UseLazyWildCardEvaluation = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildSkipEagerWildCardEvaluationRegexes")); - public readonly bool LogExpandedWildcards = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDLOGEXPANDEDWILDCARDS")); - public readonly bool ThrowOnDriveEnumeratingWildcard = Environment.GetEnvironmentVariable("MSBUILDFAILONDRIVEENUMERATINGWILDCARD") == "1"; - - /// - /// Cache file existence for the entire process - /// - public readonly bool CacheFileExistence = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildCacheFileExistence")); - - public readonly bool UseSimpleProjectRootElementCacheConcurrency = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildUseSimpleProjectRootElementCacheConcurrency")); - - /// - /// Cache wildcard expansions for the entire process - /// - public readonly bool MSBuildCacheFileEnumerations = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildCacheFileEnumerations")); - - public readonly bool EnableAllPropertyFunctions = Environment.GetEnvironmentVariable("MSBUILDENABLEALLPROPERTYFUNCTIONS") == "1"; - - /// - /// Enable restore first functionality in MSBuild.exe - /// - public readonly bool EnableRestoreFirst = Environment.GetEnvironmentVariable("MSBUILDENABLERESTOREFIRST") == "1"; - - /// - /// Allow the user to specify that two processes should not be communicating via an environment variable. - /// - public static readonly string? MSBuildNodeHandshakeSalt = Environment.GetEnvironmentVariable("MSBUILDNODEHANDSHAKESALT"); - - /// - /// Override property "MSBuildRuntimeType" to "Full", ignoring the actual runtime type of MSBuild. - /// - public readonly bool ForceEvaluateAsFullFramework = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildForceEvaluateAsFullFramework")); - - /// - /// Setting the associated environment variable to 1 restores the pre-15.8 single - /// threaded (slower) copy behavior. Zero implies Int32.MaxValue, less than zero - /// (default) uses the empirical default in Copy.cs, greater than zero can allow - /// perf tuning beyond the defaults chosen. - /// - public readonly int CopyTaskParallelism = ParseIntFromEnvironmentVariableOrDefault("MSBUILDCOPYTASKPARALLELISM", -1); - - /// - /// Instruct MSBuild to write out the generated "metaproj" file to disk when building a solution file. - /// - public readonly bool EmitSolutionMetaproj = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildEmitSolution")); - - /// - /// Modifies Solution Generator to generate a metaproj that batches multiple Targets into one MSBuild task invoke. - /// - /// - /// For example, a run of Clean;Build target will first run Clean on all projects, - /// then run Build on all projects. When enabled, it will run Clean;Build on all - /// Projects at the back to back. Allowing the second target to start sooner than before. - /// - public readonly bool SolutionBatchTargets = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildSolutionBatchTargets")); - - /// - /// Log statistics about property functions which require reflection - /// - public readonly bool LogPropertyFunctionsRequiringReflection = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildLogPropertyFunctionsRequiringReflection")); - - /// - /// Log all assembly loads including those that come from known MSBuild and .NET SDK sources in the binary log. - /// - public readonly bool LogAllAssemblyLoads = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDLOGALLASSEMBLYLOADS")); - - /// - /// Log all environment variables whether or not they are used in a build in the binary log. - /// - public static bool LogAllEnvironmentVariables = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDLOGALLENVIRONMENTVARIABLES")); - - /// - /// Log property tracking information. - /// - public readonly int LogPropertyTracking = ParseIntFromEnvironmentVariableOrDefault("MsBuildLogPropertyTracking", 0); // Default to logging nothing via the property tracker. - - /// - /// When evaluating items, this is the minimum number of items on the running list to use a dictionary-based remove optimization. - /// - public readonly int DictionaryBasedItemRemoveThreshold = ParseIntFromEnvironmentVariableOrDefault("MSBUILDDICTIONARYBASEDITEMREMOVETHRESHOLD", 100); - - /// - /// Launches a persistent RAR process. - /// - /// TODO: Replace with command line flag when feature is completed. The environment variable is intented to avoid exposing the flag early. - public readonly bool EnableRarNode = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildRarNode")); - - /// - /// Name of environment variables used to enable MSBuild server. - /// - public const string UseMSBuildServerEnvVarName = "MSBUILDUSESERVER"; - - /// - /// Name of environment variable for logging arguments (e.g., -bl, -check). - /// - public const string MSBuildLoggingArgsEnvVarName = "MSBUILD_LOGGING_ARGS"; - - /// - /// Name of environment variable that controls the logging level for diagnostic messages - /// emitted when processing the MSBUILD_LOGGING_ARGS environment variable. - /// Set to "message" to emit as low-importance build messages instead of console warnings. - /// - public const string MSBuildLoggingArgsLevelEnvVarName = "MSBUILD_LOGGING_ARGS_LEVEL"; - - /// - /// Value of the MSBUILD_LOGGING_ARGS environment variable. - /// - public static string? MSBuildLoggingArgs => Environment.GetEnvironmentVariable(MSBuildLoggingArgsEnvVarName); - - /// - /// Gets if the logging level for MSBUILD_LOGGING_ARGS diagnostic is message. - /// - public readonly bool EmitLogsAsMessage = string.Equals(Environment.GetEnvironmentVariable(MSBuildLoggingArgsLevelEnvVarName), "message", StringComparison.OrdinalIgnoreCase); - - public readonly bool DebugEngine = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildDebugEngine")); - public readonly bool DebugScheduler; + public readonly bool DebugEngine; public readonly bool DebugNodeCommunication; - public readonly bool DebugUnitTests = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildDebugUnitTests")); - - public readonly bool InProcNodeDisabled = Environment.GetEnvironmentVariable("MSBUILDNOINPROCNODE") == "1"; - - /// - /// Forces execution of tasks coming from a different TaskFactory than AssemblyTaskFactory out of proc. - /// - public readonly bool ForceTaskFactoryOutOfProc = Environment.GetEnvironmentVariable("MSBUILDFORCEINLINETASKFACTORIESOUTOFPROC") == "1"; - - /// - /// Make Console use default encoding in the system. It opts out automatic console encoding UTF-8. - /// - public readonly bool ConsoleUseDefaultEncoding = Environment.GetEnvironmentVariable("MSBUILD_CONSOLE_USE_DEFAULT_ENCODING") == "1" || Environment.GetEnvironmentVariable("DOTNET_CLI_CONSOLE_USE_DEFAULT_ENCODING") == "1"; - - /// - /// Variables controlling opt out at the level of not initializing telemetry infrastructure. Set to "1" or "true" to opt out. - /// mirroring - /// https://learn.microsoft.com/en-us/dotnet/core/tools/telemetry - /// - public bool SdkTelemetryOptOut = IsEnvVarOneOrTrue("DOTNET_CLI_TELEMETRY_OPTOUT"); - public bool FrameworkTelemetryOptOut = IsEnvVarOneOrTrue("MSBUILD_TELEMETRY_OPTOUT"); - public bool ExcludeTasksDetailsFromTelemetry = IsEnvVarOneOrTrue("MSBUILDTELEMETRYEXCLUDETASKSDETAILS"); - public bool FlushNodesTelemetryIntoConsole = IsEnvVarOneOrTrue("MSBUILDFLUSHNODESTELEMETRYINTOCONSOLE"); - - public bool EnableTargetOutputLogging = IsEnvVarOneOrTrue("MSBUILDTARGETOUTPUTLOGGING"); - - // for VS17.14 - public readonly bool SlnParsingWithSolutionPersistenceOptIn = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILD_PARSE_SLN_WITH_SOLUTIONPERSISTENCE")); - - public static void UpdateFromEnvironment() - { - // Re-create Traits instance to update values in Traits according to current environment. - if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_10)) - { - _instance = new Traits(); - } - } - - private static int ParseIntFromEnvironmentVariableOrDefault(string environmentVariable, int defaultValue) - { - return int.TryParse(Environment.GetEnvironmentVariable(environmentVariable), out int result) - ? result - : defaultValue; - } - - internal static bool IsEnvVarOneOrTrue(string name) - { - string? value = Environment.GetEnvironmentVariable(name); - return value != null && - (value.Equals("1", StringComparison.OrdinalIgnoreCase) || - value.Equals("true", StringComparison.OrdinalIgnoreCase)); - } } internal class EscapeHatches { - /// - /// Do not log command line information to build loggers. Useful to unbreak people who parse the msbuild log and who are unwilling to change their code. - /// - public readonly bool DoNotSendDeferredMessagesToBuildManager = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildDoNotSendDeferredMessagesToBuildManager")); - - /// - /// https://github.com/dotnet/msbuild/pull/4975 started expanding qualified metadata in Update operations. Before they'd expand to empty strings. - /// This escape hatch turns back the old empty string behavior. - /// - public readonly bool DoNotExpandQualifiedMetadataInUpdateOperation = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildDoNotExpandQualifiedMetadataInUpdateOperation")); - - /// - /// Force whether Project based evaluations should evaluate elements with false conditions. - /// - public readonly bool? EvaluateElementsWithFalseConditionInProjectEvaluation = ParseNullableBoolFromEnvironmentVariable("MSBUILDEVALUATEELEMENTSWITHFALSECONDITIONINPROJECTEVALUATION"); - - /// - /// Always use the accurate-but-slow CreateFile approach to timestamp extraction. - /// - public readonly bool AlwaysUseContentTimestamp = Environment.GetEnvironmentVariable("MSBUILDALWAYSCHECKCONTENTTIMESTAMP") == "1"; - - /// - /// Truncate task inputs when logging them. This can reduce memory pressure - /// at the expense of log usefulness. - /// - public readonly bool TruncateTaskInputs = Environment.GetEnvironmentVariable("MSBUILDTRUNCATETASKINPUTS") == "1"; - - /// - /// Disables truncation of Condition messages in Tasks/Targets via ExpanderOptions.Truncate. - /// - public readonly bool DoNotTruncateConditions = Environment.GetEnvironmentVariable("MSBuildDoNotTruncateConditions") == "1"; - - /// - /// Disables skipping full drive/filesystem globs that are behind a false condition. - /// - public readonly bool AlwaysEvaluateDangerousGlobs = Environment.GetEnvironmentVariable("MSBuildAlwaysEvaluateDangerousGlobs") == "1"; - - /// - /// Disables skipping full up to date check for immutable files. See FileClassifier class. - /// - public readonly bool AlwaysDoImmutableFilesUpToDateCheck = Environment.GetEnvironmentVariable("MSBUILDDONOTCACHEMODIFICATIONTIME") == "1"; - - /// - /// When copying over an existing file, copy directly into the existing file rather than deleting and recreating. - /// - public readonly bool CopyWithoutDelete = Environment.GetEnvironmentVariable("MSBUILDCOPYWITHOUTDELETE") == "1"; - - /// - /// Emit events for project imports. - /// - private bool? _logProjectImports; - - /// - /// Emit events for project imports. - /// - public bool LogProjectImports - { - get - { - // Cache the first time - if (_logProjectImports == null) - { - _logProjectImports = !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDLOGIMPORTS")); - } - return _logProjectImports.Value; - } - set - { - _logProjectImports = value; - } - } - - private bool? _logTaskInputs; - public bool LogTaskInputs - { - get - { - if (_logTaskInputs == null) - { - _logTaskInputs = Environment.GetEnvironmentVariable("MSBUILDLOGTASKINPUTS") == "1"; - } - return _logTaskInputs.Value; - } - set - { - _logTaskInputs = value; - } - } - - private bool? _logPropertiesAndItemsAfterEvaluation; - private bool _logPropertiesAndItemsAfterEvaluationInitialized = false; - public bool? LogPropertiesAndItemsAfterEvaluation - { - get - { - if (!_logPropertiesAndItemsAfterEvaluationInitialized) - { - _logPropertiesAndItemsAfterEvaluationInitialized = true; - var variable = Environment.GetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION"); - if (!string.IsNullOrEmpty(variable)) - { - _logPropertiesAndItemsAfterEvaluation = variable == "1" || string.Equals(variable, "true", StringComparison.OrdinalIgnoreCase); - } - } - - return _logPropertiesAndItemsAfterEvaluation; - } - - set - { - _logPropertiesAndItemsAfterEvaluationInitialized = true; - _logPropertiesAndItemsAfterEvaluation = value; - } - } - - /// - /// Read information only once per file per ResolveAssemblyReference invocation. - /// - public readonly bool CacheAssemblyInformation = Environment.GetEnvironmentVariable("MSBUILDDONOTCACHERARASSEMBLYINFORMATION") != "1"; - - public readonly ProjectInstanceTranslationMode? ProjectInstanceTranslation = ComputeProjectInstanceTranslation(); - - /// - /// Never use the slow (but more accurate) CreateFile approach to timestamp extraction. - /// - public readonly bool UseSymlinkTimeInsteadOfTargetTime = Environment.GetEnvironmentVariable("MSBUILDUSESYMLINKTIMESTAMP") == "1"; - /// /// Allow node reuse of TaskHost nodes. This results in task assemblies locked past the build lifetime, preventing them from being rebuilt if custom tasks change, but may improve performance. /// public readonly bool ReuseTaskHostNodes = Environment.GetEnvironmentVariable("MSBUILDREUSETASKHOSTNODES") == "1"; - /// - /// Whether or not to ignore imports that are considered empty. See ProjectRootElement.IsEmptyXmlFile() for more info. - /// - public readonly bool IgnoreEmptyImports = Environment.GetEnvironmentVariable("MSBUILDIGNOREEMPTYIMPORTS") == "1"; - - /// - /// Whether to respect the TreatAsLocalProperty parameter on the Project tag. - /// - public readonly bool IgnoreTreatAsLocalProperty = Environment.GetEnvironmentVariable("MSBUILDIGNORETREATASLOCALPROPERTY") != null; - - /// - /// Whether to write information about why we evaluate to debug output. - /// - public readonly bool DebugEvaluation = Environment.GetEnvironmentVariable("MSBUILDDEBUGEVALUATION") != null; - - /// - /// Whether to warn when we set a property for the first time, after it was previously used. - /// - public readonly bool WarnOnUninitializedProperty = !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDWARNONUNINITIALIZEDPROPERTY")); - - /// - /// MSBUILDUSECASESENSITIVEITEMNAMES is an escape hatch for the fix - /// for https://github.com/dotnet/msbuild/issues/1751. It should - /// be removed (permanently set to false) after establishing that - /// it's unneeded (at least by the 16.0 timeframe). - /// - public readonly bool UseCaseSensitiveItemNames = Environment.GetEnvironmentVariable("MSBUILDUSECASESENSITIVEITEMNAMES") == "1"; - /// /// Disable the use of paths longer than Windows MAX_PATH limits (260 characters) when running on a long path enabled OS. /// public readonly bool DisableLongPaths = Environment.GetEnvironmentVariable("MSBUILDDISABLELONGPATHS") == "1"; - /// - /// Disable the use of any caching when resolving SDKs. - /// - public readonly bool DisableSdkResolutionCache = Environment.GetEnvironmentVariable("MSBUILDDISABLESDKCACHE") == "1"; - - /// - /// Don't delete TargetPath metadata from associated files found by RAR. - /// - public readonly bool TargetPathForRelatedFiles = Environment.GetEnvironmentVariable("MSBUILDTARGETPATHFORRELATEDFILES") == "1"; - - /// - /// Disable AssemblyLoadContext isolation for plugins. - /// - public readonly bool UseSingleLoadContext = Environment.GetEnvironmentVariable("MSBUILDSINGLELOADCONTEXT") == "1"; - - /// - /// Enables the user of autorun functionality in CMD.exe on Windows which is disabled by default in MSBuild. - /// - public readonly bool UseAutoRunWhenLaunchingProcessUnderCmd = Environment.GetEnvironmentVariable("MSBUILDUSERAUTORUNINCMD") == "1"; - - /// - /// Disables switching codepage to UTF-8 after detection of characters that can't be represented in the current codepage. - /// - public readonly bool AvoidUnicodeWhenWritingToolTaskBatch = Environment.GetEnvironmentVariable("MSBUILDAVOIDUNICODE") == "1"; - - /// - /// Workaround for https://github.com/Microsoft/vstest/issues/1503. - /// - public readonly bool EnsureStdOutForChildNodesIsPrimaryStdout = Environment.GetEnvironmentVariable("MSBUILDENSURESTDOUTFORTASKPROCESSES") == "1"; - - /// - /// Use the original, string-only resx parsing in .NET Core scenarios. - /// - /// - /// Escape hatch for problems arising from https://github.com/dotnet/msbuild/pull/4420. - /// - public readonly bool UseMinimalResxParsingInCoreScenarios = Environment.GetEnvironmentVariable("MSBUILDUSEMINIMALRESX") == "1"; - - /// - /// Escape hatch to ensure msbuild produces the compatible build results cache without versioning. - /// - /// - /// Escape hatch for problems arising from https://github.com/dotnet/msbuild/issues/10208. - /// - public readonly bool DoNotVersionBuildResult = Environment.GetEnvironmentVariable("MSBUILDDONOTVERSIONBUILDRESULT") == "1"; - - /// - /// Escape hatch to ensure build check does not limit amount of results. - /// - public readonly bool DoNotLimitBuildCheckResultsNumber = Environment.GetEnvironmentVariable("MSBUILDDONOTLIMITBUILDCHECKRESULTSNUMBER") == "1"; - - private bool _sdkReferencePropertyExpansionInitialized; - private SdkReferencePropertyExpansionMode? _sdkReferencePropertyExpansionValue; - - /// - /// Overrides the default behavior of property expansion on evaluation of a . - /// - /// - /// Escape hatch for problems arising from https://github.com/dotnet/msbuild/pull/5552. - /// - public SdkReferencePropertyExpansionMode? SdkReferencePropertyExpansion - { - get - { - if (!_sdkReferencePropertyExpansionInitialized) - { - _sdkReferencePropertyExpansionValue = ComputeSdkReferencePropertyExpansion(); - _sdkReferencePropertyExpansionInitialized = true; - } - - return _sdkReferencePropertyExpansionValue; - } - } - - public bool UnquoteTargetSwitchParameters - { - get - { - return ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_10); - } - } - - private static bool? ParseNullableBoolFromEnvironmentVariable(string environmentVariable) - { - var value = Environment.GetEnvironmentVariable(environmentVariable); - - if (string.IsNullOrEmpty(value)) - { - return null; - } - - if (bool.TryParse(value, out bool result)) - { - return result; - } - - ThrowInternalError($"Environment variable \"{environmentVariable}\" should have values \"true\", \"false\" or undefined"); - - return null; - } - - private static ProjectInstanceTranslationMode? ComputeProjectInstanceTranslation() - { - var mode = Environment.GetEnvironmentVariable("MSBUILD_PROJECTINSTANCE_TRANSLATION_MODE"); - - if (mode == null) - { - return null; - } - - if (mode.Equals("full", StringComparison.OrdinalIgnoreCase)) - { - return ProjectInstanceTranslationMode.Full; - } - - if (mode.Equals("partial", StringComparison.OrdinalIgnoreCase)) - { - return ProjectInstanceTranslationMode.Partial; - } - - ThrowInternalError($"Invalid escape hatch for project instance translation: {mode}"); - - return null; - } - - private static SdkReferencePropertyExpansionMode? ComputeSdkReferencePropertyExpansion() - { - var mode = Environment.GetEnvironmentVariable("MSBUILD_SDKREFERENCE_PROPERTY_EXPANSION_MODE"); - - if (mode == null) - { - return null; - } - - // The following uses StartsWith instead of Equals to enable possible tricks like - // the dpiAware "True/PM" trick (see https://devblogs.microsoft.com/oldnewthing/20160617-00/?p=93695) - // in the future. - - const StringComparison comparison = StringComparison.OrdinalIgnoreCase; - - if (mode.StartsWith("no", comparison)) - { - return SdkReferencePropertyExpansionMode.NoExpansion; - } - - if (mode.StartsWith("default", comparison)) - { - return SdkReferencePropertyExpansionMode.DefaultExpand; - } - - if (mode.StartsWith(nameof(SdkReferencePropertyExpansionMode.ExpandUnescape), comparison)) - { - return SdkReferencePropertyExpansionMode.ExpandUnescape; - } - - if (mode.StartsWith(nameof(SdkReferencePropertyExpansionMode.ExpandLeaveEscaped), comparison)) - { - return SdkReferencePropertyExpansionMode.ExpandLeaveEscaped; - } - - ThrowInternalError($"Invalid escape hatch for SdkReference property expansion: {mode}"); - - return null; - } - - public enum ProjectInstanceTranslationMode - { - Full, - Partial - } - - public enum SdkReferencePropertyExpansionMode - { - NoExpansion, - DefaultExpand, - ExpandUnescape, - ExpandLeaveEscaped - } - /// /// Throws InternalErrorException. /// /// /// Clone of ErrorUtilities.ThrowInternalError which isn't available in Framework. /// - internal static void ThrowInternalError(string message) + public static void ThrowInternalError(string message) { throw new InternalErrorException(message); } - - /// - /// Throws InternalErrorException. - /// This is only for situations that would mean that there is a bug in MSBuild itself. - /// - /// - /// Clone from ErrorUtilities which isn't available in Framework. - /// - internal static void ThrowInternalError(string message, params object?[] args) - { - throw new InternalErrorException(FormatString(message, args)); - } - - /// - /// Formats the given string using the variable arguments passed in. - /// - /// PERF WARNING: calling a method that takes a variable number of arguments is expensive, because memory is allocated for - /// the array of arguments -- do not call this method repeatedly in performance-critical scenarios - /// - /// Thread safe. - /// - /// The string to format. - /// Optional arguments for formatting the given string. - /// The formatted string. - /// - /// Clone from ResourceUtilities which isn't available in Framework. - /// - internal static string FormatString(string unformatted, params object?[] args) - { - string formatted = unformatted; - - // NOTE: String.Format() does not allow a null arguments array - if ((args?.Length > 0)) - { -#if DEBUG - // If you accidentally pass some random type in that can't be converted to a string, - // FormatResourceString calls ToString() which returns the full name of the type! - foreach (object? param in args) - { - // Check it has a real implementation of ToString() and the type is not actually System.String - if (param != null) - { - if (string.Equals(param.GetType().ToString(), param.ToString(), StringComparison.Ordinal) && - param.GetType() != typeof(string)) - { - ThrowInternalError("Invalid resource parameter type, was {0}", - param.GetType().FullName); - } - } - } -#endif - // Format the string, using the variable arguments passed in. - // NOTE: all String methods are thread-safe - formatted = String.Format(CultureInfo.CurrentCulture, unformatted, args); - } - - return formatted; - } } } From 0879e78476a4cf695c2e953d3706bfbc7d5f19c5 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 09:28:32 -0800 Subject: [PATCH 057/136] MSBuildTaskHost: Remove ChangeWaves This is unused by MSBuildTaskHost. Truthfully, it would be concerning if it *were* used by MSBuildTaskHost. --- src/MSBuildTaskHost/Framework/ChangeWaves.cs | 211 ------------------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - 2 files changed, 212 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/ChangeWaves.cs diff --git a/src/MSBuildTaskHost/Framework/ChangeWaves.cs b/src/MSBuildTaskHost/Framework/ChangeWaves.cs deleted file mode 100644 index a01ff47556a..00000000000 --- a/src/MSBuildTaskHost/Framework/ChangeWaves.cs +++ /dev/null @@ -1,211 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -#if DEBUG -using System.Diagnostics; -#endif -using System.Linq; - -#nullable disable - -namespace Microsoft.Build.Framework -{ - internal enum ChangeWaveConversionState - { - NotConvertedYet, - Valid, - InvalidFormat, - OutOfRotation - } - - /// - /// Coupled together with the MSBUILDDISABLEFEATURESFROMVERSION environment variable, - /// this class acts as a way to make risky changes while giving customers an opt-out. - /// - /// See docs here: https://github.com/dotnet/msbuild/blob/main/documentation/wiki/ChangeWaves.md - /// For dev docs: https://github.com/dotnet/msbuild/blob/main/documentation/wiki/ChangeWaves-Dev.md - internal static class ChangeWaves - { - internal static readonly Version Wave17_10 = new Version(17, 10); - internal static readonly Version Wave17_12 = new Version(17, 12); - internal static readonly Version Wave17_14 = new Version(17, 14); - internal static readonly Version Wave18_3 = new Version(18, 3); - internal static readonly Version Wave18_4 = new Version(18, 4); - internal static readonly Version Wave18_5 = new Version(18, 5); - internal static readonly Version[] AllWaves = [Wave17_10, Wave17_12, Wave17_14, Wave18_3, Wave18_4, Wave18_5]; - - /// - /// Special value indicating that all features behind all Change Waves should be enabled. - /// - internal static readonly Version EnableAllFeatures = new Version(999, 999); - -#if DEBUG - /// - /// True if has been called. - /// - private static bool _runningTests = false; -#endif - - /// - /// The lowest wave in the current rotation of Change Waves. - /// - internal static Version LowestWave - { - get - { - return AllWaves[0]; - } - } - - /// - /// The highest wave in the current rotation of Change Waves. - /// - internal static Version HighestWave - { - get - { - return AllWaves[AllWaves.Length - 1]; - } - } - - /// - /// Checks the conditions for whether or not we want ApplyChangeWave to be called again. - /// - private static bool ShouldApplyChangeWave - { - get - { - return ConversionState == ChangeWaveConversionState.NotConvertedYet || _cachedWave == null; - } - } - - private static Version _cachedWave; - - /// - /// The current disabled wave. - /// - internal static Version DisabledWave - { - get - { - if (ShouldApplyChangeWave) - { - ApplyChangeWave(); - } - - return _cachedWave; - } - } - - private static ChangeWaveConversionState _state; - - /// - /// The status of how the disabled wave was set. - /// - internal static ChangeWaveConversionState ConversionState - { - get - { - return _state; - } - set - { - // Keep state persistent. - if (_state == ChangeWaveConversionState.NotConvertedYet) - { - _state = value; - } - } - } - - /// - /// Read from environment variable `MSBUILDDISABLEFEATURESFROMVERSION`, correct it if required, cache it and its ConversionState. - /// - internal static void ApplyChangeWave() - { - // Once set, change wave should not need to be set again. - if (!ShouldApplyChangeWave) - { - return; - } - - string msbuildDisableFeaturesFromVersion = Environment.GetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION"); - - // Most common case, `MSBUILDDISABLEFEATURESFROMVERSION` unset - if (string.IsNullOrEmpty(msbuildDisableFeaturesFromVersion)) - { - ConversionState = ChangeWaveConversionState.Valid; - _cachedWave = ChangeWaves.EnableAllFeatures; - } - else if (!TryParseVersion(msbuildDisableFeaturesFromVersion, out _cachedWave)) - { - ConversionState = ChangeWaveConversionState.InvalidFormat; - _cachedWave = ChangeWaves.EnableAllFeatures; - } - else if (_cachedWave == EnableAllFeatures || Array.IndexOf(AllWaves, _cachedWave) >= 0) - { - ConversionState = ChangeWaveConversionState.Valid; - } - else if (_cachedWave < LowestWave) - { - ConversionState = ChangeWaveConversionState.OutOfRotation; - _cachedWave = LowestWave; - } - else if (_cachedWave > HighestWave) - { - ConversionState = ChangeWaveConversionState.OutOfRotation; - _cachedWave = HighestWave; - } - // _cachedWave is somewhere between valid waves, find the next valid version. - else - { - _cachedWave = AllWaves.First((x) => x > _cachedWave); - ConversionState = ChangeWaveConversionState.Valid; - } - } - - /// - /// Determines whether features behind the given wave are enabled. - /// - /// The version to compare. - /// A bool indicating whether the change wave is enabled. - internal static bool AreFeaturesEnabled(Version wave) - { - ApplyChangeWave(); - -#if DEBUG - Debug.Assert(_runningTests || Array.IndexOf(AllWaves, wave) >= 0, $"Change wave version {wave} is invalid"); -#endif - - return wave < _cachedWave; - } - - /// - /// Resets the state and value of the currently disabled version. - /// Used for testing only. - /// - internal static void ResetStateForTests() - { -#if DEBUG - _runningTests = true; -#endif - _cachedWave = null; - _state = ChangeWaveConversionState.NotConvertedYet; - } - - private static bool TryParseVersion(string stringVersion, out Version version) - { - try - { - version = new Version(stringVersion); - return true; - } - catch (Exception) - { - version = null; - return false; - } - } - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index e60d44f526a..308e67712b8 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -66,7 +66,6 @@ - From 5b7410aabd9b7236b2b7d4129ae4819bdd6c061d Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 09:32:10 -0800 Subject: [PATCH 058/136] MSBuildTaskHost: Remove BuildEnvironmentState All of the fields in BuildEnvironmentState are always false in MSBuildTaskHost. So, BuildEnvironemntState can be removed and all of the usages updated. --- .../Framework/BuildEnvironmentState.cs | 19 ------------------- .../Framework/InternalErrorException.cs | 6 +----- src/MSBuildTaskHost/Framework/Traits.cs | 14 +------------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - 4 files changed, 2 insertions(+), 38 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/BuildEnvironmentState.cs diff --git a/src/MSBuildTaskHost/Framework/BuildEnvironmentState.cs b/src/MSBuildTaskHost/Framework/BuildEnvironmentState.cs deleted file mode 100644 index 9743b2a5eab..00000000000 --- a/src/MSBuildTaskHost/Framework/BuildEnvironmentState.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable disable - -namespace Microsoft.Build.Framework -{ - /// - /// Class to encapsulate state that was stored in BuildEnvironmentHelper. - /// - /// - /// This should be deleted when BuildEnvironmentHelper can be moved into Framework. - /// - internal static class BuildEnvironmentState - { - internal static bool s_runningInVisualStudio = false; - internal static bool s_runningTests = false; - } -} diff --git a/src/MSBuildTaskHost/Framework/InternalErrorException.cs b/src/MSBuildTaskHost/Framework/InternalErrorException.cs index f85d7a7d57b..53c847f741f 100644 --- a/src/MSBuildTaskHost/Framework/InternalErrorException.cs +++ b/src/MSBuildTaskHost/Framework/InternalErrorException.cs @@ -116,7 +116,7 @@ private static void ConsiderDebuggerLaunch(string message, Exception innerExcept } #if DEBUG - if (!RunningTests() && Environment.GetEnvironmentVariable("MSBUILDDONOTLAUNCHDEBUGGER") == null + if (Environment.GetEnvironmentVariable("MSBUILDDONOTLAUNCHDEBUGGER") == null && Environment.GetEnvironmentVariable("_NTROOT") == null) { LaunchDebugger(message, innerMessage); @@ -144,9 +144,5 @@ private static void LaunchDebugger(string message, string innerMessage) #endif } #endregion - -#if DEBUG - private static bool RunningTests() => BuildEnvironmentState.s_runningTests; -#endif } } diff --git a/src/MSBuildTaskHost/Framework/Traits.cs b/src/MSBuildTaskHost/Framework/Traits.cs index 1756e8b40b8..44f1e552b93 100644 --- a/src/MSBuildTaskHost/Framework/Traits.cs +++ b/src/MSBuildTaskHost/Framework/Traits.cs @@ -10,19 +10,7 @@ namespace Microsoft.Build.Framework /// internal class Traits { - private static Traits _instance = new Traits(); - - public static Traits Instance - { - get - { - if (BuildEnvironmentState.s_runningTests) - { - return new Traits(); - } - return _instance; - } - } + public static Traits Instance { get; } = new Traits(); public Traits() { diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 308e67712b8..0f62aa750e4 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -61,7 +61,6 @@ - From dcb8acaa86f0e7d12205a29d301a15380ea0fb71 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 09:34:32 -0800 Subject: [PATCH 059/136] MSBuildTaskHost: Remove SupportOSPlatform attributes These attributes have no effect in .NET 3.5 and can be removed from MSBuildTaskHost. --- .../CommunicationsUtilities.cs | 6 ++--- .../Framework/SupportedOSPlatform.cs | 27 ------------------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - 3 files changed, 2 insertions(+), 32 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/SupportedOSPlatform.cs diff --git a/src/MSBuildTaskHost/CommunicationsUtilities.cs b/src/MSBuildTaskHost/CommunicationsUtilities.cs index 33d47355cf5..7aaacb1362d 100644 --- a/src/MSBuildTaskHost/CommunicationsUtilities.cs +++ b/src/MSBuildTaskHost/CommunicationsUtilities.cs @@ -414,15 +414,13 @@ internal static int NodeConnectionTimeout /// Get environment block. /// [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - [System.Runtime.Versioning.SupportedOSPlatform("windows")] - internal static extern unsafe char* GetEnvironmentStrings(); + private static extern unsafe char* GetEnvironmentStrings(); /// /// Free environment block. /// [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - [System.Runtime.Versioning.SupportedOSPlatform("windows")] - internal static extern unsafe bool FreeEnvironmentStrings(char* pStrings); + private static extern unsafe bool FreeEnvironmentStrings(char* pStrings); /// /// Set environment variable P/Invoke. diff --git a/src/MSBuildTaskHost/Framework/SupportedOSPlatform.cs b/src/MSBuildTaskHost/Framework/SupportedOSPlatform.cs deleted file mode 100644 index f532d4569e6..00000000000 --- a/src/MSBuildTaskHost/Framework/SupportedOSPlatform.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#if !NET6_0_OR_GREATER -namespace System.Runtime.Versioning -{ - /// - /// SupportedOSPlatform is a net5.0+ Attribute. - /// Create the same type only in full-framework and netstandard2.0 builds - /// to prevent many #if RUNTIME_TYPE_NETCORE checks. - /// - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] - internal class SupportedOSPlatformGuard : Attribute - { - internal SupportedOSPlatformGuard(string platformName) - { - } - } - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Class)] - internal class SupportedOSPlatform : Attribute - { - internal SupportedOSPlatform(string platformName) - { - } - } -} -#endif diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 0f62aa750e4..0f198c8d101 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -78,7 +78,6 @@ - From 325090721f4bc28502386d2fe02b75d7654c381f Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 09:51:12 -0800 Subject: [PATCH 060/136] MSBuildTaskHost: Remove uncalled members in ResourceUtilities --- src/MSBuildTaskHost/ResourceUtilities.cs | 154 ----------------------- 1 file changed, 154 deletions(-) diff --git a/src/MSBuildTaskHost/ResourceUtilities.cs b/src/MSBuildTaskHost/ResourceUtilities.cs index 241831873a9..e35a3b6dec5 100644 --- a/src/MSBuildTaskHost/ResourceUtilities.cs +++ b/src/MSBuildTaskHost/ResourceUtilities.cs @@ -151,23 +151,6 @@ internal static string FormatResourceStringStripCodeAndKeyword(out string? code, return ExtractMessageCode(true /* msbuildCodeOnly */, FormatString(GetResourceString(resourceName), args), out code); } - // Overloads with 0-3 arguments to avoid array allocations. - - /// - /// Loads the specified string resource and formats it with the arguments passed in. If the string resource has an MSBuild - /// message code and help keyword associated with it, they too are returned. - /// - /// This method is thread-safe. - /// [out] The MSBuild message code, or null. - /// [out] The MSBuild F1-help keyword for the host IDE, or null. - /// Resource string to load. - /// The formatted resource string. - internal static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName) - { - helpKeyword = GetHelpKeyword(resourceName); - return ExtractMessageCode(true, GetResourceString(resourceName), out code); - } - /// /// Loads the specified string resource and formats it with the arguments passed in. If the string resource has an MSBuild /// message code and help keyword associated with it, they too are returned. @@ -182,37 +165,6 @@ internal static string FormatResourceStringStripCodeAndKeyword(out string? code, return ExtractMessageCode(true, FormatString(GetResourceString(resourceName), arg1), out code); } - /// - /// Loads the specified string resource and formats it with the arguments passed in. If the string resource has an MSBuild - /// message code and help keyword associated with it, they too are returned. - /// - /// [out] The MSBuild message code, or null. - /// [out] The MSBuild F1-help keyword for the host IDE, or null. - /// Resource string to load. - /// First argument for formatting the resource string. - /// Second argument for formatting the resource string. - internal static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName, object? arg1, object? arg2) - { - helpKeyword = GetHelpKeyword(resourceName); - return ExtractMessageCode(true, FormatString(GetResourceString(resourceName), arg1, arg2), out code); - } - - /// - /// Loads the specified string resource and formats it with the arguments passed in. If the string resource has an MSBuild - /// message code and help keyword associated with it, they too are returned. - /// - /// [out] The MSBuild message code, or null. - /// [out] The MSBuild F1-help keyword for the host IDE, or null. - /// Resource string to load. - /// First argument for formatting the resource string. - /// Second argument for formatting the resource string. - /// Third argument for formatting the resource string. - internal static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName, object? arg1, object? arg2, object? arg3) - { - helpKeyword = GetHelpKeyword(resourceName); - return ExtractMessageCode(true, FormatString(GetResourceString(resourceName), arg1, arg2, arg3), out code); - } - /// /// Looks up a string in the resources, and formats it with the arguments passed in. If the string resource has an MSBuild /// message code and help keyword associated with it, they are discarded. @@ -227,18 +179,6 @@ internal static string FormatResourceStringStripCodeAndKeyword(out string? code, internal static string FormatResourceStringStripCodeAndKeyword(string resourceName, params object?[]? args) => FormatResourceStringStripCodeAndKeyword(out _, out _, resourceName, args); - // Overloads with 0-3 arguments to avoid array allocations. - - /// - /// Looks up a string in the resources. If the string resource has an MSBuild - /// message code and help keyword associated with it, they are discarded. - /// - /// This method is thread-safe. - /// Resource string to load. - /// The formatted resource string. - internal static string FormatResourceStringStripCodeAndKeyword(string resourceName) - => FormatResourceStringStripCodeAndKeyword(out _, out _, resourceName); - /// /// Looks up a string in the resources, and formats it with the argument passed in. If the string resource has an MSBuild /// message code and help keyword associated with it, they are discarded. @@ -250,86 +190,6 @@ internal static string FormatResourceStringStripCodeAndKeyword(string resourceNa internal static string FormatResourceStringStripCodeAndKeyword(string resourceName, object? arg1) => FormatResourceStringStripCodeAndKeyword(out _, out _, resourceName, arg1); - /// - /// Looks up a string in the resources, and formats it with the arguments passed in. If the string resource has an MSBuild - /// message code and help keyword associated with it, they are discarded. - /// - /// This method is thread-safe. - /// Resource string to load. - /// First argument for formatting the resource string. - /// Second argument for formatting the resource string. - /// The formatted resource string. - internal static string FormatResourceStringStripCodeAndKeyword(string resourceName, object? arg1, object? arg2) - => FormatResourceStringStripCodeAndKeyword(out _, out _, resourceName, arg1, arg2); - - /// - /// Looks up a string in the resources, and formats it with the arguments passed in. If the string resource has an MSBuild - /// message code and help keyword associated with it, they are discarded. - /// - /// This method is thread-safe. - /// Resource string to load. - /// First argument for formatting the resource string. - /// Second argument for formatting the resource string. - /// Third argument for formatting the resource string. - /// The formatted resource string. - internal static string FormatResourceStringStripCodeAndKeyword(string resourceName, object? arg1, object? arg2, object? arg3) - => FormatResourceStringStripCodeAndKeyword(out _, out _, resourceName, arg1, arg2, arg3); - - /// - /// Formats the resource string with the given arguments. - /// Ignores error codes and keywords. - /// - /// Resource string to load. - /// Optional arguments for formatting the resource string. - /// The formatted resource string. - /// the AssemblyResources.GetString() method is thread-safe. - internal static string FormatResourceStringIgnoreCodeAndKeyword(string resourceName, params object?[]? args) - => FormatString(GetResourceString(resourceName), args); - - // Overloads with 0-3 arguments to avoid array allocations. - - /// - /// Formats the resource string. - /// Ignores error codes and keywords. - /// - /// Resource string to load. - /// The formatted resource string. - internal static string FormatResourceStringIgnoreCodeAndKeyword(string resourceName) - => GetResourceString(resourceName); - - /// - /// Formats the resource string with the given argument. - /// Ignores error codes and keywords. - /// - /// Resource string to load. - /// Argument for formatting the resource string. - /// The formatted resource string. - internal static string FormatResourceStringIgnoreCodeAndKeyword(string resourceName, object? arg1) - => FormatString(GetResourceString(resourceName), arg1); - - /// - /// Formats the resource string with the given arguments. - /// Ignores error codes and keywords. - /// - /// Resource string to load. - /// First argument for formatting the resource string. - /// Second argument for formatting the resource string. - /// The formatted resource string. - internal static string FormatResourceStringIgnoreCodeAndKeyword(string resourceName, object? arg1, object? arg2) - => FormatString(GetResourceString(resourceName), arg1, arg2); - - /// - /// Formats the resource string with the given arguments. - /// Ignores error codes and keywords. - /// - /// Resource string to load. - /// First argument for formatting the resource string. - /// Second argument for formatting the resource string. - /// Third argument for formatting the resource string. - /// The formatted resource string. - internal static string FormatResourceStringIgnoreCodeAndKeyword(string resourceName, object? arg1, object? arg2, object? arg3) - => FormatString(GetResourceString(resourceName), arg1, arg2, arg3); - /// /// Formats the given string using the variable arguments passed in. /// @@ -385,20 +245,6 @@ internal static string FormatString(string unformatted, object? arg1, object? ar return string.Format(CultureInfo.CurrentCulture, unformatted, arg1, arg2); } - /// - /// Formats the given string using the variable arguments passed in. - /// - /// The string to format. - /// First argument for formatting the given string. - /// Second argument for formatting the given string. - /// Third argument for formatting the given string. - /// The formatted string. - internal static string FormatString(string unformatted, object? arg1, object? arg2, object? arg3) - { - ValidateArgsIfDebug([arg1, arg2, arg3]); - return string.Format(CultureInfo.CurrentCulture, unformatted, arg1, arg2, arg3); - } - [Conditional("DEBUG")] private static void ValidateArgsIfDebug(object?[] args) { From 86fd3c4c86de557813cb222a9b1dc0242ae7dd92 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 10:21:32 -0800 Subject: [PATCH 061/136] MSBuildTaskHost: Remove ITaskItem2 ITaskItem2 did not exist in the .NET 3.5 version of Microsoft.Build.Framework. So, it's impossible that MSBuildTaskHost will ever encounter a .NET 3.5 that implements ITaskItem2. The ITaskItem2 copy in MSBuildTaskHost can be wholly removed. --- src/MSBuildTaskHost/Framework/ITaskItem2.cs | 58 -------------------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - src/MSBuildTaskHost/TaskParameter.cs | 61 +++------------------ 3 files changed, 7 insertions(+), 113 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/ITaskItem2.cs diff --git a/src/MSBuildTaskHost/Framework/ITaskItem2.cs b/src/MSBuildTaskHost/Framework/ITaskItem2.cs deleted file mode 100644 index f245353a19b..00000000000 --- a/src/MSBuildTaskHost/Framework/ITaskItem2.cs +++ /dev/null @@ -1,58 +0,0 @@ -// 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; -using System.Runtime.InteropServices; - -#nullable disable - -namespace Microsoft.Build.Framework -{ - /// - /// This interface adds escaping support to the ITaskItem interface. - /// - [ComVisible(true)] - [Guid("ac6d5a59-f877-461b-88e3-b2f06fce0cb9")] - public interface ITaskItem2 : ITaskItem - { - /// - /// Gets or sets the item include value e.g. for disk-based items this would be the file path. - /// - /// - /// Taking the opportunity to fix the property name, although this doesn't - /// make it obvious it's an improvement on ItemSpec. - /// - string EvaluatedIncludeEscaped - { - get; - set; - } - - /// - /// Allows the values of metadata on the item to be queried. - /// - /// - /// Taking the opportunity to fix the property name, although this doesn't - /// make it obvious it's an improvement on GetMetadata. - /// - string GetMetadataValueEscaped(string metadataName); - - /// - /// Allows a piece of custom metadata to be set on the item. Assumes that the value passed - /// in is unescaped, and escapes the value as necessary in order to maintain its value. - /// - /// - /// Taking the opportunity to fix the property name, although this doesn't - /// make it obvious it's an improvement on SetMetadata. - /// - void SetMetadataValueLiteral(string metadataName, string metadataValue); - - /// - /// ITaskItem2 implementation which returns a clone of the metadata on this object. - /// Values returned are in their original escaped form. - /// - /// The cloned metadata, with values' escaping preserved. - IDictionary CloneCustomMetadataEscaped(); - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 0f198c8d101..27bec121af7 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -70,7 +70,6 @@ - diff --git a/src/MSBuildTaskHost/TaskParameter.cs b/src/MSBuildTaskHost/TaskParameter.cs index 8785feab094..942ad90fe6c 100644 --- a/src/MSBuildTaskHost/TaskParameter.cs +++ b/src/MSBuildTaskHost/TaskParameter.cs @@ -522,7 +522,7 @@ private void TranslateValueTypeArray(ITranslator translator) /// /// Super simple ITaskItem derivative that we can use as a container for read items. /// - private class TaskParameterTaskItem : MarshalByRefObject, ITaskItem, ITaskItem2, ITranslatable + private class TaskParameterTaskItem : MarshalByRefObject, ITaskItem, ITranslatable { /// /// The item spec @@ -549,22 +549,11 @@ private class TaskParameterTaskItem : MarshalByRefObject, ITaskItem, ITaskItem2, /// internal TaskParameterTaskItem(ITaskItem copyFrom) { - if (copyFrom is ITaskItem2 copyFromAsITaskItem2) + if (copyFrom is TaskParameterTaskItem taskParmeterTaskItem) { - _escapedItemSpec = copyFromAsITaskItem2.EvaluatedIncludeEscaped; - _escapedDefiningProject = copyFromAsITaskItem2.GetMetadataValueEscaped(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath); - - IDictionary nonGenericEscapedMetadata = copyFromAsITaskItem2.CloneCustomMetadataEscaped(); - _customEscapedMetadata = nonGenericEscapedMetadata as Dictionary; - - if (_customEscapedMetadata is null) - { - _customEscapedMetadata = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (DictionaryEntry entry in nonGenericEscapedMetadata) - { - _customEscapedMetadata[(string)entry.Key] = (string)entry.Value ?? string.Empty; - } - } + _escapedItemSpec = taskParmeterTaskItem._escapedItemSpec; + _escapedDefiningProject = taskParmeterTaskItem.GetMetadataValueEscaped(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath); + _customEscapedMetadata = new(taskParmeterTaskItem._customEscapedMetadata); } else { @@ -644,22 +633,6 @@ public int MetadataCount } } - /// - /// Returns the escaped version of this item's ItemSpec - /// - string ITaskItem2.EvaluatedIncludeEscaped - { - get - { - return _escapedItemSpec; - } - - set - { - _escapedItemSpec = value; - } - } - /// /// Allows the values of metadata on the item to be queried. /// @@ -667,7 +640,7 @@ string ITaskItem2.EvaluatedIncludeEscaped /// The value of the specified metadata. public string GetMetadata(string metadataName) { - string metadataValue = (this as ITaskItem2).GetMetadataValueEscaped(metadataName); + string metadataValue = GetMetadataValueEscaped(metadataName); return EscapingUtilities.UnescapeAll(metadataValue); } @@ -778,10 +751,7 @@ public override object InitializeLifetimeService() return null; } - /// - /// Returns the escaped value of the requested metadata name. - /// - string ITaskItem2.GetMetadataValueEscaped(string metadataName) + private string GetMetadataValueEscaped(string metadataName) { ErrorUtilities.VerifyThrowArgumentNull(metadataName); @@ -801,23 +771,6 @@ string ITaskItem2.GetMetadataValueEscaped(string metadataName) return metadataValue ?? String.Empty; } - /// - /// Sets the exact metadata value given to the metadata name requested. - /// - void ITaskItem2.SetMetadataValueLiteral(string metadataName, string metadataValue) - { - SetMetadata(metadataName, EscapingUtilities.Escape(metadataValue)); - } - - /// - /// Returns a dictionary containing all metadata and their escaped forms. - /// - IDictionary ITaskItem2.CloneCustomMetadataEscaped() - { - IDictionary clonedDictionary = new Dictionary(_customEscapedMetadata); - return clonedDictionary; - } - public IEnumerable> EnumerateMetadata() { if (!AppDomain.CurrentDomain.IsDefaultAppDomain()) From 586c2c1b419fb92c1435fda55c41fedb579cf8b1 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 10:34:33 -0800 Subject: [PATCH 062/136] MSBuildTaskHost: Remove uncalled members from TaskParameterTaskItem --- src/MSBuildTaskHost/TaskParameter.cs | 66 ++-------------------------- 1 file changed, 4 insertions(+), 62 deletions(-) diff --git a/src/MSBuildTaskHost/TaskParameter.cs b/src/MSBuildTaskHost/TaskParameter.cs index 942ad90fe6c..d49f461de17 100644 --- a/src/MSBuildTaskHost/TaskParameter.cs +++ b/src/MSBuildTaskHost/TaskParameter.cs @@ -549,11 +549,11 @@ private class TaskParameterTaskItem : MarshalByRefObject, ITaskItem, ITranslatab /// internal TaskParameterTaskItem(ITaskItem copyFrom) { - if (copyFrom is TaskParameterTaskItem taskParmeterTaskItem) + if (copyFrom is TaskParameterTaskItem taskParameterTaskItem) { - _escapedItemSpec = taskParmeterTaskItem._escapedItemSpec; - _escapedDefiningProject = taskParmeterTaskItem.GetMetadataValueEscaped(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath); - _customEscapedMetadata = new(taskParmeterTaskItem._customEscapedMetadata); + _escapedItemSpec = taskParameterTaskItem._escapedItemSpec; + _escapedDefiningProject = taskParameterTaskItem.GetMetadataValueEscaped(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath); + _customEscapedMetadata = new(taskParameterTaskItem._customEscapedMetadata); } else { @@ -771,64 +771,6 @@ private string GetMetadataValueEscaped(string metadataName) return metadataValue ?? String.Empty; } - public IEnumerable> EnumerateMetadata() - { - if (!AppDomain.CurrentDomain.IsDefaultAppDomain()) - { - return EnumerateMetadataEager(); - } - - return EnumerateMetadataLazy(); - } - - private IEnumerable> EnumerateMetadataEager() - { - if (_customEscapedMetadata == null || _customEscapedMetadata.Count == 0) - { - return []; - } - - var result = new KeyValuePair[_customEscapedMetadata.Count]; - int index = 0; - foreach (var kvp in _customEscapedMetadata) - { - var unescaped = new KeyValuePair(kvp.Key, EscapingUtilities.UnescapeAll(kvp.Value)); - result[index++] = unescaped; - } - - return result; - } - - private IEnumerable> EnumerateMetadataLazy() - { - if (_customEscapedMetadata == null) - { - yield break; - } - - foreach (var kvp in _customEscapedMetadata) - { - var unescaped = new KeyValuePair(kvp.Key, EscapingUtilities.UnescapeAll(kvp.Value)); - yield return unescaped; - } - } - - public void ImportMetadata(IEnumerable> metadata) - { - foreach (KeyValuePair kvp in metadata) - { - SetMetadata(kvp.Key, kvp.Value); - } - } - - public void RemoveMetadataRange(IEnumerable metadataNames) - { - foreach (string metadataName in metadataNames) - { - RemoveMetadata(metadataName); - } - } - public void Translate(ITranslator translator) { translator.Translate(ref _escapedItemSpec); From 20af3328e4755ed9e04a7775a6b3ea8735eb9cd3 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 13:14:30 -0800 Subject: [PATCH 063/136] MSBuildTaskHost: Remove TaskEngineAssemblyResolver TaskEngineAssemblyResolver is unused by MSBuildTaskHost --- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - .../TaskEngineAssemblyResolver.cs | 128 ------------------ 2 files changed, 129 deletions(-) delete mode 100644 src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 27bec121af7..2f176170068 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -106,7 +106,6 @@ - diff --git a/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs b/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs deleted file mode 100644 index 22d5244dd7e..00000000000 --- a/src/MSBuildTaskHost/TaskEngineAssemblyResolver.cs +++ /dev/null @@ -1,128 +0,0 @@ -// 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.IO; -using System.Reflection; -using System.Diagnostics; -using Microsoft.Build.Shared; -using Microsoft.Build.Shared.FileSystem; - -#nullable disable - -namespace Microsoft.Build.BackEnd.Logging -{ - /// - /// This is a helper class to install an AssemblyResolver event handler in whatever AppDomain this class is created in. - /// - internal class TaskEngineAssemblyResolver : MarshalByRefObject - { - /// - /// This public default constructor is needed so that instances of this class can be created by NDP. - /// - internal TaskEngineAssemblyResolver() - { - // do nothing - } - - /// - /// Initializes the instance. - /// - /// - internal void Initialize(string taskAssemblyFileToResolve) - { - _taskAssemblyFile = taskAssemblyFileToResolve; - } - - /// - /// Installs an AssemblyResolve handler in the current AppDomain. This class can be created in any AppDomain, - /// so it's possible to create an AppDomain, create an instance of this class in it and use this method to install - /// an event handler in that AppDomain. Since the event handler instance is stored internally, this method - /// should only be called once before a corresponding call to RemoveHandler (not that it would make sense to do - /// anything else). - /// - internal void InstallHandler() - { - Debug.Assert(_eventHandler == null, "The TaskEngineAssemblyResolver.InstallHandler method should only be called once!"); - - _eventHandler = new ResolveEventHandler(ResolveAssembly); - - AppDomain.CurrentDomain.AssemblyResolve += _eventHandler; - } - - /// - /// Removes the event handler. - /// - internal void RemoveHandler() - { - if (_eventHandler != null) - { - AppDomain.CurrentDomain.AssemblyResolve -= _eventHandler; - _eventHandler = null; - } - else - { - Debug.Assert(false, "There is no handler to remove."); - } - } - - /// - /// This is an assembly resolution handler necessary for fixing up types instantiated in different - /// AppDomains and loaded with a Assembly.LoadFrom equivalent call. See comments in TaskEngine.ExecuteTask - /// for more details. - /// - /// - /// - /// - internal Assembly ResolveAssembly(object sender, ResolveEventArgs args) - { - // Is this our task assembly? - if (_taskAssemblyFile != null) - { - if (FileSystems.Default.FileExists(_taskAssemblyFile)) - { - try - { - AssemblyNameExtension taskAssemblyName = new AssemblyNameExtension(AssemblyName.GetAssemblyName(_taskAssemblyFile)); - AssemblyNameExtension argAssemblyName = new AssemblyNameExtension(args.Name); - - if (taskAssemblyName.Equals(argAssemblyName)) - { - return Assembly.LoadFrom(_taskAssemblyFile); - } - } - // any problems with the task assembly? return null. - catch (FileNotFoundException) - { - return null; - } - catch (BadImageFormatException) - { - return null; - } - } - } - - // otherwise, have a nice day. - return null; - } - - /// - /// Overridden to give this class infinite lease time. Otherwise we end up with a limited - /// lease (5 minutes I think) and instances can expire if they take long time processing. - /// - [System.Security.SecurityCritical] - public override object InitializeLifetimeService() - { - // null means infinite lease time - return null; - } - - - // we have to store the event handler instance in case we have to remove it - private ResolveEventHandler _eventHandler = null; - - // path to the task assembly, but only if it's loaded using LoadFrom. If it's loaded with Load, this is null. - private string _taskAssemblyFile = null; - } -} From cc7731f8f6f8260739f0c10927e9d7b9c85fe593 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 13:16:47 -0800 Subject: [PATCH 064/136] MSBuildTaskHost: Remove AssemblyNameExtension and AssemblyNameComparer This types reference one another but are not used by MSBuildTaskHost. --- src/MSBuildTaskHost/AssemblyNameComparer.cs | 104 -- src/MSBuildTaskHost/AssemblyNameExtension.cs | 986 ------------------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 2 - 3 files changed, 1092 deletions(-) delete mode 100644 src/MSBuildTaskHost/AssemblyNameComparer.cs delete mode 100644 src/MSBuildTaskHost/AssemblyNameExtension.cs diff --git a/src/MSBuildTaskHost/AssemblyNameComparer.cs b/src/MSBuildTaskHost/AssemblyNameComparer.cs deleted file mode 100644 index f9a30fab128..00000000000 --- a/src/MSBuildTaskHost/AssemblyNameComparer.cs +++ /dev/null @@ -1,104 +0,0 @@ -// 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; -using System.Collections.Generic; - -#nullable disable - -namespace Microsoft.Build.Shared -{ - /// - /// IKeyComparer implementation that compares AssemblyNames for using in Hashtables. - /// - [Serializable] - internal sealed class AssemblyNameComparer : IComparer, IEqualityComparer, IEqualityComparer - { - /// - /// Comparer for two assembly name extensions - /// - internal static readonly IComparer Comparer = new AssemblyNameComparer(false); - - /// - /// Comparer for two assembly name extensions - /// - internal static readonly IComparer ComparerConsiderRetargetable = new AssemblyNameComparer(true); - - /// - /// Comparer for two assembly name extensions - /// - internal static readonly IEqualityComparer GenericComparer = Comparer as IEqualityComparer; - - /// - /// Comparer for two assembly name extensions - /// - internal static readonly IEqualityComparer GenericComparerConsiderRetargetable = ComparerConsiderRetargetable as IEqualityComparer; - - /// - /// Should the comparer consider the retargetable flag when doing comparisons - /// - private readonly bool considerRetargetableFlag; - - /// - /// Private construct so there's only one instance. - /// - private AssemblyNameComparer(bool considerRetargetableFlag) - { - this.considerRetargetableFlag = considerRetargetableFlag; - } - - /// - /// Compare o1 and o2 as AssemblyNames. - /// - public int Compare(object o1, object o2) - { - AssemblyNameExtension a1 = (AssemblyNameExtension)o1; - AssemblyNameExtension a2 = (AssemblyNameExtension)o2; - - int result = a1.CompareTo(a2, considerRetargetableFlag); - return result; - } - - /// - /// Treat o1 and o2 as AssemblyNames. Are they equal? - /// - public new bool Equals(object o1, object o2) - { - AssemblyNameExtension a1 = (AssemblyNameExtension)o1; - AssemblyNameExtension a2 = (AssemblyNameExtension)o2; - return Equals(a1, a2); - } - - /// - /// Get a hashcode for AssemblyName. - /// - public int GetHashCode(object o) - { - AssemblyNameExtension a = (AssemblyNameExtension)o; - return GetHashCode(a); - } - - #region IEqualityComparer Members - - /// - /// Determine if the assembly name extensions are equal - /// - public bool Equals(AssemblyNameExtension x, AssemblyNameExtension y) - { - bool result = x.Equals(y, considerRetargetableFlag); - return result; - } - - /// - /// Get a hashcode for AssemblyName. - /// - public int GetHashCode(AssemblyNameExtension obj) - { - int result = obj.GetHashCode(); - return result; - } - - #endregion - } -} diff --git a/src/MSBuildTaskHost/AssemblyNameExtension.cs b/src/MSBuildTaskHost/AssemblyNameExtension.cs deleted file mode 100644 index aaa2072ef91..00000000000 --- a/src/MSBuildTaskHost/AssemblyNameExtension.cs +++ /dev/null @@ -1,986 +0,0 @@ -// 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; -using System.Configuration.Assemblies; -using System.Globalization; -using System.IO; -using System.Reflection; -using System.Runtime.Serialization; -using System.Text; -using Microsoft.Build.BackEnd; - -#nullable disable - -namespace Microsoft.Build.Shared -{ - /// - /// Specifies the parts of the assembly name to partially match - /// - [FlagsAttribute] - internal enum PartialComparisonFlags : int - { - /// - /// Compare SimpleName A.PartialCompare(B,SimpleName) match the simple name on A and B if the simple name on A is not null. - /// - SimpleName = 1, // 0000 0000 0000 0001 - - /// - /// Compare Version A.PartialCompare(B, Version) match the Version on A and B if the Version on A is not null. - /// - Version = 2, // 0000 0000 0000 0010 - - /// - /// Compare Culture A.PartialCompare(B, Culture) match the Culture on A and B if the Culture on A is not null. - /// - Culture = 4, // 0000 0000 0000 0100 - - /// - /// Compare PublicKeyToken A.PartialCompare(B, PublicKeyToken) match the PublicKeyToken on A and B if the PublicKeyToken on A is not null. - /// - PublicKeyToken = 8, // 0000 0000 0000 1000 - - /// - /// When doing a comparison A.PartialCompare(B, Default) compare all fields of A which are not null with B. - /// - Default = 15, // 0000 0000 0000 1111 - } - - /// - /// A replacement for AssemblyName that optimizes calls to FullName which is expensive. - /// The assembly name is represented internally by an AssemblyName and a string, conversion - /// between the two is done lazily on demand. - /// - [Serializable] - internal sealed class AssemblyNameExtension : ISerializable, IEquatable, ITranslatable - { - private AssemblyName asAssemblyName = null; - private string asString = null; - private bool isSimpleName = false; - private bool hasProcessorArchitectureInFusionName; - private bool immutable; - - /// - /// Set of assemblyNameExtensions that THIS assemblyname was remapped from. - /// - private HashSet remappedFrom; - - private static readonly AssemblyNameExtension s_unnamedAssembly = new AssemblyNameExtension(); - - /// - /// Construct an unnamed assembly. - /// Private because we want only one of these. - /// - private AssemblyNameExtension() - { - } - - /// - /// Construct with AssemblyName. - /// - /// - internal AssemblyNameExtension(AssemblyName assemblyName) : this() - { - asAssemblyName = assemblyName; - } - - /// - /// Construct with string. - /// - /// - internal AssemblyNameExtension(string assemblyName) : this() - { - asString = assemblyName; - } - - /// - /// Construct from a string, but immediately construct a real AssemblyName. - /// This will cause an exception to be thrown up front if the assembly name - /// isn't well formed. - /// - /// - /// The string version of the assembly name. - /// - /// - /// Used when the assembly name comes from a user-controlled source like a project file or config file. - /// Does extra checking on the assembly name and will throw exceptions if something is invalid. - /// - internal AssemblyNameExtension(string assemblyName, bool validate) : this() - { - asString = assemblyName; - - if (validate) - { - // This will throw... - CreateAssemblyName(); - } - } - - /// - /// Ctor for deserializing from state file (binary serialization). - /// This is required because AssemblyName is not Serializable on .NET Core. - /// - /// - /// - private AssemblyNameExtension(SerializationInfo info, StreamingContext context) - { - var hasAssemblyName = info.GetBoolean("hasAN"); - - if (hasAssemblyName) - { - var name = info.GetString("name"); - var publicKey = (byte[])info.GetValue("pk", typeof(byte[])); - var publicKeyToken = (byte[])info.GetValue("pkt", typeof(byte[])); - var version = (Version)info.GetValue("ver", typeof(Version)); - var flags = (AssemblyNameFlags)info.GetInt32("flags"); - var processorArchitecture = (ProcessorArchitecture)info.GetInt32("cpuarch"); - - CultureInfo cultureInfo = null; - var hasCultureInfo = info.GetBoolean("hasCI"); - if (hasCultureInfo) - { - cultureInfo = new CultureInfo(info.GetInt32("ci")); - } - - var hashAlgorithm = (System.Configuration.Assemblies.AssemblyHashAlgorithm)info.GetInt32("hashAlg"); - var versionCompatibility = (AssemblyVersionCompatibility)info.GetInt32("verCompat"); - var codeBase = info.GetString("codebase"); - - asAssemblyName = new AssemblyName - { - Name = name, - Version = version, - Flags = flags, - ProcessorArchitecture = processorArchitecture, - CultureInfo = cultureInfo, - HashAlgorithm = hashAlgorithm, - VersionCompatibility = versionCompatibility, - CodeBase = codeBase, - }; - - asAssemblyName.SetPublicKey(publicKey); - asAssemblyName.SetPublicKeyToken(publicKeyToken); - } - - asString = info.GetString("asStr"); - isSimpleName = info.GetBoolean("isSName"); - hasProcessorArchitectureInFusionName = info.GetBoolean("hasCpuArch"); - immutable = info.GetBoolean("immutable"); - remappedFrom = (HashSet)info.GetValue("remapped", typeof(HashSet)); - } - - /// - /// Ctor for deserializing from state file (custom binary serialization) using translator. - /// - internal AssemblyNameExtension(ITranslator translator) : this() - { - Translate(translator); - } - - /// - /// To be used as a delegate. Gets the AssemblyName of the given file. - /// - /// - /// - internal static AssemblyNameExtension GetAssemblyNameEx(string path) - { - try - { - return new AssemblyNameExtension(AssemblyName.GetAssemblyName(path)); - } - catch (FileLoadException) - { - // Its pretty hard to get here, you need an assembly that contains a valid reference - // to a dependent assembly that, in turn, throws a FileLoadException during GetAssemblyName. - // Still it happened once, with an older version of the CLR. - } - catch (FileNotFoundException) - { - // Its pretty hard to get here, also since we do a file existence check right before calling this method so it can only happen if the file got deleted between that check and this call. - } - - return null; - } - - /// - /// Run after the object has been deserialized - /// - [OnDeserialized] - private void SetRemappedFromDefaultAfterSerialization(StreamingContext sc) - { - InitializeRemappedFrom(); - } - - /// - /// Initialize the remapped from structure. - /// - private void InitializeRemappedFrom() - { - if (remappedFrom == null) - { - remappedFrom = CreateRemappedFrom(); - } - } - - /// - /// Create remappedFrom HashSet. Used by deserialization as well. - /// - private static HashSet CreateRemappedFrom() - { - return new HashSet(AssemblyNameComparer.GenericComparerConsiderRetargetable); - } - - /// - /// Assume there is a string version, create the AssemblyName version. - /// - private void CreateAssemblyName() - { - if (asAssemblyName == null) - { - asAssemblyName = GetAssemblyNameFromDisplayName(asString); - - if (asAssemblyName != null) - { - hasProcessorArchitectureInFusionName = asString.IndexOf("ProcessorArchitecture", StringComparison.OrdinalIgnoreCase) != -1; - isSimpleName = ((Version == null) && (CultureInfo == null) && (GetPublicKeyToken() == null) && (!hasProcessorArchitectureInFusionName)); - } - } - } - - /// - /// Assume there is a string version, create the AssemblyName version. - /// - private void CreateFullName() - { - if (asString == null) - { - asString = asAssemblyName.FullName; - } - } - - /// - /// The base name of the assembly. - /// - /// - internal string Name - { - get - { - // Is there a string? - CreateAssemblyName(); - return asAssemblyName.Name; - } - } - - /// - /// Gets the backing AssemblyName, this can be None. - /// - internal ProcessorArchitecture ProcessorArchitecture => - asAssemblyName?.ProcessorArchitecture ?? ProcessorArchitecture.None; - - /// - /// The assembly's version number. - /// - /// - internal Version Version - { - get - { - // Is there a string? - CreateAssemblyName(); - return asAssemblyName.Version; - } - } - - /// - /// Is the assembly a complex name or a simple name. A simple name is where only the name is set - /// a complex name is where the version, culture or publickeytoken is also set - /// - internal bool IsSimpleName - { - get - { - CreateAssemblyName(); - return isSimpleName; - } - } - - /// - /// Does the fullName have the processor architecture defined - /// - internal bool HasProcessorArchitectureInFusionName - { - get - { - CreateAssemblyName(); - return hasProcessorArchitectureInFusionName; - } - } - - /// - /// Replace the current version with a new version. - /// - /// - internal void ReplaceVersion(Version version) - { - ErrorUtilities.VerifyThrow(!immutable, "Object is immutable cannot replace the version"); - CreateAssemblyName(); - if (asAssemblyName.Version != version) - { - asAssemblyName.Version = version; - - // String would now be invalid. - asString = null; - } - } - - /// - /// The assembly's Culture - /// - /// - internal CultureInfo CultureInfo - { - get - { - // Is there a string? - CreateAssemblyName(); - return asAssemblyName.CultureInfo; - } - } - - /// - /// The assembly's retargetable bit - /// - /// - internal bool Retargetable - { - get - { - // Is there a string? - CreateAssemblyName(); - // Cannot use the HasFlag method on the Flags enum because this class needs to work with 3.5 - return (asAssemblyName.Flags & AssemblyNameFlags.Retargetable) == AssemblyNameFlags.Retargetable; - } - } - - /// - /// The full name of the original extension we were before being remapped. - /// - internal IEnumerable RemappedFromEnumerator - { - get - { - InitializeRemappedFrom(); - return remappedFrom; - } - } - - /// - /// Add an assemblyNameExtension which represents an assembly name which was mapped to THIS assemblyName. - /// - internal void AddRemappedAssemblyName(AssemblyNameExtension extensionToAdd) - { - ErrorUtilities.VerifyThrow(extensionToAdd.Immutable, "ExtensionToAdd is not immutable"); - InitializeRemappedFrom(); - remappedFrom.Add(extensionToAdd); - } - - /// - /// As an AssemblyName - /// - /// - internal AssemblyName AssemblyName - { - get - { - // Is there a string? - CreateAssemblyName(); - return asAssemblyName; - } - } - - /// - /// The assembly's full name. - /// - /// - internal string FullName - { - get - { - // Is there a string? - CreateFullName(); - return asString; - } - } - - /// - /// Get the assembly's public key token. - /// - /// - internal byte[] GetPublicKeyToken() - { - // Is there a string? - CreateAssemblyName(); - return asAssemblyName.GetPublicKeyToken(); - } - - - /// - /// A special "unnamed" instance of AssemblyNameExtension. - /// - /// - internal static AssemblyNameExtension UnnamedAssembly => s_unnamedAssembly; - - /// - /// Compare one assembly name to another. - /// - /// - /// - internal int CompareTo(AssemblyNameExtension that) - { - return CompareTo(that, false); - } - - /// - /// Compare one assembly name to another. - /// - internal int CompareTo(AssemblyNameExtension that, bool considerRetargetableFlag) - { - // Are they identical? - if (this.Equals(that, considerRetargetableFlag)) - { - return 0; - } - - // Are the base names not identical? - int result = CompareBaseNameTo(that); - if (result != 0) - { - return result; - } - - // We would like to compare the version numerically rather than alphabetically (because for example version 10.0.0. should be below 9 not between 1 and 2) - if (this.Version != that.Version) - { - if (this.Version == null) - { - // This is therefore less than that. Since this is null and that is not null - return -1; - } - - // Will not return 0 as the this != that check above takes care of the case where they are equal. - return this.Version.CompareTo(that.Version); - } - - // We need some final collating order for these, alphabetical by FullName seems as good as any. - return string.Compare(this.FullName, that.FullName, StringComparison.OrdinalIgnoreCase); - } - - /// - /// Get a hash code for this assembly name. - /// - /// - internal new int GetHashCode() - { - // Ok, so this isn't a great hashing algorithm. However, basenames with different - // versions or PKTs are relatively uncommon and so collisions should be low. - // Hashing on FullName is wrong because the order of tuple fields is undefined. - int hash = StringComparer.OrdinalIgnoreCase.GetHashCode(this.Name); - return hash; - } - - /// - /// Compare two base names as quickly as possible. - /// - /// - /// - internal int CompareBaseNameTo(AssemblyNameExtension that) - { - int result = CompareBaseNameToImpl(that); -#if DEBUG - // Now, compare to the real value to make sure the result was accurate. - AssemblyName a1 = asAssemblyName; - AssemblyName a2 = that.asAssemblyName; - if (a1 == null) - { - a1 = new AssemblyName(asString); - } - if (a2 == null) - { - a2 = new AssemblyName(that.asString); - } - - int baselineResult = string.Compare(a1.Name, a2.Name, StringComparison.OrdinalIgnoreCase); - ErrorUtilities.VerifyThrow(result == baselineResult, "Optimized version of CompareBaseNameTo didn't return the same result as the baseline."); -#endif - return result; - } - - /// - /// An implementation of compare that compares two base - /// names as quickly as possible. - /// - /// - /// - private int CompareBaseNameToImpl(AssemblyNameExtension that) - { - // Pointer compare, if identical then base names are equal. - if (this == that) - { - return 0; - } - - // Do both have assembly names? - if (asAssemblyName != null && that.asAssemblyName != null) - { - // Pointer compare or base name compare. - return asAssemblyName == that.asAssemblyName - ? 0 - : string.Compare(asAssemblyName.Name, that.asAssemblyName.Name, StringComparison.OrdinalIgnoreCase); - } - - // Do both have strings? - if (asString != null && that.asString != null) - { - // If we have two random-case strings, then we need to compare case sensitively. - return CompareBaseNamesStringWise(asString, that.asString); - } - - // Fall back to comparing by name. This is the slow path. - return string.Compare(this.Name, that.Name, StringComparison.OrdinalIgnoreCase); - } - - /// - /// Compare two basenames. - /// - /// - /// - /// - private static int CompareBaseNamesStringWise(string asString1, string asString2) - { - // Identical strings just match. - if (asString1 == asString2) - { - return 0; - } - - // Get the lengths of base names to compare. - int baseLenThis = asString1.IndexOf(','); - int baseLenThat = asString2.IndexOf(','); - if (baseLenThis == -1) - { - baseLenThis = asString1.Length; - } - if (baseLenThat == -1) - { - baseLenThat = asString2.Length; - } - - // If the lengths are the same then we can compare without copying. - if (baseLenThis == baseLenThat) - { - return string.Compare(asString1, 0, asString2, 0, baseLenThis, StringComparison.OrdinalIgnoreCase); - } - - // Lengths are different, so string copy is required. - string nameThis = asString1.Substring(0, baseLenThis); - string nameThat = asString2.Substring(0, baseLenThat); - return string.Compare(nameThis, nameThat, StringComparison.OrdinalIgnoreCase); - } - - /// - /// Clone this assemblyNameExtension - /// - internal AssemblyNameExtension Clone() - { - AssemblyNameExtension newExtension = new(); - - if (asAssemblyName != null) - { - newExtension.asAssemblyName = (AssemblyName)asAssemblyName.Clone(); - } - - newExtension.asString = asString; - newExtension.isSimpleName = isSimpleName; - newExtension.hasProcessorArchitectureInFusionName = hasProcessorArchitectureInFusionName; - newExtension.remappedFrom = remappedFrom; - - // We are cloning so we can now party on the object even if the parent was immutable - newExtension.immutable = false; - - return newExtension; - } - - /// - /// Clone the object but mark and mark the cloned object as immutable - /// - /// - internal AssemblyNameExtension CloneImmutable() - { - AssemblyNameExtension clonedExtension = Clone(); - clonedExtension.MarkImmutable(); - return clonedExtension; - } - - /// - /// Is this object immutable - /// - public bool Immutable => immutable; - - /// - /// Mark this object as immutable - /// - internal void MarkImmutable() - { - immutable = true; - } - - /// - /// Compare two assembly names for equality. - /// - /// - /// - internal bool Equals(AssemblyNameExtension that) - { - return EqualsImpl(that, false, false); - } - - /// - /// Interface method for IEquatable<AssemblyNameExtension> - /// - /// - /// - bool IEquatable.Equals(AssemblyNameExtension other) - { - return Equals(other); - } - - /// - /// Compare two assembly names for equality ignoring version. - /// - /// - /// - internal bool EqualsIgnoreVersion(AssemblyNameExtension that) - { - return EqualsImpl(that, true, false); - } - - /// - /// Compare two assembly names and consider the retargetable flag during the comparison - /// - internal bool Equals(AssemblyNameExtension that, bool considerRetargetableFlag) - { - return EqualsImpl(that, false, considerRetargetableFlag); - } - - /// - /// Compare two assembly names for equality. - /// - private bool EqualsImpl(AssemblyNameExtension that, bool ignoreVersion, bool considerRetargetableFlag) - { - // Pointer compare. - if (object.ReferenceEquals(this, that)) - { - return true; - } - - // If that is null then this and that are not equal. Also, this would cause a crash on the next line. - if (that is null) - { - return false; - } - - // Do both have assembly names? - if (asAssemblyName != null && that.asAssemblyName != null) - { - // Pointer compare. - if (object.ReferenceEquals(asAssemblyName, that.asAssemblyName)) - { - return true; - } - } - - // Do both have strings that equal each-other? - if (asString != null && that.asString != null) - { - if (asString == that.asString) - { - return true; - } - - // If they weren't identical then they might still differ only by - // case. So we can't assume that they don't match. So fall through... - } - - // Do the names match? - if (!string.Equals(Name, that.Name, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (!ignoreVersion && (this.Version != that.Version)) - { - return false; - } - - if (!CompareCultures(AssemblyName, that.AssemblyName)) - { - return false; - } - - if (!ComparePublicKeyToken(that)) - { - return false; - } - - if (considerRetargetableFlag && this.Retargetable != that.Retargetable) - { - return false; - } - - return true; - } - - /// - /// Allows the comparison of the culture. - /// - internal static bool CompareCultures(AssemblyName a, AssemblyName b) - { - // Do the Cultures match? - CultureInfo aCulture = a.CultureInfo; - CultureInfo bCulture = b.CultureInfo; - if (aCulture == null) - { - aCulture = CultureInfo.InvariantCulture; - } - if (bCulture == null) - { - bCulture = CultureInfo.InvariantCulture; - } - - return aCulture.LCID == bCulture.LCID; - } - - /// - /// Allows the comparison of just the PublicKeyToken - /// - internal bool ComparePublicKeyToken(AssemblyNameExtension that) - { - // Do the PKTs match? - byte[] aPKT = GetPublicKeyToken(); - byte[] bPKT = that.GetPublicKeyToken(); - return ComparePublicKeyTokens(aPKT, bPKT); - } - - /// - /// Compare two public key tokens. - /// - internal static bool ComparePublicKeyTokens(byte[] aPKT, byte[] bPKT) - { - // Some assemblies (real case was interop assembly) may have null PKTs. - aPKT ??= []; - bPKT ??= []; - - if (aPKT.Length != bPKT.Length) - { - return false; - } - - for (int i = 0; i < aPKT.Length; ++i) - { - if (aPKT[i] != bPKT[i]) - { - return false; - } - } - - return true; - } - - /// - /// Only the unnamed assembly has both null assemblyname and null string. - /// - /// - internal bool IsUnnamedAssembly => asAssemblyName == null && asString == null; - - /// - /// Given a display name, construct an assembly name. - /// - /// The display name. - /// The assembly name. - private static AssemblyName GetAssemblyNameFromDisplayName(string displayName) - { - AssemblyName assemblyName = new AssemblyName(displayName); - return assemblyName; - } - - /// - /// Return a string that has AssemblyName special characters escaped. - /// Those characters are Equals(=), Comma(,), Quote("), Apostrophe('), Backslash(\). - /// - /// - /// WARNING! This method is not meant as a general purpose escaping method for assembly names. - /// Use only if you really know that this does what you need. - /// - /// - /// - internal static string EscapeDisplayNameCharacters(string displayName) - { - StringBuilder sb = new StringBuilder(displayName); - sb = sb.Replace("\\", "\\\\"); - sb = sb.Replace("=", "\\="); - sb = sb.Replace(",", "\\,"); - sb = sb.Replace("\"", "\\\""); - sb = sb.Replace("'", "\\'"); - return sb.ToString(); - } - - /// - /// Convert to a string for display. - /// - /// - public override string ToString() - { - CreateFullName(); - return asString; - } - - /// - /// Compare the fields of this with that if they are not null. - /// - internal bool PartialNameCompare(AssemblyNameExtension that) - { - return PartialNameCompare(that, PartialComparisonFlags.Default, false /* do not consider retargetable flag*/); - } - - /// - /// Compare the fields of this with that if they are not null. - /// - internal bool PartialNameCompare(AssemblyNameExtension that, bool considerRetargetableFlag) - { - return PartialNameCompare(that, PartialComparisonFlags.Default, considerRetargetableFlag); - } - - /// - /// Do a partial comparison between two assembly name extensions. - /// Compare the fields of A and B on the following conditions: - /// 1) A.Field has a non null value - /// 2) The field has been selected in the comparison flags or the default comparison flags are passed in. - /// - /// If A.Field is null then we will not compare A.Field and B.Field even when the comparison flag is set for that field unless skipNullFields is false. - /// - internal bool PartialNameCompare(AssemblyNameExtension that, PartialComparisonFlags comparisonFlags) - { - return PartialNameCompare(that, comparisonFlags, false /* do not consider retargetable flag*/); - } - - /// - /// Do a partial comparison between two assembly name extensions. - /// Compare the fields of A and B on the following conditions: - /// 1) A.Field has a non null value - /// 2) The field has been selected in the comparison flags or the default comparison flags are passed in. - /// - /// If A.Field is null then we will not compare A.Field and B.Field even when the comparison flag is set for that field unless skipNullFields is false. - /// - internal bool PartialNameCompare(AssemblyNameExtension that, PartialComparisonFlags comparisonFlags, bool considerRetargetableFlag) - { - // Pointer compare. - if (object.ReferenceEquals(this, that)) - { - return true; - } - - // If that is null then this and that are not equal. Also, this would cause a crash on the next line. - if (that is null) - { - return false; - } - - // Do both have assembly names? - if (asAssemblyName != null && that.asAssemblyName != null) - { - // Pointer compare. - if (object.ReferenceEquals(asAssemblyName, that.asAssemblyName)) - { - return true; - } - } - - // Do the names match? - if ((comparisonFlags & PartialComparisonFlags.SimpleName) != 0 && Name != null && !string.Equals(Name, that.Name, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if ((comparisonFlags & PartialComparisonFlags.Version) != 0 && Version != null && this.Version != that.Version) - { - return false; - } - - if ((comparisonFlags & PartialComparisonFlags.Culture) != 0 && CultureInfo != null && (that.CultureInfo == null || !CompareCultures(AssemblyName, that.AssemblyName))) - { - return false; - } - - if ((comparisonFlags & PartialComparisonFlags.PublicKeyToken) != 0 && GetPublicKeyToken() != null && !ComparePublicKeyToken(that)) - { - return false; - } - - if (considerRetargetableFlag && (Retargetable != that.Retargetable)) - { - return false; - } - return true; - } - - public void GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue("hasAN", asAssemblyName != null); - if (asAssemblyName != null) - { - info.AddValue("name", asAssemblyName.Name); - info.AddValue("pk", asAssemblyName.GetPublicKey()); - info.AddValue("pkt", asAssemblyName.GetPublicKeyToken()); - info.AddValue("ver", asAssemblyName.Version); - info.AddValue("flags", (int)asAssemblyName.Flags); - info.AddValue("cpuarch", (int)asAssemblyName.ProcessorArchitecture); - - info.AddValue("hasCI", asAssemblyName.CultureInfo != null); - if (asAssemblyName.CultureInfo != null) - { - info.AddValue("ci", asAssemblyName.CultureInfo.LCID); - } - - info.AddValue("hashAlg", asAssemblyName.HashAlgorithm); - info.AddValue("verCompat", asAssemblyName.VersionCompatibility); - info.AddValue("codebase", asAssemblyName.CodeBase); - } - - info.AddValue("asStr", asString); - info.AddValue("isSName", isSimpleName); - info.AddValue("hasCpuArch", hasProcessorArchitectureInFusionName); - info.AddValue("immutable", immutable); - info.AddValue("remapped", remappedFrom); - } - - /// - /// Reads/writes this class - /// - /// - public void Translate(ITranslator translator) - { - translator.Translate(ref asAssemblyName); - translator.Translate(ref asString); - translator.Translate(ref isSimpleName); - translator.Translate(ref hasProcessorArchitectureInFusionName); - translator.Translate(ref immutable); - - // TODO: consider some kind of protection against infinite loop during serialization, hint: pre serialize check for cycle in graph - translator.TranslateHashSet(ref remappedFrom, - (t) => new AssemblyNameExtension(t), - (capacity) => CreateRemappedFrom()); - } - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 2f176170068..9d8f747988b 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -39,8 +39,6 @@ - - From 92f15fc2918088d1a70e24e9cae96aafe37036fe Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 13:24:12 -0800 Subject: [PATCH 065/136] MSBuildTaskHost: Remove IFileSystem and related types BuildEnvironmentHelper and ItemSpecModifiers call FileExists and DirectoryExists through IFileSystem. However, MSBuildTaskHost has just a single IFileSystem implementation that calls into NativeMethods.FileExists and NativeMethods.DirectoryExists. So, those few calls can be updated to call through NativeMethods directly and the IFileSystem implementation can be removed. --- src/MSBuildTaskHost/BuildEnvironmentHelper.cs | 9 +- src/MSBuildTaskHost/FileSystem/FileSystems.cs | 20 ----- src/MSBuildTaskHost/FileSystem/IFileSystem.cs | 49 ----------- .../FileSystem/MSBuildTaskHostFileSystem.cs | 83 ------------------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 3 - src/MSBuildTaskHost/Modifiers.cs | 6 +- 6 files changed, 6 insertions(+), 164 deletions(-) delete mode 100644 src/MSBuildTaskHost/FileSystem/FileSystems.cs delete mode 100644 src/MSBuildTaskHost/FileSystem/IFileSystem.cs delete mode 100644 src/MSBuildTaskHost/FileSystem/MSBuildTaskHostFileSystem.cs diff --git a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs index 6b6b3799791..46e3265f7f4 100644 --- a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs +++ b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; -using Microsoft.Build.Shared.FileSystem; #nullable disable @@ -158,11 +157,11 @@ private static BuildEnvironment TryFromMSBuildAssembly() // We're not in VS, check for MSBuild.exe / dll to consider this a standalone environment. string msBuildPath = null; - if (FileSystems.Default.FileExists(msBuildExe)) + if (NativeMethodsShared.FileExists(msBuildExe)) { msBuildPath = msBuildExe; } - else if (FileSystems.Default.FileExists(msBuildDll)) + else if (NativeMethodsShared.FileExists(msBuildDll)) { msBuildPath = msBuildDll; } @@ -200,7 +199,7 @@ private static BuildEnvironment TryFromDevConsole() var vsVersion = GetEnvironmentVariable("VisualStudioVersion"); if (string.IsNullOrEmpty(vsInstallDir) || string.IsNullOrEmpty(vsVersion) || - vsVersion != CurrentVisualStudioVersion || !FileSystems.Default.DirectoryExists(vsInstallDir)) + vsVersion != CurrentVisualStudioVersion || !NativeMethodsShared.DirectoryExists(vsInstallDir)) { return null; } @@ -212,7 +211,7 @@ private static BuildEnvironment TryFromDevConsole() private static BuildEnvironment TryFromStandaloneMSBuildExe(string msBuildExePath) { - if (!string.IsNullOrEmpty(msBuildExePath) && FileSystems.Default.FileExists(msBuildExePath)) + if (!string.IsNullOrEmpty(msBuildExePath) && NativeMethodsShared.FileExists(msBuildExePath)) { // MSBuild.exe was found outside of Visual Studio. Assume Standalone mode. return new BuildEnvironment(BuildEnvironmentMode.Standalone, msBuildExePath); diff --git a/src/MSBuildTaskHost/FileSystem/FileSystems.cs b/src/MSBuildTaskHost/FileSystem/FileSystems.cs deleted file mode 100644 index d1dc29c6a6f..00000000000 --- a/src/MSBuildTaskHost/FileSystem/FileSystems.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable disable - -namespace Microsoft.Build.Shared.FileSystem -{ - /// - /// Factory for - /// - internal static class FileSystems - { - public static IFileSystem Default = GetFileSystem(); - - private static IFileSystem GetFileSystem() - { - return MSBuildTaskHostFileSystem.Singleton(); - } - } -} diff --git a/src/MSBuildTaskHost/FileSystem/IFileSystem.cs b/src/MSBuildTaskHost/FileSystem/IFileSystem.cs deleted file mode 100644 index 8bfcb130067..00000000000 --- a/src/MSBuildTaskHost/FileSystem/IFileSystem.cs +++ /dev/null @@ -1,49 +0,0 @@ -// 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; -using System.IO; - -#nullable disable - -namespace Microsoft.Build.Shared.FileSystem -{ - - /* - * This is a clone of Microsoft.Build.FileSystem.MSBuildFileSystemBase. - * MSBuildFileSystemBase is the public, reference interface. Changes should be made to MSBuildFileSystemBase and cloned in IFileSystem. - * Any new code should depend on MSBuildFileSystemBase instead of IFileSystem, if possible. - * - * MSBuild uses IFileSystem internally and adapts MSBuildFileSystemBase instances received from the outside to IFileSystem. - * Ideally there should be only one, public interface. However, such an interface would need to be put into the - * Microsoft.Build.Framework assembly, but that assembly cannot take new types because it breaks some old version of Nuget.exe. - * IFileSystem cannot be deleted for the same reason. - */ - internal interface IFileSystem - { - TextReader ReadFile(string path); - - Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share); - - string ReadFileAllText(string path); - - byte[] ReadFileAllBytes(string path); - - IEnumerable EnumerateFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly); - - IEnumerable EnumerateDirectories(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly); - - IEnumerable EnumerateFileSystemEntries(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly); - - FileAttributes GetAttributes(string path); - - DateTime GetLastWriteTimeUtc(string path); - - bool DirectoryExists(string path); - - bool FileExists(string path); - - bool FileOrDirectoryExists(string path); - } -} diff --git a/src/MSBuildTaskHost/FileSystem/MSBuildTaskHostFileSystem.cs b/src/MSBuildTaskHost/FileSystem/MSBuildTaskHostFileSystem.cs deleted file mode 100644 index 46cb997dc51..00000000000 --- a/src/MSBuildTaskHost/FileSystem/MSBuildTaskHostFileSystem.cs +++ /dev/null @@ -1,83 +0,0 @@ -// 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; -using System.IO; - -#nullable disable - -namespace Microsoft.Build.Shared.FileSystem -{ - /// - /// Legacy implementation for MSBuildTaskHost which is stuck on net20 APIs - /// - internal class MSBuildTaskHostFileSystem : IFileSystem - { - private static readonly MSBuildTaskHostFileSystem Instance = new MSBuildTaskHostFileSystem(); - - public static MSBuildTaskHostFileSystem Singleton() => Instance; - - public bool FileOrDirectoryExists(string path) - { - return NativeMethodsShared.FileOrDirectoryExists(path); - } - - public FileAttributes GetAttributes(string path) - { - return File.GetAttributes(path); - } - - public DateTime GetLastWriteTimeUtc(string path) - { - return File.GetLastWriteTimeUtc(path); - } - - public bool DirectoryExists(string path) - { - return NativeMethodsShared.DirectoryExists(path); - } - - public IEnumerable EnumerateDirectories(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly) - { - return Directory.GetDirectories(path, searchPattern, searchOption); - } - - public TextReader ReadFile(string path) - { - return new StreamReader(path); - } - - public Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share) - { - return new FileStream(path, mode, access, share); - } - - public string ReadFileAllText(string path) - { - return File.ReadAllText(path); - } - - public byte[] ReadFileAllBytes(string path) - { - return File.ReadAllBytes(path); - } - - public IEnumerable EnumerateFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly) - { - return Directory.GetFiles(path, searchPattern, searchOption); - } - - public IEnumerable EnumerateFileSystemEntries(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly) - { - ErrorUtilities.VerifyThrow(searchOption == SearchOption.TopDirectoryOnly, $"In net20 {nameof(Directory.GetFileSystemEntries)} does not take a {nameof(SearchOption)} parameter"); - - return Directory.GetFileSystemEntries(path, searchPattern); - } - - public bool FileExists(string path) - { - return NativeMethodsShared.FileExists(path); - } - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 9d8f747988b..494a2115522 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -52,9 +52,6 @@ - - - diff --git a/src/MSBuildTaskHost/Modifiers.cs b/src/MSBuildTaskHost/Modifiers.cs index 0db4aaa85a6..4e05a68d659 100644 --- a/src/MSBuildTaskHost/Modifiers.cs +++ b/src/MSBuildTaskHost/Modifiers.cs @@ -5,8 +5,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; -using Microsoft.Build.Framework; -using Microsoft.Build.Shared.FileSystem; #nullable disable @@ -296,7 +294,7 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS // to unescape first. string unescapedItemSpec = EscapingUtilities.UnescapeAll(itemSpec); - if (FileSystems.Default.FileExists(unescapedItemSpec)) + if (NativeMethodsShared.FileExists(unescapedItemSpec)) { modifiedItemSpec = File.GetCreationTime(unescapedItemSpec).ToString(FileTimeFormat, null); } @@ -312,7 +310,7 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS // to unescape first. string unescapedItemSpec = EscapingUtilities.UnescapeAll(itemSpec); - if (FileSystems.Default.FileExists(unescapedItemSpec)) + if (NativeMethodsShared.FileExists(unescapedItemSpec)) { modifiedItemSpec = File.GetLastAccessTime(unescapedItemSpec).ToString(FileTimeFormat, null); } From c635ab64c676c6510ec66222b4fa0cda81eb3609 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 13:25:28 -0800 Subject: [PATCH 066/136] MSBuildTaskHost: Remove AnsiDetector AnsiDetector is unused by MSBuildTaskHost --- .../Framework/Logging/AnsiDetector.cs | 52 ------------------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - 2 files changed, 53 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/Logging/AnsiDetector.cs diff --git a/src/MSBuildTaskHost/Framework/Logging/AnsiDetector.cs b/src/MSBuildTaskHost/Framework/Logging/AnsiDetector.cs deleted file mode 100644 index 6c2b09bd7a0..00000000000 --- a/src/MSBuildTaskHost/Framework/Logging/AnsiDetector.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// Portions of the code in this file were ported from the spectre.console by Patrik Svensson, Phil Scott, Nils Andresen -// https://github.com/spectreconsole/spectre.console/blob/main/src/Spectre.Console/Internal/Backends/Ansi/AnsiDetector.cs -// and from the supports-ansi project by Qingrong Ke -// https://github.com/keqingrong/supports-ansi/blob/master/index.js - -using System.Linq; -using System.Text.RegularExpressions; - -namespace Microsoft.Build.Framework.Logging -{ - internal class AnsiDetector - { - private static readonly Regex[] terminalsRegexes = - { - new("^xterm"), // xterm, PuTTY, Mintty - new("^rxvt"), // RXVT - new("^(?!eterm-color).*eterm.*"), // Accepts eterm, but not eterm-color, which does not support moving the cursor, see #9950. - new("^screen"), // GNU screen, tmux - new("tmux"), // tmux - new("^vt100"), // DEC VT series - new("^vt102"), // DEC VT series - new("^vt220"), // DEC VT series - new("^vt320"), // DEC VT series - new("ansi"), // ANSI - new("scoansi"), // SCO ANSI - new("cygwin"), // Cygwin, MinGW - new("linux"), // Linux console - new("konsole"), // Konsole - new("bvterm"), // Bitvise SSH Client - new("^st-256color"), // Suckless Simple Terminal, st - new("alacritty"), // Alacritty - }; - - internal static bool IsAnsiSupported(string termType) - { - if (string.IsNullOrEmpty(termType)) - { - return false; - } - - if (terminalsRegexes.Any(regex => regex.IsMatch(termType))) - { - return true; - } - - return false; - } - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 494a2115522..ec06980902a 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -67,7 +67,6 @@ - From 8fd687b13a73c5c6d2ea98892b82ea80e22b6f48 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 13:27:40 -0800 Subject: [PATCH 067/136] MSBuildTaskHost: Remove uncalled members from TranslationHelpers --- src/MSBuildTaskHost/TranslatorHelpers.cs | 278 ----------------------- 1 file changed, 278 deletions(-) diff --git a/src/MSBuildTaskHost/TranslatorHelpers.cs b/src/MSBuildTaskHost/TranslatorHelpers.cs index 1892a45b484..17c78804c62 100644 --- a/src/MSBuildTaskHost/TranslatorHelpers.cs +++ b/src/MSBuildTaskHost/TranslatorHelpers.cs @@ -55,31 +55,6 @@ static void TranslateUsingValueFactory(ITranslator translator, NodePacketValueFa return TranslateUsingValueFactory; } - public static void Translate( - this ITranslator translator, - ref List list, - NodePacketValueFactory valueFactory) where T : class, ITranslatable - { - translator.Translate(ref list, AdaptFactory(valueFactory), valueFactory); - } - - public static void Translate( - this ITranslator translator, - ref IList list, - NodePacketValueFactory valueFactory, - NodePacketCollectionCreator collectionFactory) where L : IList where T : ITranslatable - { - translator.Translate(ref list, AdaptFactory(valueFactory), valueFactory, collectionFactory); - } - - public static void TranslateArray( - this ITranslator translator, - ref T[] array, - NodePacketValueFactory valueFactory) where T : class, ITranslatable - { - translator.TranslateArray(ref array, AdaptFactory(valueFactory), valueFactory); - } - public static void TranslateDictionary( this ITranslator translator, ref Dictionary dictionary, @@ -88,258 +63,5 @@ public static void TranslateDictionary( { translator.TranslateDictionary(ref dictionary, comparer, AdaptFactory(valueFactory), valueFactory); } - - public static void InternDictionary( - this ITranslator translator, - ref Dictionary dictionary, - IEqualityComparer comparer) - { - IDictionary localDict = dictionary; - translator.TranslateDictionary( - ref localDict, - (ITranslator translator, ref string key) => translator.Intern(ref key), - (ITranslator translator, ref string val) => translator.Intern(ref val), - capacity => new Dictionary(capacity, comparer)); - dictionary = (Dictionary)localDict; - } - - public static void InternDictionary( - this ITranslator translator, - ref Dictionary dictionary, - IEqualityComparer stringComparer, - NodePacketValueFactory valueFactory) - where T : ITranslatable - { - IDictionary localDict = dictionary; - translator.TranslateDictionary( - ref localDict, - (ITranslator translator, ref string key) => translator.Intern(ref key), - AdaptFactory(valueFactory), - valueFactory, - capacity => new Dictionary(capacity, stringComparer)); - dictionary = (Dictionary)localDict; - } - - public static void InternPathDictionary( - this ITranslator translator, - ref Dictionary dictionary, - IEqualityComparer comparer) - { - IDictionary localDict = dictionary; - - // For now, assume only the value contains path-like strings (e.g. TaskItem metadata). - translator.TranslateDictionary( - ref localDict, - (ITranslator translator, ref string key) => translator.Intern(ref key), - (ITranslator translator, ref string val) => translator.InternPath(ref val), - capacity => new Dictionary(capacity, comparer)); - dictionary = (Dictionary)localDict; - } - - public static void InternPathDictionary( - this ITranslator translator, - ref Dictionary dictionary, - IEqualityComparer stringComparer, - NodePacketValueFactory valueFactory) - where T : ITranslatable - { - IDictionary localDict = dictionary; - translator.TranslateDictionary( - ref localDict, - (ITranslator translator, ref string key) => translator.InternPath(ref key), - AdaptFactory(valueFactory), - valueFactory, - capacity => new Dictionary(capacity, stringComparer)); - dictionary = (Dictionary)localDict; - } - - public static void TranslateDictionary( - this ITranslator translator, - ref D dictionary, - NodePacketValueFactory valueFactory) - where D : IDictionary, new() - where T : class, ITranslatable - { - translator.TranslateDictionary(ref dictionary, AdaptFactory(valueFactory), valueFactory); - } - - public static void TranslateDictionary( - this ITranslator translator, - ref D dictionary, - NodePacketValueFactory valueFactory, - NodePacketCollectionCreator collectionCreator) - where D : IDictionary - where T : class, ITranslatable - { - translator.TranslateDictionary(ref dictionary, AdaptFactory(valueFactory), valueFactory, collectionCreator); - } - - public static void TranslateHashSet( - this ITranslator translator, - ref HashSet hashSet, - NodePacketValueFactory valueFactory, - NodePacketCollectionCreator> collectionFactory) where T : class, ITranslatable - { - if (!translator.TranslateNullable(hashSet)) - { - return; - } - - int count = default; - if (translator.Mode == TranslationDirection.WriteToStream) - { - count = hashSet.Count; - } - translator.Translate(ref count); - - if (translator.Mode == TranslationDirection.ReadFromStream) - { - hashSet = collectionFactory(count); - for (int i = 0; i < count; i++) - { - T value = default; - translator.Translate(ref value, valueFactory); - hashSet.Add(value); - } - } - - if (translator.Mode == TranslationDirection.WriteToStream) - { - foreach (T item in hashSet) - { - T value = item; - translator.Translate(ref value, valueFactory); - } - } - } - - public static void Translate(this ITranslator translator, ref CultureInfo cultureInfo) - { - if (!translator.TranslateNullable(cultureInfo)) - { - return; - } - - int lcid = default; - - if (translator.Mode == TranslationDirection.WriteToStream) - { - lcid = cultureInfo.LCID; - } - - translator.Translate(ref lcid); - - if (translator.Mode == TranslationDirection.ReadFromStream) - { - cultureInfo = new CultureInfo(lcid); - } - } - - public static void Translate(this ITranslator translator, ref Version version) - { - if (!translator.TranslateNullable(version)) - { - return; - } - - int major = 0; - int minor = 0; - int build = 0; - int revision = 0; - - if (translator.Mode == TranslationDirection.WriteToStream) - { - major = version.Major; - minor = version.Minor; - build = version.Build; - revision = version.Revision; - } - - translator.Translate(ref major); - translator.Translate(ref minor); - translator.Translate(ref build); - translator.Translate(ref revision); - - if (translator.Mode == TranslationDirection.ReadFromStream) - { - if (build < 0) - { - version = new Version(major, minor); - } - else if (revision < 0) - { - version = new Version(major, minor, build); - } - else - { - version = new Version(major, minor, build, revision); - } - } - } - - public static void Translate(this ITranslator translator, ref AssemblyName assemblyName) - { - if (!translator.TranslateNullable(assemblyName)) - { - return; - } - - string name = null; - Version version = null; - AssemblyNameFlags flags = default; - ProcessorArchitecture processorArchitecture = default; - CultureInfo cultureInfo = null; - AssemblyHashAlgorithm hashAlgorithm = default; - AssemblyVersionCompatibility versionCompatibility = default; - string codeBase = null; - - byte[] publicKey = null; - byte[] publicKeyToken = null; - - if (translator.Mode == TranslationDirection.WriteToStream) - { - name = assemblyName.Name; - version = assemblyName.Version; - flags = assemblyName.Flags; - processorArchitecture = assemblyName.ProcessorArchitecture; - cultureInfo = assemblyName.CultureInfo; - hashAlgorithm = assemblyName.HashAlgorithm; - versionCompatibility = assemblyName.VersionCompatibility; - codeBase = assemblyName.CodeBase; - - publicKey = assemblyName.GetPublicKey(); // TODO: no need to serialize, public key is not used anywhere in context of RAR, only public key token - publicKeyToken = assemblyName.GetPublicKeyToken(); - } - - translator.Translate(ref name); - translator.Translate(ref version); - translator.TranslateEnum(ref flags, (int)flags); - translator.TranslateEnum(ref processorArchitecture, (int)processorArchitecture); - translator.Translate(ref cultureInfo); - translator.TranslateEnum(ref hashAlgorithm, (int)hashAlgorithm); - translator.TranslateEnum(ref versionCompatibility, (int)versionCompatibility); - translator.Translate(ref codeBase); - - translator.Translate(ref publicKey); - translator.Translate(ref publicKeyToken); - - if (translator.Mode == TranslationDirection.ReadFromStream) - { - assemblyName = new AssemblyName - { - Name = name, - Version = version, - Flags = flags, - ProcessorArchitecture = processorArchitecture, - CultureInfo = cultureInfo, - HashAlgorithm = hashAlgorithm, - VersionCompatibility = versionCompatibility, - CodeBase = codeBase, - }; - - assemblyName.SetPublicKey(publicKey); - assemblyName.SetPublicKeyToken(publicKeyToken); - } - } } } From 6bd8400c72396552f2304ea5ac8834190f8a978f Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 13:35:53 -0800 Subject: [PATCH 068/136] MSBuildTaskHost: Remove BinaryTranslator interning support MSBuildTaskHost never uses the interning functionality of BinaryTranslator, so that can be removed. --- .../Framework/BinaryTranslator.cs | 183 ------------------ src/MSBuildTaskHost/Framework/ITranslator.cs | 54 ------ .../Framework/InternPathIds.cs | 7 - .../Framework/InterningReadTranslator.cs | 79 -------- .../Framework/InterningWriteTranslator.cs | 157 --------------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 3 - 6 files changed, 483 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/InternPathIds.cs delete mode 100644 src/MSBuildTaskHost/Framework/InterningReadTranslator.cs delete mode 100644 src/MSBuildTaskHost/Framework/InterningWriteTranslator.cs diff --git a/src/MSBuildTaskHost/Framework/BinaryTranslator.cs b/src/MSBuildTaskHost/Framework/BinaryTranslator.cs index 326dd7ccd77..1bfb26278f3 100644 --- a/src/MSBuildTaskHost/Framework/BinaryTranslator.cs +++ b/src/MSBuildTaskHost/Framework/BinaryTranslator.cs @@ -55,21 +55,11 @@ internal static ITranslator GetWriteTranslator(Stream stream) /// private class BinaryReadTranslator : ITranslator { - /// - /// The intern reader used in an intern scope. - /// - private InterningReadTranslator _interner; - /// /// The binary reader used in read mode. /// private BinaryReader _reader; - /// - /// Whether the caller has entered an intern scope. - /// - private bool _isInterning; - #nullable enable /// /// Constructs a serializer from the specified stream, operating in the designated mode. @@ -823,81 +813,6 @@ public bool TranslateNullable(T value) bool haveRef = _reader.ReadBoolean(); return haveRef; } - - public void WithInterning(IEqualityComparer comparer, int initialCapacity, Action internBlock) - { - if (_isInterning) - { - throw new InvalidOperationException("Cannot enter recursive intern block."); - } - - _isInterning = true; - - // Deserialize the intern header before entering the intern scope. - _interner ??= new InterningReadTranslator(this); - _interner.Translate(this); - - // No other setup is needed since we can parse the packet directly from the stream. - internBlock(this); - - _isInterning = false; - } - - public void Intern(ref string str, bool nullable = true) - { - if (!_isInterning) - { - Translate(ref str); - return; - } - - if (nullable && !TranslateNullable(string.Empty)) - { - str = null; - return; - } - - str = _interner.Read(); - } - - public void Intern(ref string[] array) - { - if (!_isInterning) - { - Translate(ref array); - return; - } - - if (!TranslateNullable(array)) - { - return; - } - - int count = _reader.ReadInt32(); - array = new string[count]; - - for (int i = 0; i < count; i++) - { - array[i] = _interner.Read(); - } - } - - public void InternPath(ref string str, bool nullable = true) - { - if (!_isInterning) - { - Translate(ref str); - return; - } - - if (nullable && !TranslateNullable(string.Empty)) - { - str = null; - return; - } - - str = _interner.ReadPath(); - } } /// @@ -910,18 +825,6 @@ private class BinaryWriteTranslator : ITranslator /// private BinaryWriter _writer; - /// - /// The intern writer used in an intern scope. - /// This must be lazily instantiated since the interner has its own internal write translator, and - /// would otherwise go into a recursive loop on initalization. - /// - private InterningWriteTranslator _interner; - - /// - /// Whether the caller has entered an intern scope. - /// - private bool _isInterning; - /// /// Constructs a serializer from the specified stream, operating in the designated mode. /// @@ -1665,92 +1568,6 @@ public bool TranslateNullable(T value) _writer.Write(haveRef); return haveRef; } - - public void WithInterning(IEqualityComparer comparer, int initialCapacity, Action internBlock) - { - if (_isInterning) - { - throw new InvalidOperationException("Cannot enter recursive intern block."); - } - - // Every new scope requires the interner's state to be reset. - _interner ??= new InterningWriteTranslator(); - _interner.Setup(comparer, initialCapacity); - - // Temporaily swap our writer with the interner. - // This forwards all writes to this translator into the interning buffer, so that any non-interned - // writes which are interleaved will be in the correct order. - BinaryWriter streamWriter = _writer; - _writer = _interner.Writer; - _isInterning = true; - - try - { - internBlock(this); - } - finally - { - _writer = streamWriter; - _isInterning = false; - } - - // Write the interned buffer into the real output stream. - _interner.Translate(this); - } - - public void Intern(ref string str, bool nullable = true) - { - if (!_isInterning) - { - Translate(ref str); - return; - } - - if (nullable && !TranslateNullable(str)) - { - return; - } - - _interner.Intern(str); - } - - public void Intern(ref string[] array) - { - if (!_isInterning) - { - Translate(ref array); - return; - } - - if (!TranslateNullable(array)) - { - return; - } - - int count = array.Length; - Translate(ref count); - - for (int i = 0; i < count; i++) - { - _interner.Intern(array[i]); - } - } - - public void InternPath(ref string str, bool nullable = true) - { - if (!_isInterning) - { - Translate(ref str); - return; - } - - if (nullable && !TranslateNullable(str)) - { - return; - } - - _interner.InternPath(str); - } } } } diff --git a/src/MSBuildTaskHost/Framework/ITranslator.cs b/src/MSBuildTaskHost/Framework/ITranslator.cs index 2172a34693d..cf1aba0a829 100644 --- a/src/MSBuildTaskHost/Framework/ITranslator.cs +++ b/src/MSBuildTaskHost/Framework/ITranslator.cs @@ -400,59 +400,5 @@ void TranslateDictionary(ref D dictionary, ObjectTranslatorWithValueFactor /// The type of object to test. /// True if the object should be written, false otherwise. bool TranslateNullable(T value); - - /// - /// Creates a scope which activates string interning / deduplication for any Intern_xx method. - /// This should generally be called from the root level packet. - /// - /// The string comparer to use when populating the intern cache. - /// The initial capacity of the intern cache. - /// A delegate providing a translator, in which all Intern_xx calls will go through the intern cache. - /// - /// Packet interning is implemented via a header with an array of all interned strings, followed by the body in - /// which any interned / duplicated strings are replaced by their ID. - /// modes have different ordering requirements, so it would not be - /// possible to implement direction-agnostic serialization via the Intern_xx methods alone: - /// - Write: Because we don't know the full list of strings ahead of time, we need to create a temporary buffer - /// for the packet body, which we can later offset when flushing to the real stream. - /// - Read: The intern header needs to be deserialized before the packet body, otherwise we won't know what - /// string each ID maps to. - /// This method abstracts these requirements to the caller, such that the underlying translator will - /// automatically handle the appropriate IO ordering when entering / exiting the delegate scope. - /// - void WithInterning(IEqualityComparer comparer, int initialCapacity, Action internBlock); - - /// - /// Interns the string if the translator is currently within an intern block. - /// Otherwise, this forwards to the regular Translate method. - /// - /// The value to be translated. - /// - /// Whether to null check and translate the nullable marker. - /// Setting this to false can reduce packet sizes when interning large numbers of strings - /// which are validated to always be non-null, such as dictionary keys. - /// - void Intern(ref string str, bool nullable = true); - - /// - /// Interns each string in the array if the translator is currently within an intern block. - /// Otherwise, this forwards to the regular Translate method. To match behavior, all strings - /// assumed to be non-null. - /// - /// The array to be translated. - void Intern(ref string[] array); - - /// - /// Interns the string if the translator is currently within an intern block. - /// Otherwise, this forwards to the regular Translate method. - /// If the string is determined to be path-like, the path components will be interned separately. - /// - /// The value to be translated. - /// - /// Whether to null check and translate the nullable marker. - /// Setting this to false can reduce packet sizes when interning large numbers of strings - /// which are validated to always be non-null, such as dictionary keys. - /// - void InternPath(ref string str, bool nullable = true); } } diff --git a/src/MSBuildTaskHost/Framework/InternPathIds.cs b/src/MSBuildTaskHost/Framework/InternPathIds.cs deleted file mode 100644 index 029f31f95ed..00000000000 --- a/src/MSBuildTaskHost/Framework/InternPathIds.cs +++ /dev/null @@ -1,7 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.Build.BackEnd -{ - internal readonly record struct InternPathIds(int DirectoryId, int FileNameId); -} diff --git a/src/MSBuildTaskHost/Framework/InterningReadTranslator.cs b/src/MSBuildTaskHost/Framework/InterningReadTranslator.cs deleted file mode 100644 index 8536e9c6430..00000000000 --- a/src/MSBuildTaskHost/Framework/InterningReadTranslator.cs +++ /dev/null @@ -1,79 +0,0 @@ -// 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; - -namespace Microsoft.Build.BackEnd -{ - /// - /// Reads strings form a translator which contains interned packets. - /// - /// - /// This maintains a reusable lookup table to deserialize packets interned by . - /// On Translate, the intern header (aka the array of strings indexed by ID) is deserialized. - /// The caller can then forward reads to deserialize any interned values in the packet body. - /// - internal sealed class InterningReadTranslator : ITranslatable - { - private readonly ITranslator _translator; - - private List _strings = []; - - private Dictionary _pathIdsToString = []; - - internal InterningReadTranslator(ITranslator translator) - { - if (translator.Mode != TranslationDirection.ReadFromStream) - { - throw new InvalidOperationException( - $"{nameof(InterningReadTranslator)} can only be used with {nameof(TranslationDirection.ReadFromStream)}."); - } - - _translator = translator; - } - - internal string? Read() - { - int key = -1; - _translator.Translate(ref key); - return _strings[key]; - } - - internal string? ReadPath() - { - // If the writer set a null marker, read this as a single string. - if (!_translator.TranslateNullable(string.Empty)) - { - return Read(); - } - - int directoryKey = -1; - int fileNameKey = -1; - _translator.Translate(ref directoryKey); - _translator.Translate(ref fileNameKey); - - InternPathIds pathIds = new(directoryKey, fileNameKey); - - // Only concatenate paths the first time we encounter a pair. - if (_pathIdsToString.TryGetValue(pathIds, out string? path)) - { - return path; - } - - string directory = _strings[pathIds.DirectoryId]; - string fileName = _strings[pathIds.FileNameId]; - string str = string.Concat(directory, fileName); - _pathIdsToString.Add(pathIds, str); - - return str; - } - - public void Translate(ITranslator translator) - { - // Only deserialize the intern header since the caller will be reading directly from the stream. - _translator.Translate(ref _strings); - _pathIdsToString = new(_strings.Count); - } - } -} diff --git a/src/MSBuildTaskHost/Framework/InterningWriteTranslator.cs b/src/MSBuildTaskHost/Framework/InterningWriteTranslator.cs deleted file mode 100644 index f8daf6306c8..00000000000 --- a/src/MSBuildTaskHost/Framework/InterningWriteTranslator.cs +++ /dev/null @@ -1,157 +0,0 @@ -// 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; -using System.IO; - -namespace Microsoft.Build.BackEnd -{ - /// - /// Writes strings into a translator with interning / deduplication. - /// - /// - /// This maintains a reusable temporary buffer and lookup table for deduplicating strings within a translatable packet. - /// All unique strings (as determined by the comparer) will be assigned an incrementing ID and stored into a dictionary. - /// This ID will be written to a private buffer in place of the string and any repeat occurrences. - /// When serialized into another translator, the interner will: - /// 1. Serialize the list of unique strings to an array, where the ID is the index. - /// 2. Serialize the temporary buffer (aka the packet body) with all interned strings replaced by their ID. - /// This ordering is important since the reader will need the string lookup table before parsing the body. - /// As such, two rules need to be followed when using this class: - /// 1. Any interleaved non-interned writes should be written using the exposed BinaryWriter to keep the overall - /// packet in sync. - /// 2. Translate should *only* be called after all internable writes have been processed. - /// - internal sealed class InterningWriteTranslator : ITranslatable - { - private static readonly char[] DirectorySeparatorChars = [Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar]; - - private static readonly string IsPathMarker = string.Empty; - - private static readonly string? NotPathMarker = null; - - private readonly ITranslator _translator; - - private readonly MemoryStream _packetStream; - - private List _strings = []; - - private Dictionary _stringToIds = []; - - private Dictionary _stringToPathIds = []; - - internal InterningWriteTranslator() - { - _packetStream = new MemoryStream(); - _translator = BinaryTranslator.GetWriteTranslator(_packetStream); - - // Avoid directly exposing the buffered translator - any accidental Intern_xx method calls could go into a - // recursive loop. - Writer = _translator.Writer; - } - - /// - /// The writer for the underlying buffer. - /// Use to forward any non-interning writes into this translator. - /// - internal BinaryWriter Writer { get; } - - /// - /// Setup the intern cache and underlying buffer. This allows the interner to be reused. - /// - /// The string comparer to use for string deduplication. - /// An estimate of the number of unique strings to be interned. - internal void Setup(IEqualityComparer comparer, int initialCapacity) - { - // If the interner is in a reused translator, the comparer might not match between packets. - // Just throw away the old collections in this case. - _strings.Clear(); - _strings.Capacity = initialCapacity; - _stringToIds = new Dictionary(initialCapacity, comparer); - _stringToPathIds = new Dictionary(initialCapacity, comparer); - - _packetStream.Position = 0; - _packetStream.SetLength(0); - - // This is a rough estimate since the final size will depend on the length of each string and the total number - // of intern cache hits. Assume a mixture of short strings (e.g. item metadata pairs, RAR assembly metadata) - // and file paths (e.g. item include paths, RAR statefile entries). - const int CharactersPerString = 32; - const int BytesPerCharacter = 2; - const int BytesPerInternedString = 5; - int internHeaderSize = initialCapacity * CharactersPerString * BytesPerCharacter; - int packetPayloadSize = initialCapacity * BytesPerInternedString; - _packetStream.Capacity = internHeaderSize + packetPayloadSize; - } - - internal void Intern(string str) => _ = InternString(str); - - private int InternString(string str) - { - if (!_stringToIds.TryGetValue(str, out int index)) - { - index = _strings.Count; - _stringToIds.Add(str, index); - _strings.Add(str); - } - - _translator.Translate(ref index); - return index; - } - - internal void InternPath(string str) - { - // If we've seen a string already and know it's path-like, we just need the index pair. - if (_stringToPathIds.TryGetValue(str, out InternPathIds pathIds)) - { - _ = _translator.TranslateNullable(IsPathMarker); - int directoryId = pathIds.DirectoryId; - int fileNameId = pathIds.FileNameId; - _translator.Translate(ref directoryId); - _translator.Translate(ref fileNameId); - return; - } - - // Quick and basic heuristic to check if we have a path-like string. - int splitId = str.LastIndexOfAny(DirectorySeparatorChars); - bool hasDirectorySeparator = splitId > -1 - && splitId < str.Length - 1 - && str.IndexOf('%') == -1; - - if (!hasDirectorySeparator) - { - // Set a marker to signal the reader to parse this as a single string. - _ = _translator.TranslateNullable(NotPathMarker); - _ = InternString(str); - return; - } - - string directory = str.Substring(0, splitId + 1); - string fileName = str.Substring(splitId + 1); - - _ = _translator.TranslateNullable(IsPathMarker); - int directoryIndex = InternString(directory); - int fileNameIndex = InternString(fileName); - - _stringToPathIds.Add(str, new InternPathIds(directoryIndex, fileNameIndex)); - } - - public void Translate(ITranslator translator) - { - if (translator.Mode != TranslationDirection.WriteToStream) - { - throw new InvalidOperationException( - $"{nameof(InterningWriteTranslator)} can only be used with {nameof(TranslationDirection.WriteToStream)}."); - } - - // Write the set of unique strings as the packet header. - translator.Translate(ref _strings); - - // Write the temporary buffer as the packet body. - byte[] buffer = _packetStream.GetBuffer(); - int bufferSize = (int)_packetStream.Length; - translator.Writer.Write(buffer, 0, bufferSize); - } - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index ec06980902a..2d9846fbf59 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -62,9 +62,6 @@ - - - From 407b75d720fdeccf5f445634951fb97c9f92198e Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 13:53:48 -0800 Subject: [PATCH 069/136] MSBuildTaskHost: Remove uncalled members from ITranslater --- .../Framework/BinaryTranslator.cs | 802 +----------------- src/MSBuildTaskHost/Framework/ITranslator.cs | 147 ---- 2 files changed, 2 insertions(+), 947 deletions(-) diff --git a/src/MSBuildTaskHost/Framework/BinaryTranslator.cs b/src/MSBuildTaskHost/Framework/BinaryTranslator.cs index 1bfb26278f3..658f08a656c 100644 --- a/src/MSBuildTaskHost/Framework/BinaryTranslator.cs +++ b/src/MSBuildTaskHost/Framework/BinaryTranslator.cs @@ -21,14 +21,6 @@ namespace Microsoft.Build.BackEnd /// internal static class BinaryTranslator { - /// - /// Presence of this key in the dictionary indicates that it was null. - /// - /// - /// This constant is needed for a workaround concerning serializing BuildResult with a version. - /// - private const string SpecialKeyForDictionaryBeingNull = "=MSBUILDDICTIONARYWASNULL="; - #nullable enable /// /// Returns a read-only serializer. @@ -176,40 +168,6 @@ public void Translate(ref int value) value = _reader.ReadInt32(); } - /// - public void Translate(ref uint unsignedInteger) => unsignedInteger = _reader.ReadUInt32(); - - /// - /// Translates a TaskHostParameters. - /// - /// The TaskHostParameters to be translated. - public void Translate(ref TaskHostParameters value) - { - string runtime = null; - string architecture = null; - string dotnetHostPath = null; - string msBuildAssemblyPath = null; - bool? isTaskHostFactory = null; - - Translate(ref runtime); - Translate(ref architecture); - Translate(ref dotnetHostPath); - Translate(ref msBuildAssemblyPath); - - bool hasTaskHostFactory = _reader.ReadBoolean(); - if (hasTaskHostFactory) - { - isTaskHostFactory = _reader.ReadBoolean(); - } - - value = new TaskHostParameters( - runtime: runtime, - architecture: architecture, - dotnetHostPath: dotnetHostPath, - msBuildAssemblyPath: msBuildAssemblyPath, - taskHostFactoryExplicitlyRequested: isTaskHostFactory); - } - /// /// Translates an array. /// @@ -286,17 +244,6 @@ public void Translate(ref byte[] byteArray) } } - /// - /// Translates a byte array - /// - /// The array to be translated. - /// The length of array which will be used in translation. This parameter is not used when reading - public void Translate(ref byte[] byteArray, ref int length) - { - Translate(ref byteArray); - length = byteArray.Length; - } - /// /// Translates a string array. /// @@ -317,102 +264,6 @@ public void Translate(ref string[] array) } } - /// - public void Translate(ref HashSet set) - { - if (!TranslateNullable(set)) - { - return; - } - - int count = _reader.ReadInt32(); - set = new HashSet(); - - for (int i = 0; i < count; i++) - { - set.Add(_reader.ReadString()); - } - } - - /// - /// Translates a list of strings - /// - /// The list to be translated. - public void Translate(ref List list) - { - if (!TranslateNullable(list)) - { - return; - } - - int count = _reader.ReadInt32(); - list = new List(count); - - for (int i = 0; i < count; i++) - { - list.Add(_reader.ReadString()); - } - } - - /// - /// Translates a list of T using an - /// - /// The list to be translated. - /// The translator to use for the items in the list - /// TaskItem type - public void Translate(ref List list, ObjectTranslator objectTranslator) - { - IList listAsInterface = list; - Translate(ref listAsInterface, objectTranslator, count => new List(count)); - list = (List)listAsInterface; - } - - /// - public void Translate(ref List list, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) - { - IList listAsInterface = list; - Translate(ref listAsInterface, objectTranslator, valueFactory, count => new List(count)); - list = (List)listAsInterface; - } - - public void Translate(ref IList list, ObjectTranslator objectTranslator, NodePacketCollectionCreator collectionFactory) where L : IList - { - if (!TranslateNullable(list)) - { - return; - } - - int count = _reader.ReadInt32(); - list = collectionFactory(count); - - for (int i = 0; i < count; i++) - { - T value = default(T); - - objectTranslator(this, ref value); - list.Add(value); - } - } - - public void Translate(ref IList list, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory, NodePacketCollectionCreator collectionFactory) where L : IList - { - if (!TranslateNullable(list)) - { - return; - } - - int count = _reader.ReadInt32(); - list = collectionFactory(count); - - for (int i = 0; i < count; i++) - { - T value = default(T); - - objectTranslator(this, valueFactory, ref value); - list.Add(value); - } - } - /// /// Translates a collection of T into the specified type using an and /// @@ -450,17 +301,6 @@ public void Translate(ref DateTime value) value = new DateTime(_reader.ReadInt64(), kind); } - /// - /// Translates a TimeSpan. - /// - /// The value to be translated. - public void Translate(ref TimeSpan value) - { - long ticks = 0; - Translate(ref ticks); - value = new System.TimeSpan(ticks); - } - /// /// Translates a CultureInfo /// @@ -526,91 +366,12 @@ public void TranslateException(ref Exception value) } - /// - /// Translates an object implementing INodePacketTranslatable. - /// - /// The reference type. - /// The value to be translated. - public void Translate(ref T value) - where T : ITranslatable, new() - { - if (!TranslateNullable(value)) - { - return; - } - - value = new T(); - value.Translate(this); - } - - /// - /// Translates an array of objects implementing INodePacketTranslatable. - /// - /// The reference type. - /// The array to be translated. - public void TranslateArray(ref T[] array) - where T : ITranslatable, new() - { - if (!TranslateNullable(array)) - { - return; - } - - int count = _reader.ReadInt32(); - array = new T[count]; - - for (int i = 0; i < count; i++) - { - array[i] = new T(); - array[i].Translate(this); - } - } - - /// - public void TranslateArray(ref T[] array, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) - { - if (!TranslateNullable(array)) - { - return; - } - - int count = _reader.ReadInt32(); - array = new T[count]; - - for (int i = 0; i < count; i++) - { - objectTranslator(this, valueFactory, ref array[i]); - } - } - /// /// Translates a dictionary of { string, string }. /// /// The dictionary to be translated. /// The comparer used to instantiate the dictionary. public void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer) - { - IDictionary copy = dictionary; - - TranslateDictionary( - ref copy, - count => new Dictionary(count, comparer)); - - dictionary = (Dictionary)copy; - } - - /// - /// Translates a dictionary of { string, string } with additional entries. The dictionary might be null despite being populated. - /// - /// The dictionary to be translated. - /// The comparer used to instantiate the dictionary. - /// Additional entries to be translated - /// Additional entries keys - /// - /// This overload is needed for a workaround concerning serializing BuildResult with a version. - /// It deserializes additional entries together with the main dictionary. - /// - public void TranslateDictionary(ref IDictionary dictionary, IEqualityComparer comparer, ref Dictionary additionalEntries, HashSet additionalEntriesKeys) { if (!TranslateNullable(dictionary)) { @@ -619,42 +380,6 @@ public void TranslateDictionary(ref IDictionary dictionary, IEqu int count = _reader.ReadInt32(); dictionary = new Dictionary(count, comparer); - additionalEntries = new(); - - for (int i = 0; i < count; i++) - { - string key = null; - Translate(ref key); - string value = null; - Translate(ref value); - if (additionalEntriesKeys.Contains(key)) - { - additionalEntries[key] = value; - } - else if (comparer.Equals(key, SpecialKeyForDictionaryBeingNull)) - { - // Presence of special key SpecialKeyForDictionaryBeingNull indicates that the dictionary was null. - dictionary = null; - - // If the dictionary is null, we should have only two keys: SpecialKeyForDictionaryBeingNull, SpecialKeyForVersion - Debug.Assert(count == 2); - } - else if (dictionary is not null) - { - dictionary[key] = value; - } - } - } - - public void TranslateDictionary(ref IDictionary dictionary, NodePacketCollectionCreator> dictionaryCreator) - { - if (!TranslateNullable(dictionary)) - { - return; - } - - int count = _reader.ReadInt32(); - dictionary = dictionaryCreator(count); for (int i = 0; i < count; i++) { @@ -666,56 +391,6 @@ public void TranslateDictionary(ref IDictionary dictionary, Node } } - public void TranslateDictionary( - ref IDictionary dictionary, - ObjectTranslator keyTranslator, - ObjectTranslator valueTranslator, - NodePacketCollectionCreator> dictionaryCreator) - { - if (!TranslateNullable(dictionary)) - { - return; - } - - int count = _reader.ReadInt32(); - dictionary = dictionaryCreator(count); - - for (int i = 0; i < count; i++) - { - K key = default(K); - keyTranslator(this, ref key); - V value = default(V); - valueTranslator(this, ref value); - dictionary[key] = value; - } - } - - /// - public void TranslateDictionary( - ref IDictionary dictionary, - ObjectTranslator keyTranslator, - ObjectTranslatorWithValueFactory valueTranslator, - NodePacketValueFactory valueFactory, - NodePacketCollectionCreator> dictionaryCreator) - { - if (!TranslateNullable(dictionary)) - { - return; - } - - int count = _reader.ReadInt32(); - dictionary = dictionaryCreator(count); - - for (int i = 0; i < count; i++) - { - K key = default(K); - keyTranslator(this, ref key); - V value = default(V); - valueTranslator(this, valueFactory, ref value); - dictionary[key] = value; - } - } - /// public void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) where T : class @@ -738,71 +413,6 @@ public void TranslateDictionary(ref Dictionary dictionary, IEquali } } - /// - public void TranslateDictionary(ref D dictionary, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) - where D : IDictionary, new() - where T : class - { - if (!TranslateNullable(dictionary)) - { - return; - } - - int count = _reader.ReadInt32(); - dictionary = new D(); - - for (int i = 0; i < count; i++) - { - string key = null; - Translate(ref key); - T value = null; - objectTranslator(this, valueFactory, ref value); - dictionary[key] = value; - } - } - - /// - public void TranslateDictionary(ref D dictionary, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory, NodePacketCollectionCreator dictionaryCreator) - where D : IDictionary - where T : class - { - if (!TranslateNullable(dictionary)) - { - return; - } - - int count = _reader.ReadInt32(); - dictionary = dictionaryCreator(count); - - for (int i = 0; i < count; i++) - { - string key = null; - Translate(ref key); - T value = null; - objectTranslator(this, valueFactory, ref value); - dictionary[key] = value; - } - } - - public void TranslateDictionary(ref Dictionary dictionary, StringComparer comparer) - { - if (!TranslateNullable(dictionary)) - { - return; - } - - int count = _reader.ReadInt32(); - dictionary = new(count, comparer); - string key = string.Empty; - DateTime val = DateTime.MinValue; - for (int i = 0; i < count; i++) - { - Translate(ref key); - Translate(ref val); - dictionary.Add(key, val); - } - } - /// /// Reads in the boolean which says if this object is null or not. /// @@ -940,9 +550,6 @@ public void Translate(ref int value) _writer.Write(value); } - /// - public void Translate(ref uint unsignedInteger) => _writer.Write(unsignedInteger); - /// /// Translates an array. /// @@ -981,30 +588,6 @@ public void Translate(ref double value) _writer.Write(value); } - /// - /// Translates a TaskHostParameters. - /// - /// The TaskHostParameters to be translated. - public void Translate(ref TaskHostParameters value) - { - string runtime = value.Runtime; - string architecture = value.Architecture; - string dotnetHostPath = value.DotnetHostPath; - string msBuildAssemblyPath = value.MSBuildAssemblyPath; - - Translate(ref runtime); - Translate(ref architecture); - Translate(ref dotnetHostPath); - Translate(ref msBuildAssemblyPath); - - bool hasTaskHostFactory = value.TaskHostFactoryExplicitlyRequested.HasValue; - _writer.Write(hasTaskHostFactory); - if (hasTaskHostFactory) - { - _writer.Write(value.TaskHostFactoryExplicitlyRequested.Value); - } - } - /// /// Translates a string. /// @@ -1039,127 +622,6 @@ public void Translate(ref string[] array) } } - /// - /// Translates a list of strings - /// - /// The list to be translated. - public void Translate(ref List list) - { - if (!TranslateNullable(list)) - { - return; - } - - int count = list.Count; - _writer.Write(count); - - for (int i = 0; i < count; i++) - { - _writer.Write(list[i]); - } - } - - /// - public void Translate(ref HashSet set) - { - if (!TranslateNullable(set)) - { - return; - } - - int count = set.Count; - _writer.Write(count); - - foreach (var item in set) - { - _writer.Write(item); - } - } - - /// - /// Translates a list of T using an - /// - /// The list to be translated. - /// The translator to use for the items in the list - /// A TaskItemType - public void Translate(ref List list, ObjectTranslator objectTranslator) - { - if (!TranslateNullable(list)) - { - return; - } - - int count = list.Count; - _writer.Write(count); - - for (int i = 0; i < count; i++) - { - T value = list[i]; - objectTranslator(this, ref value); - } - } - - /// - public void Translate(ref List list, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) - { - if (!TranslateNullable(list)) - { - return; - } - - int count = list.Count; - _writer.Write(count); - - for (int i = 0; i < count; i++) - { - T value = list[i]; - objectTranslator(this, valueFactory, ref value); - } - } - - /// - /// Translates a list of T using an - /// - /// The list to be translated. - /// The translator to use for the items in the list - /// factory to create the IList - /// A TaskItemType - /// IList subtype - public void Translate(ref IList list, ObjectTranslator objectTranslator, NodePacketCollectionCreator collectionFactory) where L : IList - { - if (!TranslateNullable(list)) - { - return; - } - - int count = list.Count; - _writer.Write(count); - - for (int i = 0; i < count; i++) - { - T value = list[i]; - objectTranslator(this, ref value); - } - } - - /// - public void Translate(ref IList list, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory, NodePacketCollectionCreator collectionFactory) where L : IList - { - if (!TranslateNullable(list)) - { - return; - } - - int count = list.Count; - _writer.Write(count); - - for (int i = 0; i < count; i++) - { - T value = list[i]; - objectTranslator(this, valueFactory, ref value); - } - } - /// /// Translates a collection of T into the specified type using an and /// @@ -1195,15 +657,6 @@ public void Translate(ref DateTime value) _writer.Write(value.Ticks); } - /// - /// Translates a TimeSpan. - /// - /// The value to be translated. - public void Translate(ref TimeSpan value) - { - _writer.Write(value.Ticks); - } - /// /// Translates a CultureInfo /// @@ -1240,44 +693,19 @@ public void TranslateException(ref Exception value) BuildExceptionBase.WriteExceptionToTranslator(this, value); } - /// - /// Translates an object implementing INodePacketTranslatable. - /// - /// The reference type. - /// The value to be translated. - public void Translate(ref T value) - where T : ITranslatable, new() - { - if (!TranslateNullable(value)) - { - return; - } - - value.Translate(this); - } - /// /// Translates a byte array /// /// The byte array to be translated public void Translate(ref byte[] byteArray) - { - var length = byteArray?.Length ?? 0; - Translate(ref byteArray, ref length); - } - - /// - /// Translates a byte array - /// - /// The array to be translated. - /// The length of array which will be used in translation - public void Translate(ref byte[] byteArray, ref int length) { if (!TranslateNullable(byteArray)) { return; } + var length = byteArray?.Length ?? 0; + _writer.Write(length); if (length > 0) { @@ -1285,123 +713,12 @@ public void Translate(ref byte[] byteArray, ref int length) } } - /// - /// Translates an array of objects implementing INodePacketTranslatable. - /// - /// The reference type. - /// The array to be translated. - public void TranslateArray(ref T[] array) - where T : ITranslatable, new() - { - if (!TranslateNullable(array)) - { - return; - } - - int count = array.Length; - _writer.Write(count); - - for (int i = 0; i < count; i++) - { - array[i].Translate(this); - } - } - - /// - public void TranslateArray(ref T[] array, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) - { - if (!TranslateNullable(array)) - { - return; - } - - int count = array.Length; - _writer.Write(count); - - for (int i = 0; i < count; i++) - { - objectTranslator(this, valueFactory, ref array[i]); - } - } - /// /// Translates a dictionary of { string, string }. /// /// The dictionary to be translated. /// The comparer used to instantiate the dictionary. public void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer) - { - IDictionary copy = dictionary; - TranslateDictionary(ref copy, (NodePacketCollectionCreator>)null); - } - - /// - /// Translates a dictionary of { string, string } adding additional entries. - /// - /// The dictionary to be translated. - /// The comparer used to instantiate the dictionary. - /// Additional entries to be translated. - /// Additional entries keys. - /// - /// This overload is needed for a workaround concerning serializing BuildResult with a version. - /// It serializes additional entries together with the main dictionary. - /// - public void TranslateDictionary(ref IDictionary dictionary, IEqualityComparer comparer, ref Dictionary additionalEntries, HashSet additionalEntriesKeys) - { - // Translate whether object is null - if ((dictionary is null) && ((additionalEntries is null) || (additionalEntries.Count == 0))) - { - _writer.Write(false); - return; - } - else - { - // Translate that object is not null - _writer.Write(true); - } - - // Writing a dictionary, additional entries and special key if dictionary was null. We need the special key for distinguishing whether the initial dictionary was null or empty. - int count = (dictionary is null ? 1 : 0) + - (additionalEntries is null ? 0 : additionalEntries.Count) + - (dictionary is null ? 0 : dictionary.Count); - - _writer.Write(count); - - // If the dictionary was null, serialize a special key SpecialKeyForDictionaryBeingNull. - if (dictionary is null) - { - string key = SpecialKeyForDictionaryBeingNull; - Translate(ref key); - string value = string.Empty; - Translate(ref value); - } - - // Serialize additional entries - if (additionalEntries is not null) - { - foreach (KeyValuePair pair in additionalEntries) - { - string key = pair.Key; - Translate(ref key); - string value = pair.Value; - Translate(ref value); - } - } - - // Serialize dictionary - if (dictionary is not null) - { - foreach (KeyValuePair pair in dictionary) - { - string key = pair.Key; - Translate(ref key); - string value = pair.Value; - Translate(ref value); - } - } - } - - public void TranslateDictionary(ref IDictionary dictionary, NodePacketCollectionCreator> dictionaryCreator) { if (!TranslateNullable(dictionary)) { @@ -1420,54 +737,6 @@ public void TranslateDictionary(ref IDictionary dictionary, Node } } - public void TranslateDictionary( - ref IDictionary dictionary, - ObjectTranslator keyTranslator, - ObjectTranslator valueTranslator, - NodePacketCollectionCreator> collectionCreator) - { - if (!TranslateNullable(dictionary)) - { - return; - } - - int count = dictionary.Count; - _writer.Write(count); - - foreach (KeyValuePair pair in dictionary) - { - K key = pair.Key; - keyTranslator(this, ref key); - V value = pair.Value; - valueTranslator(this, ref value); - } - } - - /// - public void TranslateDictionary( - ref IDictionary dictionary, - ObjectTranslator keyTranslator, - ObjectTranslatorWithValueFactory valueTranslator, - NodePacketValueFactory valueFactory, - NodePacketCollectionCreator> collectionCreator) - { - if (!TranslateNullable(dictionary)) - { - return; - } - - int count = dictionary.Count; - _writer.Write(count); - - foreach (KeyValuePair pair in dictionary) - { - K key = pair.Key; - keyTranslator(this, ref key); - V value = pair.Value; - valueTranslator(this, valueFactory, ref value); - } - } - /// public void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) where T : class @@ -1489,73 +758,6 @@ public void TranslateDictionary(ref Dictionary dictionary, IEquali } } - /// - public void TranslateDictionary(ref D dictionary, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) - where D : IDictionary, new() - where T : class - { - if (!TranslateNullable(dictionary)) - { - return; - } - - int count = dictionary.Count; - _writer.Write(count); - - foreach (KeyValuePair pair in dictionary) - { - string key = pair.Key; - Translate(ref key); - T value = pair.Value; - objectTranslator(this, valueFactory, ref value); - } - } - - /// - public void TranslateDictionary(ref D dictionary, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory, NodePacketCollectionCreator dictionaryCreator) - where D : IDictionary - where T : class - { - if (!TranslateNullable(dictionary)) - { - return; - } - - int count = dictionary.Count; - _writer.Write(count); - - foreach (KeyValuePair pair in dictionary) - { - string key = pair.Key; - Translate(ref key); - T value = pair.Value; - objectTranslator(this, valueFactory, ref value); - } - } - - /// - /// Translates a dictionary of { string, DateTime }. - /// - /// The dictionary to be translated. - /// Key comparer - public void TranslateDictionary(ref Dictionary dictionary, StringComparer comparer) - { - if (!TranslateNullable(dictionary)) - { - return; - } - - int count = dictionary.Count; - _writer.Write(count); - foreach (KeyValuePair kvp in dictionary) - { - string key = kvp.Key; - DateTime val = kvp.Value; - Translate(ref key); - Translate(ref val); - } - } - /// /// Writes out the boolean which says if this object is null or not. /// diff --git a/src/MSBuildTaskHost/Framework/ITranslator.cs b/src/MSBuildTaskHost/Framework/ITranslator.cs index cf1aba0a829..21c7e755354 100644 --- a/src/MSBuildTaskHost/Framework/ITranslator.cs +++ b/src/MSBuildTaskHost/Framework/ITranslator.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Globalization; using System.IO; -using Microsoft.Build.Framework; #nullable disable @@ -155,12 +154,6 @@ BinaryWriter Writer /// The value to be translated. void Translate(ref int value); - /// - /// Translates an unsigned integer. - /// - /// The unsigned integer to translate. - void Translate(ref uint unsignedInteger); - /// /// Translates an array. /// @@ -191,56 +184,6 @@ BinaryWriter Writer /// The array to be translated. void Translate(ref string[] array); - /// - /// Translates a list of strings - /// - /// The list to be translated. - void Translate(ref List list); - - /// - /// Translates a set of strings - /// - /// The set to be translated. - void Translate(ref HashSet set); - - /// - /// Translates a list of T using an - /// - /// The list to be translated. - /// The translator to use for the items in the list - /// A TaskItemType - void Translate(ref List list, ObjectTranslator objectTranslator); - - /// - /// Translates a list of T using an - /// - /// The list to be translated. - /// The translator to use for the items in the list - /// The factory to use to create the value. - /// A TaskItemType - void Translate(ref List list, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory); - - /// - /// Translates a list of T using an anda collection factory - /// - /// The list to be translated. - /// The translator to use for the items in the list - /// An ITranslatable subtype - /// An IList subtype - /// factory to create a collection - void Translate(ref IList list, ObjectTranslator objectTranslator, NodePacketCollectionCreator collectionFactory) where L : IList; - - /// - /// Translates a list of T using an and a collection factory - /// - /// The list to be translated. - /// The translator to use for the items in the list - /// The factory to use to create the value. - /// An ITranslatable subtype - /// An IList subtype - /// factory to create a collection - void Translate(ref IList list, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory, NodePacketCollectionCreator collectionFactory) where L : IList; - /// /// Translates a collection of T into the specified type using an and /// @@ -257,12 +200,6 @@ BinaryWriter Writer /// The value to be translated. void Translate(ref DateTime value); - /// - /// Translates a TimeSpan. - /// - /// The value to be translated. - void Translate(ref TimeSpan value); - /// /// Translates an enumeration. /// @@ -279,14 +216,6 @@ void TranslateEnum(ref T value, int numericValue) void TranslateException(ref Exception value); - /// - /// Translates an object implementing INodePacketTranslatable. - /// - /// The reference type. - /// The value to be translated. - void Translate(ref T value) - where T : ITranslatable, new(); - /// /// Translates a culture /// @@ -299,30 +228,6 @@ void Translate(ref T value) /// The array to be translated. void Translate(ref byte[] byteArray); - /// - /// Translates a byte array - /// - /// The array to be translated. - /// The length of array which will be used in translation - void Translate(ref byte[] byteArray, ref int length); - - /// - /// Translates an array of objects implementing INodePacketTranslatable. - /// - /// The reference type. - /// The array to be translated. - void TranslateArray(ref T[] array) - where T : ITranslatable, new(); - - /// - /// Translates an array of objects using an . - /// - /// The reference type. - /// The array to be translated. - /// The translator to use for the elements in the array. - /// The factory to use to create the value. - void TranslateArray(ref T[] array, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory); - /// /// Translates a dictionary of { string, string }. /// @@ -330,33 +235,6 @@ void TranslateArray(ref T[] array) /// The comparer used to instantiate the dictionary. void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer); - /// - /// Translates a TaskHostParameters. - /// - /// The TaskHostParameters to translate. - void Translate(ref TaskHostParameters value); - - /// - /// Translates a dictionary of { string, string } adding additional entries. - /// - /// The dictionary to be translated. - /// The comparer used to instantiate the dictionary. - /// Additional entries to be translated - /// Additional entries keys - /// - /// This overload is needed for a workaround concerning serializing BuildResult with a version. - /// It serializes/deserializes additional entries together with the main dictionary. - /// - void TranslateDictionary(ref IDictionary dictionary, IEqualityComparer comparer, ref Dictionary additionalEntries, HashSet additionalEntriesKeys); - - void TranslateDictionary(ref IDictionary dictionary, NodePacketCollectionCreator> collectionCreator); - - void TranslateDictionary(ref Dictionary dictionary, StringComparer comparer); - - void TranslateDictionary(ref IDictionary dictionary, ObjectTranslator keyTranslator, ObjectTranslator valueTranslator, NodePacketCollectionCreator> dictionaryCreator); - - void TranslateDictionary(ref IDictionary dictionary, ObjectTranslator keyTranslator, ObjectTranslatorWithValueFactory valueTranslator, NodePacketValueFactory valueFactory, NodePacketCollectionCreator> dictionaryCreator); - /// /// Translates a dictionary of { string, T }. /// @@ -368,31 +246,6 @@ void TranslateArray(ref T[] array) void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) where T : class; - /// - /// Translates a dictionary of { string, T } for dictionaries with public parameterless constructors. - /// - /// The reference type for the dictionary. - /// The reference type for values in the dictionary. - /// The dictionary to be translated. - /// The translator to use for the values in the dictionary. - /// The factory to use to create the value. - void TranslateDictionary(ref D dictionary, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) - where D : IDictionary, new() - where T : class; - - /// - /// Translates a dictionary of { string, T } for dictionaries with public parameterless constructors. - /// - /// The reference type for the dictionary. - /// The reference type for values in the dictionary. - /// The dictionary to be translated. - /// The translator to use for the values in the dictionary - /// /// The factory to use to create the value. - /// A factory used to create the dictionary. - void TranslateDictionary(ref D dictionary, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory, NodePacketCollectionCreator collectionCreator) - where D : IDictionary - where T : class; - /// /// Translates the boolean that says whether this value is null or not /// From 65ff4fe1949d96ef963a5cf57bee66b207489cea Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 14:03:44 -0800 Subject: [PATCH 070/136] MSBuildTaskHost: Remove uncalled members from OutOfProcTaskHostNode --- .../MSBuild/OutOfProcTaskHostNode.cs | 61 +++---------------- 1 file changed, 10 insertions(+), 51 deletions(-) diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs index b85f181bf54..b9b9cab503d 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs @@ -6,14 +6,13 @@ using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Reflection; +using System.Runtime.Remoting; using System.Threading; using Microsoft.Build.BackEnd; using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.Internal; using Microsoft.Build.Shared; -using System.Runtime.Remoting; #nullable disable @@ -170,11 +169,9 @@ public OutOfProcTaskHostNode() _packetFactory = new NodePacketFactory(); - INodePacketFactory thisINodePacketFactory = (INodePacketFactory)this; - - thisINodePacketFactory.RegisterPacketHandler(NodePacketType.TaskHostConfiguration, TaskHostConfiguration.FactoryForDeserialization, this); - thisINodePacketFactory.RegisterPacketHandler(NodePacketType.TaskHostTaskCancelled, TaskHostTaskCancelled.FactoryForDeserialization, this); - thisINodePacketFactory.RegisterPacketHandler(NodePacketType.NodeBuildComplete, NodeBuildComplete.FactoryForDeserialization, this); + RegisterPacketHandler(NodePacketType.TaskHostConfiguration, TaskHostConfiguration.FactoryForDeserialization, this); + RegisterPacketHandler(NodePacketType.TaskHostTaskCancelled, TaskHostTaskCancelled.FactoryForDeserialization, this); + RegisterPacketHandler(NodePacketType.NodeBuildComplete, NodeBuildComplete.FactoryForDeserialization, this); } #region IBuildEngine Implementation (Properties) @@ -182,7 +179,7 @@ public OutOfProcTaskHostNode() /// /// Returns the value of ContinueOnError for the currently executing task. /// - public bool ContinueOnError + bool IBuildEngine.ContinueOnError { get { @@ -235,7 +232,7 @@ public string ProjectFileOfTaskNode /// Stub implementation of IBuildEngine2.IsRunningMultipleNodes. The task host does not support this sort of /// IBuildEngine callback, so error. /// - public bool IsRunningMultipleNodes + bool IBuildEngine2.IsRunningMultipleNodes { get { @@ -246,42 +243,6 @@ public bool IsRunningMultipleNodes #endregion // IBuildEngine2 Implementation (Properties) - #region IBuildEngine7 Implementation - /// - /// Enables or disables emitting a default error when a task fails without logging errors - /// - public bool AllowFailureWithoutError { get; set; } = false; - #endregion - - #region IBuildEngine8 Implementation - - /// - /// Contains all warnings that should be logged as errors. - /// Non-null empty set when all warnings should be treated as errors. - /// - private ICollection WarningsAsErrors { get; set; } - - private ICollection WarningsNotAsErrors { get; set; } - - private ICollection WarningsAsMessages { get; set; } - - public bool ShouldTreatWarningAsError(string warningCode) - { - // Warnings as messages overrides warnings as errors. - if (WarningsAsErrors == null || WarningsAsMessages?.Contains(warningCode) == true) - { - return false; - } - - return (WarningsAsErrors.Count == 0 && WarningAsErrorNotOverriden(warningCode)) || WarningsAsMessages.Contains(warningCode); - } - - private bool WarningAsErrorNotOverriden(string warningCode) - { - return WarningsNotAsErrors?.Contains(warningCode) != true; - } - #endregion - #region IBuildEngine Implementation (Methods) /// @@ -328,7 +289,7 @@ public void LogCustomEvent(CustomBuildEventArgs e) /// Stub implementation of IBuildEngine.BuildProjectFile. The task host does not support IBuildEngine /// callbacks for the purposes of building projects, so error. /// - public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs) + bool IBuildEngine.BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs) { LogErrorFromResource("BuildEngineCallbacksInTaskHostUnsupported"); return false; @@ -342,7 +303,7 @@ public bool BuildProjectFile(string projectFileName, string[] targetNames, IDict /// Stub implementation of IBuildEngine2.BuildProjectFile. The task host does not support IBuildEngine /// callbacks for the purposes of building projects, so error. /// - public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion) + bool IBuildEngine2.BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion) { LogErrorFromResource("BuildEngineCallbacksInTaskHostUnsupported"); return false; @@ -352,7 +313,7 @@ public bool BuildProjectFile(string projectFileName, string[] targetNames, IDict /// Stub implementation of IBuildEngine2.BuildProjectFilesInParallel. The task host does not support IBuildEngine /// callbacks for the purposes of building projects, so error. /// - public bool BuildProjectFilesInParallel(string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IDictionary[] targetOutputsPerProject, string[] toolsVersion, bool useResultsCache, bool unloadProjectsOnCompletion) + bool IBuildEngine2.BuildProjectFilesInParallel(string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IDictionary[] targetOutputsPerProject, string[] toolsVersion, bool useResultsCache, bool unloadProjectsOnCompletion) { LogErrorFromResource("BuildEngineCallbacksInTaskHostUnsupported"); return false; @@ -702,9 +663,7 @@ private void RunTask(object state) _debugCommunications = taskConfiguration.BuildProcessEnvironment.ContainsValueAndIsEqual("MSBUILDDEBUGCOMM", "1", StringComparison.OrdinalIgnoreCase); _updateEnvironment = !taskConfiguration.BuildProcessEnvironment.ContainsValueAndIsEqual("MSBuildTaskHostDoNotUpdateEnvironment", "1", StringComparison.OrdinalIgnoreCase); _updateEnvironmentAndLog = taskConfiguration.BuildProcessEnvironment.ContainsValueAndIsEqual("MSBuildTaskHostUpdateEnvironmentAndLog", "1", StringComparison.OrdinalIgnoreCase); - WarningsAsErrors = taskConfiguration.WarningsAsErrors; - WarningsNotAsErrors = taskConfiguration.WarningsNotAsErrors; - WarningsAsMessages = taskConfiguration.WarningsAsMessages; + try { // Change to the startup directory From d0e24c7d5fdb5c21d3861d0dde8537b526a88ff3 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 14:10:28 -0800 Subject: [PATCH 071/136] MSBuildTaskHost: Remove FrameworkErrorUtilities FrameworkErrorUtilities is unused in MSBuildTaskHost. --- .../Framework/ErrorUtilities.cs | 62 ------------------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - 2 files changed, 63 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/ErrorUtilities.cs diff --git a/src/MSBuildTaskHost/Framework/ErrorUtilities.cs b/src/MSBuildTaskHost/Framework/ErrorUtilities.cs deleted file mode 100644 index e9b9275d7c8..00000000000 --- a/src/MSBuildTaskHost/Framework/ErrorUtilities.cs +++ /dev/null @@ -1,62 +0,0 @@ -// 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.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -namespace Microsoft.Build.Framework -{ - // TODO: this should be unified with Shared\ErrorUtilities.cs, but it is hard to untangle everything - // because some of the errors there will use localized resources from different assemblies, - // which won't be referenceable in Framework. - - internal static class FrameworkErrorUtilities - { - /// - /// This method should be used in places where one would normally put - /// an "assert". It should be used to validate that our assumptions are - /// true, where false would indicate that there must be a bug in our - /// code somewhere. This should not be used to throw errors based on bad - /// user input or anything that the user did wrong. - /// - /// - /// - internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string unformattedMessage) - { - if (!condition) - { - ThrowInternalError(unformattedMessage, innerException: null, args: null); - } - } - - /// - /// Helper to throw an InternalErrorException when the specified parameter is null. - /// This should be used ONLY if this would indicate a bug in MSBuild rather than - /// anything caused by user action. - /// - /// The value of the argument. - /// Parameter that should not be null. - internal static void VerifyThrowInternalNull([NotNull] object? parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) - { - if (parameter is null) - { - ThrowInternalError("{0} unexpectedly null", innerException: null, args: parameterName); - } - } - - /// - /// Throws InternalErrorException. - /// This is only for situations that would mean that there is a bug in MSBuild itself. - /// - [DoesNotReturn] - internal static void ThrowInternalError(string message, Exception? innerException, params object?[]? args) - { - throw new InternalErrorException( - args is null ? - message : - string.Format(message, args), - innerException); - } - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 2d9846fbf59..ad1538032b7 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -60,7 +60,6 @@ - From f6211c69d1ceb001eba7d476c4a031b3e9f0a5bd Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 14:42:59 -0800 Subject: [PATCH 072/136] MSBuildTaskHost: Remove ResponseFileUsedEventArgs ResponseFileUsedEventArgs did not exist in .NET 3.5 and are never produced in MSBuildTaskHost. --- .../Framework/ResponseFileUsedEventArgs.cs | 26 --- src/MSBuildTaskHost/LogMessagePacketBase.cs | 217 ++---------------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - 3 files changed, 20 insertions(+), 224 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/ResponseFileUsedEventArgs.cs diff --git a/src/MSBuildTaskHost/Framework/ResponseFileUsedEventArgs.cs b/src/MSBuildTaskHost/Framework/ResponseFileUsedEventArgs.cs deleted file mode 100644 index 7e9f132a262..00000000000 --- a/src/MSBuildTaskHost/Framework/ResponseFileUsedEventArgs.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; - -namespace Microsoft.Build.Framework -{ - /// - /// Arguments for the response file used event - /// - [Serializable] - public class ResponseFileUsedEventArgs : BuildMessageEventArgs - { - public ResponseFileUsedEventArgs() - { - } - /// - /// Initialize a new instance of the ResponseFileUsedEventArgs class. - /// - public ResponseFileUsedEventArgs(string? responseFilePath) : base() - { - ResponseFilePath = responseFilePath; - } - public string? ResponseFilePath { set; get; } - } -} diff --git a/src/MSBuildTaskHost/LogMessagePacketBase.cs b/src/MSBuildTaskHost/LogMessagePacketBase.cs index 830ca1b8dae..2e95a4f300f 100644 --- a/src/MSBuildTaskHost/LogMessagePacketBase.cs +++ b/src/MSBuildTaskHost/LogMessagePacketBase.cs @@ -13,6 +13,23 @@ namespace Microsoft.Build.Shared { + // On .NET Framework 3.5, Microsoft.Build.Framework includes the following concrete event args types: + // + // - BuildErrorEventArgs + // - BuildFinishedEventArgs + // - BuildMessageEventArgs + // - BuildStartedEventArgs + // - BuildWarningEventArgs + // - ExternalProjectFinishedEventArgs + // - ExternalProjectStartedEventArgs + // - ProjectFinishedEventArgs + // - ProjectStartedEventArgs + // - TargetFinishedEventArgs + // - TargetStartedEventArgs + // - TaskCommandLineEventArgs + // - TaskFinishedEventArgs + // - TaskStartedEventArgs + #region Enumerations /// /// An enumeration of all the types of BuildEventArgs that can be @@ -90,51 +107,6 @@ internal enum LoggingEventType : int /// TaskCommandLineEvent = 12, - /// - /// Event is a . - /// - TaskParameterEvent = 13, - - /// - /// Event is a . - /// - ProjectEvaluationStartedEvent = 14, - - /// - /// Event is a . - /// - ProjectEvaluationFinishedEvent = 15, - - /// - /// Event is a . - /// - ProjectImportedEvent = 16, - - /// - /// Event is a . - /// - TargetSkipped = 17, - - /// - /// Event is a . - /// - Telemetry = 18, - - /// - /// Event is an . - /// - EnvironmentVariableReadEvent = 19, - - /// - /// Event is a . - /// - ResponseFileUsedEvent = 20, - - /// - /// Event is an . - /// - AssemblyLoadEvent = 21, - /// /// Event is . /// @@ -144,101 +116,6 @@ internal enum LoggingEventType : int /// Event is . /// ExternalProjectFinishedEvent = 23, - - /// - /// Event is . - /// - ExtendedCustomEvent = 24, - - /// - /// Event is . - /// - ExtendedBuildErrorEvent = 25, - - /// - /// Event is . - /// - ExtendedBuildWarningEvent = 26, - - /// - /// Event is . - /// - ExtendedBuildMessageEvent = 27, - - /// - /// Event is . - /// - CriticalBuildMessage = 28, - - /// - /// Event is . - /// - MetaprojectGenerated = 29, - - /// - /// Event is . - /// - PropertyInitialValueSet = 30, - - /// - /// Event is . - /// - PropertyReassignment = 31, - - /// - /// Event is . - /// - UninitializedPropertyRead = 32, - - /// - /// Event is . - /// - ExtendedCriticalBuildMessageEvent = 33, - - /// - /// Event is a . - /// - GeneratedFileUsedEvent = 34, - - /// - /// Event is . - /// - BuildCheckMessageEvent = 35, - - /// - /// Event is . - /// - BuildCheckWarningEvent = 36, - - /// - /// Event is . - /// - BuildCheckErrorEvent = 37, - - /// - /// Event is . - /// - BuildCheckTracingEvent = 38, - - /// - /// Event is . - /// - BuildCheckAcquisitionEvent = 39, - - /// - /// Event is . - /// - BuildSubmissionStartedEvent = 40, - - /// - /// Event is - /// - BuildCanceledEvent = 41, - - /// - /// Event is - /// - WorkerNodeTelemetryEvent = 42, } #endregion @@ -298,11 +175,6 @@ internal LogMessagePacketBase(KeyValuePair? nodeBuildEvent) _eventType = GetLoggingEventId(_buildEvent); } - /// - /// Constructor for deserialization - /// - internal LogMessagePacketBase(ITranslator translator) => Translate(translator); - #endregion #region Delegates @@ -328,29 +200,6 @@ public NodePacketType Type { get { return NodePacketType.LogMessage; } } - - /// - /// The buildEventArg wrapped by this packet - /// - internal KeyValuePair? NodeBuildEvent - { - get - { - return new KeyValuePair(_sinkId, _buildEvent); - } - } - - /// - /// The event type of the wrapped buildEventArg - /// based on the LoggingEventType enumeration - /// - internal LoggingEventType EventType - { - get - { - return _eventType; - } - } #endregion #region INodePacket Methods @@ -486,7 +335,7 @@ protected virtual void TranslateAdditionalProperties(ITranslator translator, Log /// that doesn't force the delegate to be closed over its first argument, so that we can /// only create the delegate once per event type and cache it. /// - private static Delegate CreateDelegateRobust(Type type, Object firstArgument, MethodInfo methodInfo) + private static Delegate CreateDelegateRobust(Type type, object firstArgument, MethodInfo methodInfo) { Delegate delegateMethod = null; @@ -528,7 +377,8 @@ private BuildEventArgs GetBuildEventArgFromId() LoggingEventType.TaskStartedEvent => new TaskStartedEventArgs(null, null, null, null, null), LoggingEventType.TaskFinishedEvent => new TaskFinishedEventArgs(null, null, null, null, null, false), LoggingEventType.TaskCommandLineEvent => new TaskCommandLineEventArgs(null, null, MessageImportance.Normal), - LoggingEventType.ResponseFileUsedEvent => new ResponseFileUsedEventArgs(null), + LoggingEventType.ExternalProjectStartedEvent => new ExternalProjectStartedEventArgs(null, null, null, null, null), + LoggingEventType.ExternalProjectFinishedEvent => new ExternalProjectFinishedEventArgs(null, null, null, null, false), _ => throw new InternalErrorException("Should not get to the default of GetBuildEventArgFromId ID: " + _eventType) }; @@ -600,10 +450,6 @@ private LoggingEventType GetLoggingEventId(BuildEventArgs eventArg) { return LoggingEventType.BuildErrorEvent; } - else if (eventType == typeof(ResponseFileUsedEventArgs)) - { - return LoggingEventType.ResponseFileUsedEvent; - } else { return LoggingEventType.CustomEvent; @@ -633,9 +479,6 @@ protected virtual void WriteEventToStream(BuildEventArgs buildEvent, LoggingEven case LoggingEventType.BuildMessageEvent: WriteBuildMessageEventToStream((BuildMessageEventArgs)buildEvent, translator); break; - case LoggingEventType.ResponseFileUsedEvent: - WriteResponseFileUsedEventToStream((ResponseFileUsedEventArgs)buildEvent, translator); - break; case LoggingEventType.TaskCommandLineEvent: WriteTaskCommandLineEventToStream((TaskCommandLineEventArgs)buildEvent, translator); break; @@ -731,16 +574,6 @@ private void WriteBuildMessageEventToStream(BuildMessageEventArgs buildMessageEv translator.TranslateEnum(ref importance, (int)importance); } - /// - /// Write a response file used log message into the translator - /// - private void WriteResponseFileUsedEventToStream(ResponseFileUsedEventArgs responseFileUsedEventArgs, ITranslator translator) - { - string filePath = responseFileUsedEventArgs.ResponseFilePath; - - translator.Translate(ref filePath); - } - #endregion #region Reads from Stream @@ -767,7 +600,6 @@ protected virtual BuildEventArgs ReadEventFromStream(LoggingEventType eventType, LoggingEventType.TaskCommandLineEvent => ReadTaskCommandLineEventFromStream(translator, message, helpKeyword, senderName), LoggingEventType.BuildErrorEvent => ReadTaskBuildErrorEventFromStream(translator, message, helpKeyword, senderName), LoggingEventType.BuildMessageEvent => ReadBuildMessageEventFromStream(translator, message, helpKeyword, senderName), - LoggingEventType.ResponseFileUsedEvent => ReadResponseFileUsedEventFromStream(translator, message, helpKeyword, senderName), LoggingEventType.BuildWarningEvent => ReadBuildWarningEventFromStream(translator, message, helpKeyword, senderName), _ => null, }; @@ -888,15 +720,6 @@ private BuildMessageEventArgs ReadBuildMessageEventFromStream(ITranslator transl return buildEvent; } - private ResponseFileUsedEventArgs ReadResponseFileUsedEventFromStream(ITranslator translator, string message, string helpKeyword, string senderName) - { - string responseFilePath = String.Empty; - translator.Translate(ref responseFilePath); - ResponseFileUsedEventArgs buildEvent = new ResponseFileUsedEventArgs(responseFilePath); - - return buildEvent; - } - #endregion #endregion diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index ad1538032b7..6023d70f71e 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -64,7 +64,6 @@ - From 80650a066262627c9dbef73cafdb182b14f67cda Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 15:16:47 -0800 Subject: [PATCH 073/136] MSBuildTaskHost: Simplify HandshakeOptions In MSBuildTaskHost, the HandshakeOptions are pretty simple: TaskHost, CLR2, X64 (maybe), and Administrator (maybe). --- .../CommunicationsUtilities.cs | 92 ++----------------- .../MSBuild/NodeEndpointOutOfProcTaskHost.cs | 2 +- 2 files changed, 9 insertions(+), 85 deletions(-) diff --git a/src/MSBuildTaskHost/CommunicationsUtilities.cs b/src/MSBuildTaskHost/CommunicationsUtilities.cs index 7aaacb1362d..d6953b7dc2a 100644 --- a/src/MSBuildTaskHost/CommunicationsUtilities.cs +++ b/src/MSBuildTaskHost/CommunicationsUtilities.cs @@ -730,92 +730,16 @@ out HandshakeResult result /// /// Given the appropriate information, return the equivalent HandshakeOptions. /// - internal static HandshakeOptions GetHandshakeOptions( - bool taskHost, - TaskHostParameters taskHostParameters, - string architectureFlagToSet = null, - bool nodeReuse = false, - bool lowPriority = false) + internal static HandshakeOptions GetHandshakeOptions() { - HandshakeOptions context = taskHost ? HandshakeOptions.TaskHost : HandshakeOptions.None; + // For MSBuildTaskHost, the HandshakeOptions are easy to compute. + HandshakeOptions options = HandshakeOptions.TaskHost; - int clrVersion = 0; + options |= HandshakeOptions.CLR2; - // We don't know about the TaskHost. - if (taskHost) + if (NativeMethodsShared.Is64Bit) { - // No parameters given, default to current - if (taskHostParameters.IsEmpty) - { - clrVersion = typeof(bool).Assembly.GetName().Version.Major; - architectureFlagToSet = MSBuildArchitecture.GetCurrent(); - } - else // Figure out flags based on parameters given - { - ErrorUtilities.VerifyThrow(taskHostParameters.Runtime != null, "Should always have an explicit runtime when we call this method."); - ErrorUtilities.VerifyThrow(taskHostParameters.Architecture != null, "Should always have an explicit architecture when we call this method."); - - if (taskHostParameters.Runtime.Equals(MSBuildRuntime.clr2, StringComparison.OrdinalIgnoreCase)) - { - clrVersion = 2; - } - else if (taskHostParameters.Runtime.Equals(MSBuildRuntime.clr4, StringComparison.OrdinalIgnoreCase)) - { - clrVersion = 4; - } - else if (taskHostParameters.Runtime.Equals(MSBuildRuntime.net, StringComparison.OrdinalIgnoreCase)) - { - clrVersion = 5; - } - else - { - ErrorUtilities.ThrowInternalErrorUnreachable(); - } - - architectureFlagToSet = taskHostParameters.Architecture; - } - } - - if (!string.IsNullOrEmpty(architectureFlagToSet)) - { - if (architectureFlagToSet.Equals(MSBuildArchitecture.x64, StringComparison.OrdinalIgnoreCase)) - { - context |= HandshakeOptions.X64; - } - else if (architectureFlagToSet.Equals(MSBuildArchitecture.arm64, StringComparison.OrdinalIgnoreCase)) - { - context |= HandshakeOptions.Arm64; - } - } - - switch (clrVersion) - { - case 0: - // Not a taskhost, runtime must match - case 4: - // Default for MSBuild running on .NET Framework 4, - // not represented in handshake - break; - case 2: - context |= HandshakeOptions.CLR2; - break; - case >= 5: - context |= HandshakeOptions.NET; - break; - default: - ErrorUtilities.ThrowInternalErrorUnreachable(); - break; - } - - // Node reuse is not supported in CLR2 because it's a legacy runtime. - if (nodeReuse && clrVersion != 2) - { - context |= HandshakeOptions.NodeReuse; - } - - if (lowPriority) - { - context |= HandshakeOptions.LowPriority; + options |= HandshakeOptions.X64; } // If we are running in elevated privs, we will only accept a handshake from an elevated process as well. @@ -824,10 +748,10 @@ internal static HandshakeOptions GetHandshakeOptions( // to the username check which is also done on connection. if (new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) { - context |= HandshakeOptions.Administrator; + options |= HandshakeOptions.Administrator; } - return context; + return options; } /// diff --git a/src/MSBuildTaskHost/MSBuild/NodeEndpointOutOfProcTaskHost.cs b/src/MSBuildTaskHost/MSBuild/NodeEndpointOutOfProcTaskHost.cs index 9769d7b6b59..644b45673a1 100644 --- a/src/MSBuildTaskHost/MSBuild/NodeEndpointOutOfProcTaskHost.cs +++ b/src/MSBuildTaskHost/MSBuild/NodeEndpointOutOfProcTaskHost.cs @@ -35,6 +35,6 @@ internal NodeEndpointOutOfProcTaskHost(bool nodeReuse, byte parentPacketVersion) /// Returns the host handshake for this node endpoint /// protected override Handshake GetHandshake() => - new(CommunicationsUtilities.GetHandshakeOptions(taskHost: true, taskHostParameters: TaskHostParameters.Empty, nodeReuse: _nodeReuse)); + new(CommunicationsUtilities.GetHandshakeOptions()); } } From 6875837e724528c4b3aedd579f120eeb70b3b08f Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 15:18:01 -0800 Subject: [PATCH 074/136] MSBuildTaskHost: Remove TaskHostParameters TaskHostParameters are unused in MSBuildTaskHost. --- .../Framework/TaskHostParameters.cs | 168 ------------------ src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - 2 files changed, 169 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/TaskHostParameters.cs diff --git a/src/MSBuildTaskHost/Framework/TaskHostParameters.cs b/src/MSBuildTaskHost/Framework/TaskHostParameters.cs deleted file mode 100644 index 97ee9223a96..00000000000 --- a/src/MSBuildTaskHost/Framework/TaskHostParameters.cs +++ /dev/null @@ -1,168 +0,0 @@ -// 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; - -namespace Microsoft.Build.Framework -{ - /// - /// A readonly struct that represents task host parameters used to determine which host process to launch. - /// - public readonly struct TaskHostParameters : IEquatable - { - /// - /// A static empty instance to avoid allocations when default parameters are needed. - /// - public static readonly TaskHostParameters Empty = new(); - - private readonly string? _runtime; - private readonly string? _architecture; - private readonly string? _dotnetHostPath; - private readonly string? _msBuildAssemblyPath; - private readonly bool? _taskHostFactoryExplicitlyRequested; - - /// - /// Initializes a new instance of the TaskHostParameters struct with the specified parameters. - /// - /// The target runtime identifier (e.g., "net8.0", "net472"). - /// The target architecture (e.g., "x64", "x86", "arm64"). - /// The path to the dotnet host executable. - /// The path to the MSBuild assembly. - /// Defines if Task Host Factory was explicitly requested. - internal TaskHostParameters( - string? runtime = null, - string? architecture = null, - string? dotnetHostPath = null, - string? msBuildAssemblyPath = null, - bool? taskHostFactoryExplicitlyRequested = null) - { - _runtime = runtime; - _architecture = architecture; - _dotnetHostPath = dotnetHostPath; - _msBuildAssemblyPath = msBuildAssemblyPath; - _taskHostFactoryExplicitlyRequested = taskHostFactoryExplicitlyRequested; - } - - /// - /// Gets the target runtime identifier (e.g., "net8.0", "net472"). - /// - /// The runtime identifier, or null if not specified. - public string? Runtime => _runtime; - - /// - /// Gets the target architecture (e.g., "x64", "x86", "arm64"). - /// - /// The architecture identifier, or an empty string if not specified. - public string? Architecture => _architecture; - - /// - /// Gets the path to the dotnet host executable. - /// - /// The dotnet host path, or null if not specified. - public string? DotnetHostPath => _dotnetHostPath; - - /// - /// Gets the path to the MSBuild assembly. - /// - /// The MSBuild assembly path, or null if not specified. - public string? MSBuildAssemblyPath => _msBuildAssemblyPath; - - /// - /// Gets if Task Host Factory was requested explicitly (by specifying TaskHost="TaskHostFactory" in UsingTask element). - /// - public bool? TaskHostFactoryExplicitlyRequested => _taskHostFactoryExplicitlyRequested; - - public override bool Equals(object? obj) => obj is TaskHostParameters other && Equals(other); - - public bool Equals(TaskHostParameters other) => - StringComparer.OrdinalIgnoreCase.Equals(Runtime ?? string.Empty, other.Runtime ?? string.Empty) - && StringComparer.OrdinalIgnoreCase.Equals(Architecture ?? string.Empty, other.Architecture ?? string.Empty) - && TaskHostFactoryExplicitlyRequested == other.TaskHostFactoryExplicitlyRequested; - - public override int GetHashCode() - { - // Manual hash code implementation for compatibility with .NET Framework 4.7.2 - var comparer = StringComparer.OrdinalIgnoreCase; - - unchecked - { - int hash = 17; - hash = hash * 31 + comparer.GetHashCode(Runtime ?? string.Empty); - hash = hash * 31 + comparer.GetHashCode(Architecture ?? string.Empty); - hash = hash * 31 + (TaskHostFactoryExplicitlyRequested?.GetHashCode() ?? 0); - - return hash; - } - } - - /// - /// Gets a value indicating whether returns true if parameters were unset. - /// - internal bool IsEmpty => Equals(Empty); - - /// - /// Merges two TaskHostParameters instances, with the second parameter values taking precedence when both are specified. - /// - /// The base parameters. - /// The override parameters that take precedence. - /// A new TaskHostParameters with merged values. - internal static TaskHostParameters MergeTaskHostParameters(TaskHostParameters baseParameters, TaskHostParameters overrideParameters) - { - // If both are empty, return empty - if (baseParameters.IsEmpty && overrideParameters.IsEmpty) - { - return Empty; - } - - // If override is empty, return base - if (overrideParameters.IsEmpty) - { - return baseParameters; - } - - // If base is empty, return override - if (baseParameters.IsEmpty) - { - return overrideParameters; - } - - // Merge: override values take precedence, fall back to base values - return new TaskHostParameters( - runtime: overrideParameters.Runtime ?? baseParameters.Runtime, - architecture: overrideParameters.Architecture ?? baseParameters.Architecture, - dotnetHostPath: overrideParameters.DotnetHostPath ?? baseParameters.DotnetHostPath, - msBuildAssemblyPath: overrideParameters.MSBuildAssemblyPath ?? baseParameters.MSBuildAssemblyPath, - taskHostFactoryExplicitlyRequested: overrideParameters.TaskHostFactoryExplicitlyRequested ?? baseParameters.TaskHostFactoryExplicitlyRequested); - } - - /// - /// Creates a new instance of with the specified value for the - /// property. - /// - internal TaskHostParameters WithTaskHostFactoryExplicitlyRequested(bool taskHostFactoryExplicitlyRequested) - { - if (_taskHostFactoryExplicitlyRequested == taskHostFactoryExplicitlyRequested) - { - return this; - } - - return new TaskHostParameters( - runtime: _runtime, - architecture: _architecture, - dotnetHostPath: _dotnetHostPath, - msBuildAssemblyPath: _msBuildAssemblyPath, - taskHostFactoryExplicitlyRequested: taskHostFactoryExplicitlyRequested); - } - - /// - /// The method was added to sustain compatibility with ITaskFactory2 factoryIdentityParameters parameters dictionary. - /// - internal Dictionary ToDictionary() => new(3, StringComparer.OrdinalIgnoreCase) - { - { nameof(Runtime), Runtime ?? string.Empty }, - { nameof(Architecture), Architecture ?? string.Empty }, - { nameof(TaskHostFactoryExplicitlyRequested), TaskHostFactoryExplicitlyRequested?.ToString() ?? string.Empty }, - }; - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 6023d70f71e..3794f1db547 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -66,7 +66,6 @@ - From 63278ab3e4c16e4a8eb599b163515acaf05f25a3 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 15:30:38 -0800 Subject: [PATCH 075/136] MSBuildTaskHost: Merge NodeEndpointOutOfProcTaskHost NodeEndpointOutOfProcTaskHost and NodeEndpointOutOfProcTaskHostBase can be merged in MSBuildTaskHost. Note that "node reuse" is always false in MSBuildTaskHost. --- .../MSBuild/NodeEndpointOutOfProcTaskHost.cs | 40 ------- .../MSBuild/OutOfProcTaskHostNode.cs | 25 ++-- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 3 +- ...se.cs => NodeEndpointOutOfProcTaskHost.cs} | 107 ++++++++---------- 4 files changed, 53 insertions(+), 122 deletions(-) delete mode 100644 src/MSBuildTaskHost/MSBuild/NodeEndpointOutOfProcTaskHost.cs rename src/MSBuildTaskHost/{NodeEndpointOutOfProcBase.cs => NodeEndpointOutOfProcTaskHost.cs} (97%) diff --git a/src/MSBuildTaskHost/MSBuild/NodeEndpointOutOfProcTaskHost.cs b/src/MSBuildTaskHost/MSBuild/NodeEndpointOutOfProcTaskHost.cs deleted file mode 100644 index 644b45673a1..00000000000 --- a/src/MSBuildTaskHost/MSBuild/NodeEndpointOutOfProcTaskHost.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Build.BackEnd; -using Microsoft.Build.Framework; -using Microsoft.Build.Internal; - -#nullable disable - -namespace Microsoft.Build.CommandLine -{ - /// - /// This is an implementation of INodeEndpoint for the out-of-proc nodes. It acts only as a client. - /// - internal class NodeEndpointOutOfProcTaskHost : NodeEndpointOutOfProcBase - { - internal bool _nodeReuse; - - #region Constructors and Factories - - /// - /// Instantiates an endpoint to act as a client. - /// - /// Whether node reuse is enabled. - /// The packet version supported by the parent. 1 if parent doesn't support version negotiation. - internal NodeEndpointOutOfProcTaskHost(bool nodeReuse, byte parentPacketVersion) - { - _nodeReuse = nodeReuse; - InternalConstruct(pipeName: null, parentPacketVersion); - } - - #endregion // Constructors and Factories - - /// - /// Returns the host handshake for this node endpoint - /// - protected override Handshake GetHandshake() => - new(CommunicationsUtilities.GetHandshakeOptions()); - } -} diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs index b9b9cab503d..13d5ca2efd8 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs @@ -144,11 +144,6 @@ internal class OutOfProcTaskHostNode : MarshalByRefObject, INodePacketFactory, I /// private bool _updateEnvironmentAndLog; - /// - /// setting this to true means we're running a long-lived sidecar node. - /// - private bool _nodeReuse; - /// /// Constructor. /// @@ -402,15 +397,14 @@ public void PacketReceived(int node, INodePacket packet) /// /// The exception which caused shutdown, if any. /// The reason for shutting down. - public NodeEngineShutdownReason Run(out Exception shutdownException, bool nodeReuse = false, byte parentPacketVersion = 1) + public NodeEngineShutdownReason Run(out Exception shutdownException, byte parentPacketVersion = 1) { shutdownException = null; // Snapshot the current environment _savedEnvironment = CommunicationsUtilities.GetEnvironmentVariables(); - _nodeReuse = nodeReuse; - _nodeEndpoint = new NodeEndpointOutOfProcTaskHost(nodeReuse, parentPacketVersion); + _nodeEndpoint = new NodeEndpointOutOfProcTaskHost(parentPacketVersion); _nodeEndpoint.OnLinkStatusChanged += new LinkStatusChangedDelegate(OnLinkStatusChanged); _nodeEndpoint.Listen(this); @@ -565,16 +559,11 @@ private void HandleNodeBuildComplete(NodeBuildComplete buildComplete) { ErrorUtilities.VerifyThrow(!_isTaskExecuting, "We should never have a task in the process of executing when we receive NodeBuildComplete."); - // Sidecar TaskHost will persist after the build is done. - if (_nodeReuse) - { - _shutdownReason = NodeEngineShutdownReason.BuildCompleteReuse; - } - else - { - // TaskHostNodes lock assemblies with custom tasks produced by build scripts if NodeReuse is on. This causes failures if the user builds twice. - _shutdownReason = buildComplete.PrepareForReuse && Traits.Instance.EscapeHatches.ReuseTaskHostNodes ? NodeEngineShutdownReason.BuildCompleteReuse : NodeEngineShutdownReason.BuildComplete; - } + // TaskHostNodes lock assemblies with custom tasks produced by build scripts if NodeReuse is on. This causes failures if the user builds twice. + _shutdownReason = buildComplete.PrepareForReuse && Traits.Instance.EscapeHatches.ReuseTaskHostNodes + ? NodeEngineShutdownReason.BuildCompleteReuse + : NodeEngineShutdownReason.BuildComplete; + _shutdownEvent.Set(); } diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 3794f1db547..e3afa6c0df6 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -78,11 +78,10 @@ - - + diff --git a/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs b/src/MSBuildTaskHost/NodeEndpointOutOfProcTaskHost.cs similarity index 97% rename from src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs rename to src/MSBuildTaskHost/NodeEndpointOutOfProcTaskHost.cs index 7e2e6ee964a..dd2cf431514 100644 --- a/src/MSBuildTaskHost/NodeEndpointOutOfProcBase.cs +++ b/src/MSBuildTaskHost/NodeEndpointOutOfProcTaskHost.cs @@ -2,17 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using Microsoft.Build.Shared.Concurrent; -using System.Threading; -using Microsoft.Build.Internal; -using Microsoft.Build.Shared; -using System.IO.Pipes; using System.IO; -using System.Collections.Generic; - +using System.IO.Pipes; using System.Security.AccessControl; using System.Security.Principal; +using System.Threading; +using Microsoft.Build.Internal; +using Microsoft.Build.Shared; +using Microsoft.Build.Shared.Concurrent; #nullable disable @@ -22,7 +21,7 @@ namespace Microsoft.Build.BackEnd /// This is an implementation of INodeEndpoint for the out-of-proc nodes. It acts only as a client. /// [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "It is expected to keep the stream open for the process lifetime")] - internal abstract class NodeEndpointOutOfProcBase : INodeEndpoint + internal sealed class NodeEndpointOutOfProcTaskHost : INodeEndpoint { #region Private Data @@ -106,6 +105,42 @@ internal abstract class NodeEndpointOutOfProcBase : INodeEndpoint #endregion + public NodeEndpointOutOfProcTaskHost(byte parentPacketVersion) + { + _status = LinkStatus.Inactive; + _asyncDataMonitor = new object(); + _sharedReadBuffer = InterningBinaryReader.CreateSharedBuffer(); + + _packetStream = new MemoryStream(); + _binaryWriter = new BinaryWriter(_packetStream); + _parentPacketVersion = parentPacketVersion; + + string pipeName = $"MSBuild{EnvironmentUtilities.CurrentProcessId}"; + + SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner; + PipeSecurity security = new PipeSecurity(); + + // Restrict access to just this account. We set the owner specifically here, and on the + // pipe client side they will check the owner against this one - they must have identical + // SIDs or the client will reject this server. This is used to avoid attacks where a + // hacked server creates a less restricted pipe in an attempt to lure us into using it and + // then sending build requests to the real pipe client (which is the MSBuild Build Manager.) + PipeAccessRule rule = new PipeAccessRule(identifier, PipeAccessRights.ReadWrite, AccessControlType.Allow); + security.AddAccessRule(rule); + security.SetOwner(identifier); + + _pipeServer = new NamedPipeServerStream( + pipeName, + PipeDirection.InOut, + 1, // Only allow one connection at a time. + PipeTransmissionMode.Byte, + PipeOptions.Asynchronous | PipeOptions.WriteThrough, + PipeBufferSize, // Default input buffer + PipeBufferSize, // Default output buffer + security, + HandleInheritability.None); + } + #region INodeEndpoint Events /// @@ -127,10 +162,6 @@ public LinkStatus LinkStatus #endregion - #region Properties - - #endregion - #region INodeEndpoint Methods /// @@ -186,59 +217,11 @@ public void ClientWillDisconnect() #endregion - #region Construction - - /// - /// Instantiates an endpoint to act as a client. - /// - internal void InternalConstruct(string pipeName = null, byte parentPacketVersion = 1) - { - _status = LinkStatus.Inactive; - _asyncDataMonitor = new object(); - _sharedReadBuffer = InterningBinaryReader.CreateSharedBuffer(); - - _packetStream = new MemoryStream(); - _binaryWriter = new BinaryWriter(_packetStream); - _parentPacketVersion = parentPacketVersion; - - pipeName ??= $"MSBuild{EnvironmentUtilities.CurrentProcessId}"; - - SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner; - PipeSecurity security = new PipeSecurity(); - - // Restrict access to just this account. We set the owner specifically here, and on the - // pipe client side they will check the owner against this one - they must have identical - // SIDs or the client will reject this server. This is used to avoid attacks where a - // hacked server creates a less restricted pipe in an attempt to lure us into using it and - // then sending build requests to the real pipe client (which is the MSBuild Build Manager.) - PipeAccessRule rule = new PipeAccessRule(identifier, PipeAccessRights.ReadWrite, AccessControlType.Allow); - security.AddAccessRule(rule); - security.SetOwner(identifier); - - _pipeServer = new NamedPipeServerStream( - pipeName, - PipeDirection.InOut, - 1, // Only allow one connection at a time. - PipeTransmissionMode.Byte, - PipeOptions.Asynchronous | PipeOptions.WriteThrough, - PipeBufferSize, // Default input buffer - PipeBufferSize, // Default output buffer - security, - HandleInheritability.None); - } - - #endregion - - /// - /// Returns the host handshake for this node endpoint. - /// - protected abstract Handshake GetHandshake(); - /// /// Updates the current link status if it has changed and notifies any registered delegates. /// /// The status the node should now be in. - protected void ChangeLinkStatus(LinkStatus newStatus) + private void ChangeLinkStatus(LinkStatus newStatus) { ErrorUtilities.VerifyThrow(_status != newStatus, "Attempting to change status to existing status {0}.", _status); CommunicationsUtilities.Trace("Changing link status from {0} to {1}", _status.ToString(), newStatus.ToString()); @@ -349,7 +332,7 @@ private void PacketPumpProc() // The handshake protocol is a series of int exchanges. The host sends us a each component, and we // verify it. Afterwards, the host sends an "End of Handshake" signal, to which we respond in kind. // Once the handshake is complete, both sides can be assured the other is ready to accept data. - Handshake handshake = GetHandshake(); + Handshake handshake = new(CommunicationsUtilities.GetHandshakeOptions()); try { HandshakeComponents handshakeComponents = handshake.RetrieveHandshakeComponents(); From e49bde6fec8544947543597fdf5357b58f7bf98e Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 15:34:02 -0800 Subject: [PATCH 076/136] MSBuildTaskHost: Merge OutOfProcTaskAppDomainWrapper OutOfProcTaskAppDomainWrapper and OutOfProcTaskAppDomainWrapperBase can be merged in MSBuildTaskHost. --- .../OutOfProcTaskAppDomainWrapperBase.cs | 400 ------------------ src/MSBuildTaskHost/MSBuildTaskHost.csproj | 3 +- .../OutOfProcTaskAppDomainWrapper.cs | 385 ++++++++++++++++- 3 files changed, 385 insertions(+), 403 deletions(-) delete mode 100644 src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs deleted file mode 100644 index 4a47455560e..00000000000 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs +++ /dev/null @@ -1,400 +0,0 @@ -// 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; -using System.Threading; -using System.Reflection; - -using Microsoft.Build.BackEnd; -using Microsoft.Build.Framework; -using Microsoft.Build.Shared; - -#nullable disable - -namespace Microsoft.Build.CommandLine -{ - /// - /// Class for executing a task in an AppDomain - /// - [Serializable] - internal class OutOfProcTaskAppDomainWrapperBase : MarshalByRefObject - { - /// - /// This is the actual user task whose instance we will create and invoke Execute - /// - private ITask wrappedTask; - - /// - /// This is an appDomain instance if any is created for running this task - /// - /// - /// TaskAppDomain's non-serializability should never be an issue since even if we start running the wrapper - /// in a separate appdomain, we will not be trying to load the task on one side of the serialization - /// boundary and run it on the other. - /// - [NonSerialized] - private AppDomain _taskAppDomain; - - /// - /// Need to keep the build engine around in order to log from the task loader. - /// - private IBuildEngine buildEngine; - - /// - /// Need to keep track of the task name also so that we can log valid information - /// from the task loader. - /// - private string taskName; - - /// - /// This is the actual user task whose instance we will create and invoke Execute - /// - public ITask WrappedTask - { - get { return wrappedTask; } - } - - /// - /// We have a cancel already requested - /// This can happen before we load the module and invoke execute. - /// - internal bool CancelPending - { - get; - set; - } - - /// - /// This is responsible for invoking Execute on the Task - /// Any method calling ExecuteTask must remember to call CleanupTask - /// - /// - /// We also allow the Task to have a reference to the BuildEngine by design - /// at ITask.BuildEngine - /// - /// The OutOfProcTaskHostNode as the BuildEngine - /// The name of the task to be executed - /// The path of the task binary - /// The path to the project file in which the task invocation is located. - /// The line in the project file where the task invocation is located. - /// The column in the project file where the task invocation is located. - /// The target name that invokes this task. - /// The AppDomainSetup that we want to use to launch our AppDomainIsolated tasks - /// Parameters that will be passed to the task when created - /// Task completion result showing success, failure or if there was a crash - internal OutOfProcTaskHostTaskResult ExecuteTask( - IBuildEngine oopTaskHostNode, - string taskName, - string taskLocation, - string taskFile, - int taskLine, - int taskColumn, - string targetName, - string projectFile, - AppDomainSetup appDomainSetup, - IDictionary taskParams) - { - buildEngine = oopTaskHostNode; - this.taskName = taskName; - - _taskAppDomain = null; - wrappedTask = null; - - LoadedType taskType = null; - try - { - TypeLoader typeLoader = new(TaskLoader.IsTaskClass); - taskType = typeLoader.Load( - taskName, - AssemblyLoadInfo.Create(null, taskLocation), - logWarning: (format, args) => { }, - useTaskHost: false, - taskHostParamsMatchCurrentProc: true); - } - catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) - { - // If it's a TargetInvocationException, we only care about the contents of the inner exception, - // so just save that instead. - Exception exceptionToReturn = e is TargetInvocationException ? e.InnerException : e; - - return new OutOfProcTaskHostTaskResult( - TaskCompleteType.CrashedDuringInitialization, - exceptionToReturn, - "TaskInstantiationFailureError", - [taskName, taskLocation, String.Empty]); - } - - OutOfProcTaskHostTaskResult taskResult; - if (taskType.HasSTAThreadAttribute) - { -#if FEATURE_APARTMENT_STATE - taskResult = InstantiateAndExecuteTaskInSTAThread( - oopTaskHostNode, - taskType, - taskName, - taskLocation, - taskFile, - taskLine, - taskColumn, - targetName, - projectFile, - appDomainSetup, - taskParams); -#else - return new OutOfProcTaskHostTaskResult( - TaskCompleteType.CrashedDuringInitialization, - null, - "TaskInstantiationFailureNotSupported", - [taskName, taskLocation, typeof(RunInSTAAttribute).FullName]); -#endif - } - else - { - taskResult = InstantiateAndExecuteTask( - oopTaskHostNode, - taskType, - taskName, - taskLocation, - taskFile, - taskLine, - taskColumn, - targetName, - projectFile, - appDomainSetup, - taskParams); - } - - return taskResult; - } - - /// - /// This is responsible for cleaning up the task after the OutOfProcTaskHostNode has gathered everything it needs from this execution - /// For example: We will need to hold on new AppDomains created until we finish getting all outputs from the task - /// Add any other cleanup tasks here. Any method calling ExecuteTask must remember to call CleanupTask. - /// - internal void CleanupTask() - { - if (_taskAppDomain != null) - { - AppDomain.Unload(_taskAppDomain); - } - - TaskLoader.RemoveAssemblyResolver(); - wrappedTask = null; - } - -#if FEATURE_APARTMENT_STATE - /// - /// Execute a task on the STA thread. - /// - /// - /// STA thread launching code lifted from XMakeBuildEngine\BackEnd\Components\RequestBuilder\TaskBuilder.cs, ExecuteTaskInSTAThread method. - /// Any bug fixes made to this code, please ensure that you also fix that code. - /// - private OutOfProcTaskHostTaskResult InstantiateAndExecuteTaskInSTAThread( - IBuildEngine oopTaskHostNode, - LoadedType taskType, - string taskName, - string taskLocation, - string taskFile, - int taskLine, - int taskColumn, - string targetName, - string projectFile, - AppDomainSetup appDomainSetup, - IDictionary taskParams) - { - ManualResetEvent taskRunnerFinished = new ManualResetEvent(false); - OutOfProcTaskHostTaskResult taskResult = null; - Exception exceptionFromExecution = null; - - try - { - ThreadStart taskRunnerDelegate = delegate () - { - try - { - taskResult = InstantiateAndExecuteTask( - oopTaskHostNode, - taskType, - taskName, - taskLocation, - taskFile, - taskLine, - taskColumn, - targetName, - projectFile, - appDomainSetup, - taskParams); - } - catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) - { - exceptionFromExecution = e; - } - finally - { - taskRunnerFinished.Set(); - } - }; - - Thread staThread = new Thread(taskRunnerDelegate); - staThread.SetApartmentState(ApartmentState.STA); - staThread.Name = "MSBuild STA task runner thread"; - staThread.CurrentCulture = Thread.CurrentThread.CurrentCulture; - staThread.CurrentUICulture = Thread.CurrentThread.CurrentUICulture; - staThread.Start(); - - // TODO: Why not just Join on the thread??? - taskRunnerFinished.WaitOne(); - } - finally - { - taskRunnerFinished.Close(); - taskRunnerFinished = null; - } - - if (exceptionFromExecution != null) - { - // Unfortunately this will reset the callstack - throw exceptionFromExecution; - } - - return taskResult; - } -#endif - - /// - /// Do the work of actually instantiating and running the task. - /// - private OutOfProcTaskHostTaskResult InstantiateAndExecuteTask( - IBuildEngine oopTaskHostNode, - LoadedType taskType, - string taskName, - string taskLocation, - string taskFile, - int taskLine, - int taskColumn, - string targetName, - string projectFile, - AppDomainSetup appDomainSetup, - IDictionary taskParams) - { - _taskAppDomain = null; - wrappedTask = null; - - try - { -#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter - wrappedTask = TaskLoader.CreateTask( - taskType, - taskName, - taskFile, - taskLine, - taskColumn, - new TaskLoader.LogError(LogErrorDelegate), - appDomainSetup, - // custom app domain assembly loading won't be available for task host - null, - true, /* always out of proc */ - out _taskAppDomain); -#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter - - wrappedTask.BuildEngine = oopTaskHostNode; - } - catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) - { - Exception exceptionToReturn = e; - - // If it's a TargetInvocationException, we only care about the contents of the inner exception, - // so just save that instead. - if (e is TargetInvocationException) - { - exceptionToReturn = e.InnerException; - } - - return new OutOfProcTaskHostTaskResult( - TaskCompleteType.CrashedDuringInitialization, - exceptionToReturn, - "TaskInstantiationFailureError", - [taskName, taskLocation, String.Empty]); - } - - foreach (KeyValuePair param in taskParams) - { - try - { - PropertyInfo paramInfo = wrappedTask.GetType().GetProperty(param.Key, BindingFlags.Instance | BindingFlags.Public); - paramInfo.SetValue(wrappedTask, param.Value?.WrappedParameter, null); - } - catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) - { - return new OutOfProcTaskHostTaskResult( - TaskCompleteType.CrashedDuringInitialization, - // If it's a TargetInvocationException, we only care about the contents of the inner exception, so save that instead. - e is TargetInvocationException ? e.InnerException : e, - "InvalidTaskAttributeError", - [param.Key, param.Value.ToString(), taskName]); - } - } - - bool success = false; - try - { - if (CancelPending) - { - return new OutOfProcTaskHostTaskResult(TaskCompleteType.Failure); - } - - // If it didn't crash and return before now, we're clear to go ahead and execute here. - success = wrappedTask.Execute(); - } - catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) - { - return new OutOfProcTaskHostTaskResult(TaskCompleteType.CrashedDuringExecution, e); - } - - PropertyInfo[] finalPropertyValues = wrappedTask.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); - - IDictionary finalParameterValues = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (PropertyInfo value in finalPropertyValues) - { - // only record outputs - if (value.GetCustomAttributes(typeof(OutputAttribute), true).Length > 0) - { - try - { - finalParameterValues[value.Name] = value.GetValue(wrappedTask, null); - } - catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) - { - // If it's not a critical exception, we assume there's some sort of problem in the parameter getter -- - // so save the exception, and we'll re-throw once we're back on the main node side of the - // communications pipe. - finalParameterValues[value.Name] = e; - } - } - } - - return new OutOfProcTaskHostTaskResult(success ? TaskCompleteType.Success : TaskCompleteType.Failure, finalParameterValues); - } - - /// - /// Logs errors from TaskLoader - /// - private void LogErrorDelegate(string taskLocation, int taskLine, int taskColumn, string message, params object[] messageArgs) - { - buildEngine.LogErrorEvent(new BuildErrorEventArgs( - null, - null, - taskLocation, - taskLine, - taskColumn, - 0, - 0, - ResourceUtilities.FormatString(AssemblyResources.GetString(message), messageArgs), - null, - taskName)); - } - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index e3afa6c0df6..20095e01cf6 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -79,13 +79,12 @@ - + - diff --git a/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs b/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs index 442522f743b..370ee8462b3 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs @@ -2,6 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using Microsoft.Build.BackEnd; +using Microsoft.Build.Framework; +using Microsoft.Build.Shared; #nullable disable @@ -11,8 +17,385 @@ namespace Microsoft.Build.CommandLine /// Class for executing a task in an AppDomain /// [Serializable] - internal class OutOfProcTaskAppDomainWrapper : OutOfProcTaskAppDomainWrapperBase + internal class OutOfProcTaskAppDomainWrapper : MarshalByRefObject { + /// + /// This is the actual user task whose instance we will create and invoke Execute + /// + private ITask wrappedTask; + + /// + /// This is an appDomain instance if any is created for running this task + /// + /// + /// TaskAppDomain's non-serializability should never be an issue since even if we start running the wrapper + /// in a separate appdomain, we will not be trying to load the task on one side of the serialization + /// boundary and run it on the other. + /// + [NonSerialized] + private AppDomain _taskAppDomain; + + /// + /// Need to keep the build engine around in order to log from the task loader. + /// + private IBuildEngine buildEngine; + + /// + /// Need to keep track of the task name also so that we can log valid information + /// from the task loader. + /// + private string taskName; + + /// + /// This is the actual user task whose instance we will create and invoke Execute + /// + public ITask WrappedTask + { + get { return wrappedTask; } + } + + /// + /// We have a cancel already requested + /// This can happen before we load the module and invoke execute. + /// + internal bool CancelPending + { + get; + set; + } + + /// + /// This is responsible for invoking Execute on the Task + /// Any method calling ExecuteTask must remember to call CleanupTask + /// + /// + /// We also allow the Task to have a reference to the BuildEngine by design + /// at ITask.BuildEngine + /// + /// The OutOfProcTaskHostNode as the BuildEngine + /// The name of the task to be executed + /// The path of the task binary + /// The path to the project file in which the task invocation is located. + /// The line in the project file where the task invocation is located. + /// The column in the project file where the task invocation is located. + /// The target name that invokes this task. + /// The AppDomainSetup that we want to use to launch our AppDomainIsolated tasks + /// Parameters that will be passed to the task when created + /// Task completion result showing success, failure or if there was a crash + internal OutOfProcTaskHostTaskResult ExecuteTask( + IBuildEngine oopTaskHostNode, + string taskName, + string taskLocation, + string taskFile, + int taskLine, + int taskColumn, + string targetName, + string projectFile, + AppDomainSetup appDomainSetup, + IDictionary taskParams) + { + buildEngine = oopTaskHostNode; + this.taskName = taskName; + + _taskAppDomain = null; + wrappedTask = null; + + LoadedType taskType = null; + try + { + TypeLoader typeLoader = new(TaskLoader.IsTaskClass); + taskType = typeLoader.Load( + taskName, + AssemblyLoadInfo.Create(null, taskLocation), + logWarning: (format, args) => { }, + useTaskHost: false, + taskHostParamsMatchCurrentProc: true); + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + // If it's a TargetInvocationException, we only care about the contents of the inner exception, + // so just save that instead. + Exception exceptionToReturn = e is TargetInvocationException ? e.InnerException : e; + + return new OutOfProcTaskHostTaskResult( + TaskCompleteType.CrashedDuringInitialization, + exceptionToReturn, + "TaskInstantiationFailureError", + [taskName, taskLocation, String.Empty]); + } + + OutOfProcTaskHostTaskResult taskResult; + if (taskType.HasSTAThreadAttribute) + { +#if FEATURE_APARTMENT_STATE + taskResult = InstantiateAndExecuteTaskInSTAThread( + oopTaskHostNode, + taskType, + taskName, + taskLocation, + taskFile, + taskLine, + taskColumn, + targetName, + projectFile, + appDomainSetup, + taskParams); +#else + return new OutOfProcTaskHostTaskResult( + TaskCompleteType.CrashedDuringInitialization, + null, + "TaskInstantiationFailureNotSupported", + [taskName, taskLocation, typeof(RunInSTAAttribute).FullName]); +#endif + } + else + { + taskResult = InstantiateAndExecuteTask( + oopTaskHostNode, + taskType, + taskName, + taskLocation, + taskFile, + taskLine, + taskColumn, + targetName, + projectFile, + appDomainSetup, + taskParams); + } + + return taskResult; + } + + /// + /// This is responsible for cleaning up the task after the OutOfProcTaskHostNode has gathered everything it needs from this execution + /// For example: We will need to hold on new AppDomains created until we finish getting all outputs from the task + /// Add any other cleanup tasks here. Any method calling ExecuteTask must remember to call CleanupTask. + /// + internal void CleanupTask() + { + if (_taskAppDomain != null) + { + AppDomain.Unload(_taskAppDomain); + } + + TaskLoader.RemoveAssemblyResolver(); + wrappedTask = null; + } + +#if FEATURE_APARTMENT_STATE + /// + /// Execute a task on the STA thread. + /// + /// + /// STA thread launching code lifted from XMakeBuildEngine\BackEnd\Components\RequestBuilder\TaskBuilder.cs, ExecuteTaskInSTAThread method. + /// Any bug fixes made to this code, please ensure that you also fix that code. + /// + private OutOfProcTaskHostTaskResult InstantiateAndExecuteTaskInSTAThread( + IBuildEngine oopTaskHostNode, + LoadedType taskType, + string taskName, + string taskLocation, + string taskFile, + int taskLine, + int taskColumn, + string targetName, + string projectFile, + AppDomainSetup appDomainSetup, + IDictionary taskParams) + { + ManualResetEvent taskRunnerFinished = new ManualResetEvent(false); + OutOfProcTaskHostTaskResult taskResult = null; + Exception exceptionFromExecution = null; + + try + { + ThreadStart taskRunnerDelegate = delegate () + { + try + { + taskResult = InstantiateAndExecuteTask( + oopTaskHostNode, + taskType, + taskName, + taskLocation, + taskFile, + taskLine, + taskColumn, + targetName, + projectFile, + appDomainSetup, + taskParams); + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + exceptionFromExecution = e; + } + finally + { + taskRunnerFinished.Set(); + } + }; + + Thread staThread = new Thread(taskRunnerDelegate); + staThread.SetApartmentState(ApartmentState.STA); + staThread.Name = "MSBuild STA task runner thread"; + staThread.CurrentCulture = Thread.CurrentThread.CurrentCulture; + staThread.CurrentUICulture = Thread.CurrentThread.CurrentUICulture; + staThread.Start(); + + // TODO: Why not just Join on the thread??? + taskRunnerFinished.WaitOne(); + } + finally + { + taskRunnerFinished.Close(); + taskRunnerFinished = null; + } + + if (exceptionFromExecution != null) + { + // Unfortunately this will reset the callstack + throw exceptionFromExecution; + } + + return taskResult; + } +#endif + + /// + /// Do the work of actually instantiating and running the task. + /// + private OutOfProcTaskHostTaskResult InstantiateAndExecuteTask( + IBuildEngine oopTaskHostNode, + LoadedType taskType, + string taskName, + string taskLocation, + string taskFile, + int taskLine, + int taskColumn, + string targetName, + string projectFile, + AppDomainSetup appDomainSetup, + IDictionary taskParams) + { + _taskAppDomain = null; + wrappedTask = null; + + try + { +#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter + wrappedTask = TaskLoader.CreateTask( + taskType, + taskName, + taskFile, + taskLine, + taskColumn, + new TaskLoader.LogError(LogErrorDelegate), + appDomainSetup, + // custom app domain assembly loading won't be available for task host + null, + true, /* always out of proc */ + out _taskAppDomain); +#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter + + wrappedTask.BuildEngine = oopTaskHostNode; + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + Exception exceptionToReturn = e; + + // If it's a TargetInvocationException, we only care about the contents of the inner exception, + // so just save that instead. + if (e is TargetInvocationException) + { + exceptionToReturn = e.InnerException; + } + + return new OutOfProcTaskHostTaskResult( + TaskCompleteType.CrashedDuringInitialization, + exceptionToReturn, + "TaskInstantiationFailureError", + [taskName, taskLocation, String.Empty]); + } + + foreach (KeyValuePair param in taskParams) + { + try + { + PropertyInfo paramInfo = wrappedTask.GetType().GetProperty(param.Key, BindingFlags.Instance | BindingFlags.Public); + paramInfo.SetValue(wrappedTask, param.Value?.WrappedParameter, null); + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + return new OutOfProcTaskHostTaskResult( + TaskCompleteType.CrashedDuringInitialization, + // If it's a TargetInvocationException, we only care about the contents of the inner exception, so save that instead. + e is TargetInvocationException ? e.InnerException : e, + "InvalidTaskAttributeError", + [param.Key, param.Value.ToString(), taskName]); + } + } + + bool success = false; + try + { + if (CancelPending) + { + return new OutOfProcTaskHostTaskResult(TaskCompleteType.Failure); + } + + // If it didn't crash and return before now, we're clear to go ahead and execute here. + success = wrappedTask.Execute(); + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + return new OutOfProcTaskHostTaskResult(TaskCompleteType.CrashedDuringExecution, e); + } + + PropertyInfo[] finalPropertyValues = wrappedTask.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); + + IDictionary finalParameterValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (PropertyInfo value in finalPropertyValues) + { + // only record outputs + if (value.GetCustomAttributes(typeof(OutputAttribute), true).Length > 0) + { + try + { + finalParameterValues[value.Name] = value.GetValue(wrappedTask, null); + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + // If it's not a critical exception, we assume there's some sort of problem in the parameter getter -- + // so save the exception, and we'll re-throw once we're back on the main node side of the + // communications pipe. + finalParameterValues[value.Name] = e; + } + } + } + + return new OutOfProcTaskHostTaskResult(success ? TaskCompleteType.Success : TaskCompleteType.Failure, finalParameterValues); + } + + /// + /// Logs errors from TaskLoader + /// + private void LogErrorDelegate(string taskLocation, int taskLine, int taskColumn, string message, params object[] messageArgs) + { + buildEngine.LogErrorEvent(new BuildErrorEventArgs( + null, + null, + taskLocation, + taskLine, + taskColumn, + 0, + 0, + ResourceUtilities.FormatString(AssemblyResources.GetString(message), messageArgs), + null, + taskName)); + } + /// /// This is a stub for CLR2 in place of the OutOfProcTaskAppDomainWrapper class /// as used in CLR4 to support cancellation of ICancelable tasks. From 7b01e698a2793428e075634f2e362bd8d982acaf Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 15:38:08 -0800 Subject: [PATCH 077/136] MSBuildTaskHost: Remove OutOfProcTaskAppDomainWrapper.CancelTask Tasks are not cancellable on .NET Framework 3.5. So, the logic in MSBuildTaskHost can be simplified a bit. --- .../MSBuild/OutOfProcTaskHostNode.cs | 24 +++++++------------ .../OutOfProcTaskAppDomainWrapper.cs | 14 ----------- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs index 13d5ca2efd8..f2cf2450ce2 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs @@ -530,24 +530,16 @@ private void CompleteTask() /// private void CancelTask() { - // If the task is an ICancellable task in CLR4 we will call it here and wait for it to complete - // Otherwise it's a classic ITask. - - // Store in a local to avoid a race - var wrapper = _taskWrapper; - if (wrapper?.CancelTask() == false) + // Create a possibility for the task to be aborted if the user really wants it dropped dead asap + if (Environment.GetEnvironmentVariable("MSBUILDTASKHOSTABORTTASKONCANCEL") == "1") { - // Create a possibility for the task to be aborted if the user really wants it dropped dead asap - if (Environment.GetEnvironmentVariable("MSBUILDTASKHOSTABORTTASKONCANCEL") == "1") + // Don't bother aborting the task if it has passed the actual user task Execute() + // It means we're already in the process of shutting down - Wait for the taskCompleteEvent to be set instead. + if (_isTaskExecuting) { - // Don't bother aborting the task if it has passed the actual user task Execute() - // It means we're already in the process of shutting down - Wait for the taskCompleteEvent to be set instead. - if (_isTaskExecuting) - { - // The thread will be terminated crudely so our environment may be trashed but it's ok since we are - // shutting down ASAP. - _taskRunnerThread.Abort(); - } + // The thread will be terminated crudely so our environment may be trashed but it's ok since we are + // shutting down ASAP. + _taskRunnerThread.Abort(); } } } diff --git a/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs b/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs index 370ee8462b3..55c29a7e21c 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs @@ -395,19 +395,5 @@ private void LogErrorDelegate(string taskLocation, int taskLine, int taskColumn, null, taskName)); } - - /// - /// This is a stub for CLR2 in place of the OutOfProcTaskAppDomainWrapper class - /// as used in CLR4 to support cancellation of ICancelable tasks. - /// We provide a stub for CancelTask here so that the OutOfProcTaskHostNode - /// that's shared by both the MSBuild.exe and MSBuildTaskHost.exe, - /// can safely allow MSBuild.exe CLR4 Out-Of-Proc Task Host to call ICancelableTask.Cancel() - /// - /// False - Used by the OutOfProcTaskHostNode to determine if the task is ICancelable - internal bool CancelTask() - { - // This method is a stub we will not do anything here. - return false; - } } } From 4e930e2506ae7b926223a5962fc3612f6683651c Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 15:43:08 -0800 Subject: [PATCH 078/136] MSBuildTaskHost: Remove MSBuildArchitecture and MSBuildRuntime These are no longer used by MSBuildTaskHost. --- src/MSBuildTaskHost/MSBuildArchitecture.cs | 22 ---------------------- src/MSBuildTaskHost/MSBuildRuntime.cs | 13 ------------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 2 -- 3 files changed, 37 deletions(-) delete mode 100644 src/MSBuildTaskHost/MSBuildArchitecture.cs delete mode 100644 src/MSBuildTaskHost/MSBuildRuntime.cs diff --git a/src/MSBuildTaskHost/MSBuildArchitecture.cs b/src/MSBuildTaskHost/MSBuildArchitecture.cs deleted file mode 100644 index 0dae1c1c5bf..00000000000 --- a/src/MSBuildTaskHost/MSBuildArchitecture.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.Build.Shared; - -internal static class MSBuildArchitecture -{ - public const string x86 = "x86"; - public const string x64 = "x64"; - public const string arm64 = "arm64"; - public const string currentArchitecture = "CurrentArchitecture"; - public const string any = "*"; - - /// - /// Returns the MSBuildArchitecture value corresponding to the current process' architecture. - /// - /// - /// Revisit if we ever run on something other than Intel. - /// - public static string GetCurrent() - => NativeMethodsShared.Is64Bit ? x64 : x86; -} diff --git a/src/MSBuildTaskHost/MSBuildRuntime.cs b/src/MSBuildTaskHost/MSBuildRuntime.cs deleted file mode 100644 index 2bca51f767f..00000000000 --- a/src/MSBuildTaskHost/MSBuildRuntime.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.Build.Shared; - -public static class MSBuildRuntime -{ - public const string clr2 = "CLR2"; - public const string clr4 = "CLR4"; - public const string currentRuntime = "CurrentRuntime"; - public const string net = "NET"; - public const string any = "*"; -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 20095e01cf6..dd55d0612a3 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -76,8 +76,6 @@ - - From bbdfdb1311b776b3004eaf09e6d02551b26891b3 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 16:39:37 -0800 Subject: [PATCH 079/136] MSBuildTaskHost: Don't use AssemblyLoadInfo In MSBuildTaskHost, AssemblyLoadInfo.Create(...) has just a single call site, and only the assemblyFile parameter is passed in. Given that, don't bother creating AssemblyLoadInfo. Instead, just pass the assembly file path through TypeLoader, LoadedType, and TaskLoader. --- src/MSBuildTaskHost/LoadedType.cs | 16 ++--- .../OutOfProcTaskAppDomainWrapper.cs | 2 +- src/MSBuildTaskHost/TaskLoader.cs | 6 +- src/MSBuildTaskHost/TypeLoader.cs | 66 +++++++------------ 4 files changed, 32 insertions(+), 58 deletions(-) diff --git a/src/MSBuildTaskHost/LoadedType.cs b/src/MSBuildTaskHost/LoadedType.cs index 4c5dd6e0538..3d2a5ed657e 100644 --- a/src/MSBuildTaskHost/LoadedType.cs +++ b/src/MSBuildTaskHost/LoadedType.cs @@ -21,7 +21,7 @@ internal sealed class LoadedType /// Creates an instance of this class for the given type. /// /// The Type to be loaded - /// Information used to load the assembly + /// The assembly file path used to load the assembly /// The assembly which has been loaded, if any /// type of an ITaskItem /// Assembly runtime based on assembly attributes. @@ -29,7 +29,7 @@ internal sealed class LoadedType /// Whether this type was loaded via MetadataLoadContext internal LoadedType( Type type, - AssemblyLoadInfo assemblyLoadInfo, + string assemblyFilePath, Assembly loadedAssembly, Type iTaskItemType, string? runtime = null, @@ -37,11 +37,11 @@ internal LoadedType( bool loadedViaMetadataLoadContext = false) { ErrorUtilities.VerifyThrow(type != null, "We must have the type."); - ErrorUtilities.VerifyThrow(assemblyLoadInfo != null, "We must have the assembly the type was loaded from."); + ErrorUtilities.VerifyThrow(assemblyFilePath != null, "We must have the assembly file path the type was loaded from."); ErrorUtilities.VerifyThrow(loadedAssembly is not null, "The assembly should always be loaded even if only by MetadataLoadContext."); Type = type; - Assembly = assemblyLoadInfo; + AssemblyFilePath = assemblyFilePath; HasSTAThreadAttribute = CheckForHardcodedSTARequirement(); LoadedAssemblyName = loadedAssembly.GetName(); @@ -51,7 +51,7 @@ internal LoadedType( // For inline tasks loaded from bytes, Assembly.Location is empty, so use the original path Path = string.IsNullOrEmpty(loadedAssembly.Location) - ? assemblyLoadInfo.AssemblyLocation + ? assemblyFilePath : loadedAssembly.Location; LoadedAssembly = loadedAssembly; @@ -132,11 +132,7 @@ private bool CheckForHardcodedSTARequirement() /// internal string[]? PropertyAssemblyQualifiedNames { get; private set; } - /// - /// Gets the assembly the type was loaded from. - /// - /// The assembly info for the loaded type. - internal AssemblyLoadInfo Assembly { get; private set; } + internal string AssemblyFilePath { get; } #endregion } diff --git a/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs b/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs index 55c29a7e21c..d91f39c49de 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs @@ -106,7 +106,7 @@ internal OutOfProcTaskHostTaskResult ExecuteTask( TypeLoader typeLoader = new(TaskLoader.IsTaskClass); taskType = typeLoader.Load( taskName, - AssemblyLoadInfo.Create(null, taskLocation), + taskLocation, logWarning: (format, args) => { }, useTaskHost: false, taskHostParamsMatchCurrentProc: true); diff --git a/src/MSBuildTaskHost/TaskLoader.cs b/src/MSBuildTaskHost/TaskLoader.cs index ab0c5f418fb..0af02f5cb74 100644 --- a/src/MSBuildTaskHost/TaskLoader.cs +++ b/src/MSBuildTaskHost/TaskLoader.cs @@ -106,9 +106,9 @@ internal static bool IsTaskClass(Type type, object unused) return (ITask?)Activator.CreateInstance(loadedType.Type); } - if (loadedType.Assembly.AssemblyFile != null) + if (loadedType.AssemblyFilePath != null) { - taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceFromAndUnwrap(loadedType.Assembly.AssemblyFile, loadedType.Type.FullName); + taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceFromAndUnwrap(loadedType.AssemblyFilePath, loadedType.Type.FullName); // this will force evaluation of the task class type and try to load the task assembly Type taskType = taskInstanceInOtherAppDomain.GetType(); @@ -123,7 +123,7 @@ internal static bool IsTaskClass(Type type, object unused) taskLine, taskColumn, "ConflictingTaskAssembly", - loadedType.Assembly.AssemblyFile, + loadedType.AssemblyFilePath, loadedType.Type.Assembly.Location); taskInstanceInOtherAppDomain = null; diff --git a/src/MSBuildTaskHost/TypeLoader.cs b/src/MSBuildTaskHost/TypeLoader.cs index 3627611b337..580e866dcf1 100644 --- a/src/MSBuildTaskHost/TypeLoader.cs +++ b/src/MSBuildTaskHost/TypeLoader.cs @@ -21,7 +21,7 @@ internal class TypeLoader /// /// Cache to keep track of the assemblyLoadInfos based on a given typeFilter. /// - private static ConcurrentDictionary> s_cacheOfLoadedTypesByFilter = new ConcurrentDictionary>(); + private static ConcurrentDictionary> s_cacheOfLoadedTypesByFilter = new(); /// /// Typefilter for this typeloader @@ -134,12 +134,12 @@ internal static bool IsPartialTypeNameMatch(string typeName1, string typeName2) /// internal LoadedType Load( string typeName, - AssemblyLoadInfo assembly, + string assemblyFilePath, LogWarningDelegate logWarning, bool useTaskHost = false, bool taskHostParamsMatchCurrentProc = true) { - return GetLoadedType(s_cacheOfLoadedTypesByFilter, typeName, assembly); + return GetLoadedType(s_cacheOfLoadedTypesByFilter, typeName, assemblyFilePath); } /// @@ -147,16 +147,18 @@ internal LoadedType Load( /// any) is unambiguous; otherwise, if there are multiple types with the same name in different namespaces, the first type /// found will be returned. /// - private LoadedType GetLoadedType(ConcurrentDictionary> cache, string typeName, AssemblyLoadInfo assembly) + private LoadedType GetLoadedType( + ConcurrentDictionary> cache, + string typeName, + string assemblyFilePath) { - // A given type filter have been used on a number of assemblies, Based on the type filter we will get another dictionary which - // will map a specific AssemblyLoadInfo to a AssemblyInfoToLoadedTypes class which knows how to find a typeName in a given assembly. - ConcurrentDictionary loadInfoToType = - cache.GetOrAdd(_isDesiredType, (_) => new ConcurrentDictionary()); + // A given type filter have been used on a number of assemblies, Based on the type filter + // we will get another dictionary which will map a specific assembly file path to a + // AssemblyInfoToLoadedTypes class which knows how to find a typeName in a given assembly. + var loadInfoToType = cache.GetOrAdd(_isDesiredType, _ => new(StringComparer.OrdinalIgnoreCase)); // Get an object which is able to take a typename and determine if it is in the assembly pointed to by the AssemblyInfo. - AssemblyInfoToLoadedTypes typeNameToType = - loadInfoToType.GetOrAdd(assembly, (_) => new AssemblyInfoToLoadedTypes(_isDesiredType, _)); + var typeNameToType = loadInfoToType.GetOrAdd(assemblyFilePath, assemblyFilePath => new(_isDesiredType, assemblyFilePath)); return typeNameToType.GetLoadedTypeByTypeName(typeName); } @@ -181,9 +183,9 @@ private class AssemblyInfoToLoadedTypes private TypeFilter _isDesiredType; /// - /// Assembly load information so we can load an assembly + /// Assembly file path so we can load an assembly /// - private AssemblyLoadInfo _assemblyLoadInfo; + private string _assemblyFilePath; /// /// What is the type for the given type name, this may be null if the typeName does not map to a type. @@ -210,13 +212,13 @@ private class AssemblyInfoToLoadedTypes /// /// Given a type filter, and an assembly to load the type information from determine if a given type name is in the assembly or not. /// - internal AssemblyInfoToLoadedTypes(TypeFilter typeFilter, AssemblyLoadInfo loadInfo) + internal AssemblyInfoToLoadedTypes(TypeFilter typeFilter, string assemblyFilePath) { ErrorUtilities.VerifyThrowArgumentNull(typeFilter, "typefilter"); - ErrorUtilities.VerifyThrowArgumentNull(loadInfo); + ErrorUtilities.VerifyThrowArgumentNull(assemblyFilePath); _isDesiredType = typeFilter; - _assemblyLoadInfo = loadInfo; + _assemblyFilePath = assemblyFilePath; _typeNameToType = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); _publicTypeNameToType = new Dictionary(StringComparer.OrdinalIgnoreCase); } @@ -232,25 +234,6 @@ internal LoadedType GetLoadedTypeByTypeName(string typeName) Type type = _typeNameToType.GetOrAdd(typeName, (key) => { - if ((_assemblyLoadInfo.AssemblyName != null) && (typeName.Length > 0)) - { - try - { - // try to load the type using its assembly qualified name - Type t2 = Type.GetType(typeName + "," + _assemblyLoadInfo.AssemblyName, false /* don't throw on error */, true /* case-insensitive */); - if (t2 != null) - { - return !_isDesiredType(t2, null) ? null : t2; - } - } - catch (ArgumentException) - { - // Type.GetType() will throw this exception if the type name is invalid -- but we have no idea if it's the - // type or the assembly name that's the problem -- so just ignore the exception, because we're going to - // check the existence/validity of the assembly and type respectively, below anyway - } - } - if (Interlocked.Read(ref _haveScannedPublicTypes) == 0) { lock (_lockObject) @@ -275,7 +258,9 @@ internal LoadedType GetLoadedTypeByTypeName(string typeName) return null; }); - return type != null ? new LoadedType(type, _assemblyLoadInfo, _loadedAssembly ?? type.Assembly, typeof(ITaskItem)) : null; + return type != null + ? new LoadedType(type, _assemblyFilePath, _loadedAssembly ?? type.Assembly, typeof(ITaskItem)) + : null; } /// @@ -287,21 +272,14 @@ private void ScanAssemblyForPublicTypes() // we need to search the assembly for the type... try { - if (_assemblyLoadInfo.AssemblyName != null) - { - _loadedAssembly = Assembly.Load(_assemblyLoadInfo.AssemblyName); - } - else - { - _loadedAssembly = Assembly.LoadFrom(_assemblyLoadInfo.AssemblyFile); - } + _loadedAssembly = Assembly.LoadFrom(_assemblyFilePath); } catch (ArgumentException e) { // Assembly.Load() and Assembly.LoadFrom() will throw an ArgumentException if the assembly name is invalid // convert to a FileNotFoundException because it's more meaningful // NOTE: don't use ErrorUtilities.VerifyThrowFileExists() here because that will hit the disk again - throw new FileNotFoundException(null, _assemblyLoadInfo.AssemblyLocation, e); + throw new FileNotFoundException(null, _assemblyFilePath, e); } // only look at public types From 848d79b18f8b8456cebf447da49b0cc3694e97a7 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 16:40:09 -0800 Subject: [PATCH 080/136] MSBuildTaskHost: Remove AssemblyLoadInfo AssemblyLoadInfo is no longer used in MSBuildTaskHost. --- src/MSBuildTaskHost/AssemblyLoadInfo.cs | 232 --------------------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - 2 files changed, 233 deletions(-) delete mode 100644 src/MSBuildTaskHost/AssemblyLoadInfo.cs diff --git a/src/MSBuildTaskHost/AssemblyLoadInfo.cs b/src/MSBuildTaskHost/AssemblyLoadInfo.cs deleted file mode 100644 index a4aeee86a93..00000000000 --- a/src/MSBuildTaskHost/AssemblyLoadInfo.cs +++ /dev/null @@ -1,232 +0,0 @@ -// 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.Diagnostics; -using System.IO; -using Microsoft.Build.BackEnd; - -#nullable disable - -namespace Microsoft.Build.Shared -{ - /// - /// This class packages information about how to load a given assembly -- an assembly can be loaded by either its assembly - /// name (strong or weak), or its filename/path. - /// - /// - /// Uses factory to instantiate correct private class to save space: only one field is ever used of the two. - /// - internal abstract class AssemblyLoadInfo : ITranslatable, IEquatable - { - /// - /// This constructor initializes the assembly information. - /// - internal static AssemblyLoadInfo Create(string assemblyName, string assemblyFile) - { - ErrorUtilities.VerifyThrow((!string.IsNullOrEmpty(assemblyName)) || (!string.IsNullOrEmpty(assemblyFile)), - "We must have either the assembly name or the assembly file/path."); - ErrorUtilities.VerifyThrow((assemblyName == null) || (assemblyFile == null), - "We must not have both the assembly name and the assembly file/path."); - - if (assemblyName != null) - { - return new AssemblyLoadInfoWithName(assemblyName); - } - else - { - return new AssemblyLoadInfoWithFile(assemblyFile); - } - } - - /// - /// Gets the assembly's identity denoted by its strong/weak name. - /// - public abstract string AssemblyName - { - get; - } - - /// - /// Gets the path to the assembly file. - /// - public abstract string AssemblyFile - { - get; - } - - /// - /// Get the assembly location - /// - internal abstract string AssemblyLocation - { - get; - } - - /// - /// Gets whether this assembly is an inline task assembly that should be loaded from bytes to avoid file locking. - /// - internal abstract bool IsInlineTask - { - get; - } - - /// - /// Computes a hashcode for this assembly info, so this object can be used as a key into - /// a hash table. - /// - public override int GetHashCode() - { - return AssemblyLocation.GetHashCode(); - } - - public bool Equals(AssemblyLoadInfo other) - { - return Equals((object)other); - } - - /// - /// Determines if two AssemblyLoadInfos are effectively the same. - /// - public override bool Equals(Object obj) - { - if (obj == null) - { - return false; - } - - AssemblyLoadInfo otherAssemblyInfo = obj as AssemblyLoadInfo; - - if (otherAssemblyInfo == null) - { - return false; - } - - return (this.AssemblyName == otherAssemblyInfo.AssemblyName) && (this.AssemblyFile == otherAssemblyInfo.AssemblyFile); - } - - public void Translate(ITranslator translator) - { - ErrorUtilities.VerifyThrow(translator.Mode == TranslationDirection.WriteToStream, "write only"); - string assemblyName = AssemblyName; - string assemblyFile = AssemblyFile; - translator.Translate(ref assemblyName); - translator.Translate(ref assemblyFile); - } - - public static AssemblyLoadInfo FactoryForTranslation(ITranslator translator) - { - string assemblyName = null; - string assemblyFile = null; - translator.Translate(ref assemblyName); - translator.Translate(ref assemblyFile); - - return Create(assemblyName, assemblyFile); - } - - /// - /// Assembly represented by name - /// - [DebuggerDisplay("{AssemblyName}")] - private sealed class AssemblyLoadInfoWithName : AssemblyLoadInfo - { - /// - /// Assembly name - /// - private string _assemblyName; - - /// - /// Constructor - /// - internal AssemblyLoadInfoWithName(string assemblyName) - { - _assemblyName = assemblyName; - } - - /// - /// Gets the assembly's identity denoted by its strong/weak name. - /// - public override string AssemblyName - { - get { return _assemblyName; } - } - - /// - /// Gets the path to the assembly file. - /// - public override string AssemblyFile - { - get { return null; } - } - - /// - /// Get the assembly location - /// - internal override string AssemblyLocation - { - get { return _assemblyName; } - } - - /// - /// Gets whether this assembly is an inline task assembly. - /// Assembly names are never inline tasks. - /// - internal override bool IsInlineTask - { - get { return false; } - } - } - - /// - /// Assembly info that uses a file path - /// - [DebuggerDisplay("{AssemblyFile}")] - private sealed class AssemblyLoadInfoWithFile : AssemblyLoadInfo - { - /// - /// Path to assembly - /// - private string _assemblyFile; - - /// - /// Constructor - /// - internal AssemblyLoadInfoWithFile(string assemblyFile) - { - ErrorUtilities.VerifyThrow(Path.IsPathRooted(assemblyFile), "Assembly file path should be rooted"); - - _assemblyFile = assemblyFile; - } - - /// - /// Gets the assembly's identity denoted by its strong/weak name. - /// - public override string AssemblyName - { - get { return null; } - } - - /// - /// Gets the path to the assembly file. - /// - public override string AssemblyFile - { - get { return _assemblyFile; } - } - - /// - /// Get the assembly location - /// - internal override string AssemblyLocation - { - get { return _assemblyFile; } - } - - /// - /// Gets whether this assembly is an inline task assembly. - /// Detects inline tasks by checking if the file path ends with the inline task suffix. - /// - internal override bool IsInlineTask => false; - } - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index dd55d0612a3..252ee002244 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -38,7 +38,6 @@ - From ffa96f468207391eb29b21527d2f9f96c0d6a572 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 16:48:21 -0800 Subject: [PATCH 081/136] MSBuildTaskHost: Remove unused members and parameters from LoadedType --- src/MSBuildTaskHost/LoadedType.cs | 53 +++++-------------------------- src/MSBuildTaskHost/TypeLoader.cs | 2 +- 2 files changed, 9 insertions(+), 46 deletions(-) diff --git a/src/MSBuildTaskHost/LoadedType.cs b/src/MSBuildTaskHost/LoadedType.cs index 3d2a5ed657e..57da96b26e1 100644 --- a/src/MSBuildTaskHost/LoadedType.cs +++ b/src/MSBuildTaskHost/LoadedType.cs @@ -6,7 +6,6 @@ using Microsoft.Build.Execution; using Microsoft.Build.Framework; - namespace Microsoft.Build.Shared { /// @@ -15,26 +14,13 @@ namespace Microsoft.Build.Shared /// internal sealed class LoadedType { - #region Constructor - /// /// Creates an instance of this class for the given type. /// /// The Type to be loaded - /// The assembly file path used to load the assembly + /// The assembly file path used to load the assembly /// The assembly which has been loaded, if any - /// type of an ITaskItem - /// Assembly runtime based on assembly attributes. - /// Assembly architecture extracted from PE flags - /// Whether this type was loaded via MetadataLoadContext - internal LoadedType( - Type type, - string assemblyFilePath, - Assembly loadedAssembly, - Type iTaskItemType, - string? runtime = null, - string? architecture = null, - bool loadedViaMetadataLoadContext = false) + internal LoadedType(Type type, string assemblyFilePath, Assembly loadedAssembly) { ErrorUtilities.VerifyThrow(type != null, "We must have the type."); ErrorUtilities.VerifyThrow(assemblyFilePath != null, "We must have the assembly file path the type was loaded from."); @@ -45,9 +31,6 @@ internal LoadedType( HasSTAThreadAttribute = CheckForHardcodedSTARequirement(); LoadedAssemblyName = loadedAssembly.GetName(); - LoadedViaMetadataLoadContext = loadedViaMetadataLoadContext; - Architecture = architecture; - Runtime = runtime; // For inline tasks loaded from bytes, Assembly.Location is empty, so use the original path Path = string.IsNullOrEmpty(loadedAssembly.Location) @@ -55,13 +38,11 @@ internal LoadedType( : loadedAssembly.Location; LoadedAssembly = loadedAssembly; - HasLoadInSeparateAppDomainAttribute = Type.IsDefined(typeof(LoadInSeparateAppDomainAttribute), true /* inherited */); - HasSTAThreadAttribute = Type.IsDefined(typeof(RunInSTAAttribute), true /* inherited */); + HasLoadInSeparateAppDomainAttribute = Type.IsDefined(typeof(LoadInSeparateAppDomainAttribute), inherit: true); + HasSTAThreadAttribute = Type.IsDefined(typeof(RunInSTAAttribute), inherit: true); IsMarshalByRef = Type.IsMarshalByRef; } - #endregion - /// /// Gets whether there's a LoadInSeparateAppDomain attribute on this type. /// @@ -77,11 +58,6 @@ internal LoadedType( /// public bool IsMarshalByRef { get; } - /// - /// Gets whether this type was loaded by using MetadataLoadContext. - /// - public bool LoadedViaMetadataLoadContext { get; } - /// /// Determines if the task has a hardcoded requirement for STA thread usage. /// @@ -105,35 +81,22 @@ private bool CheckForHardcodedSTARequirement() return false; } - #region Properties - /// /// Gets the type that was loaded from an assembly. /// /// The loaded type. - internal Type Type { get; private set; } + internal Type Type { get; } - internal AssemblyName LoadedAssemblyName { get; private set; } + internal AssemblyName LoadedAssemblyName { get; } - internal string? Architecture { get; private set; } - - internal string? Runtime { get; private set; } - - internal string Path { get; private set; } + internal string Path { get; } /// /// If we loaded an assembly for this type. /// We use this information to help created AppDomains to resolve types that it could not load successfully /// - internal Assembly LoadedAssembly { get; private set; } - - /// - /// Assembly-qualified names for properties. Only has a value if this type was loaded using MetadataLoadContext. - /// - internal string[]? PropertyAssemblyQualifiedNames { get; private set; } + internal Assembly LoadedAssembly { get; } internal string AssemblyFilePath { get; } - - #endregion } } diff --git a/src/MSBuildTaskHost/TypeLoader.cs b/src/MSBuildTaskHost/TypeLoader.cs index 580e866dcf1..63d9cc29147 100644 --- a/src/MSBuildTaskHost/TypeLoader.cs +++ b/src/MSBuildTaskHost/TypeLoader.cs @@ -259,7 +259,7 @@ internal LoadedType GetLoadedTypeByTypeName(string typeName) }); return type != null - ? new LoadedType(type, _assemblyFilePath, _loadedAssembly ?? type.Assembly, typeof(ITaskItem)) + ? new LoadedType(type, _assemblyFilePath, _loadedAssembly ?? type.Assembly) : null; } From fe8e104774d80519fbe309888e2908793fb18ac9 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 3 Feb 2026 16:55:48 -0800 Subject: [PATCH 082/136] MSBuildTaskHost: Remove RunInSTAAttribute MSBuildTaskHost includes RunInSTAAttribute in order to build LoadedType, but that attribute was never available on .NET 3.5. So, tasks can't actually take advantage of RunInSTAAttribute in MSBuildTaskHost. Even if they could, the attribute in MSBuildTaskHost would have a different type identity, so Type.IsDefined would always return false. In addition, this change also removes an old check that looked for "Microsoft.Build.Tasks.Xaml.PartialClassGenerationTask" in PresentationBuildTasks, 3.5.0.0. It doesn't seem that this task exists in the PresentationBuildTasks assembly installed to the GAC any longer. --- .../Framework/RunInSTAAttribute.cs | 26 ------------------- src/MSBuildTaskHost/LoadedType.cs | 25 ------------------ src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - 3 files changed, 52 deletions(-) delete mode 100644 src/MSBuildTaskHost/Framework/RunInSTAAttribute.cs diff --git a/src/MSBuildTaskHost/Framework/RunInSTAAttribute.cs b/src/MSBuildTaskHost/Framework/RunInSTAAttribute.cs deleted file mode 100644 index 259bc5f968c..00000000000 --- a/src/MSBuildTaskHost/Framework/RunInSTAAttribute.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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.Diagnostics.CodeAnalysis; - -#nullable disable - -namespace Microsoft.Build.Framework -{ - /// - /// This attribute is used to mark a task class as being required to run in a Single Threaded Apartment for COM. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "STA", Justification = "It is cased correctly.")] - public sealed class RunInSTAAttribute : Attribute - { - /// - /// Default constructor. - /// - public RunInSTAAttribute() - { - // do nothing - } - } -} diff --git a/src/MSBuildTaskHost/LoadedType.cs b/src/MSBuildTaskHost/LoadedType.cs index 57da96b26e1..e188949a2f1 100644 --- a/src/MSBuildTaskHost/LoadedType.cs +++ b/src/MSBuildTaskHost/LoadedType.cs @@ -29,7 +29,6 @@ internal LoadedType(Type type, string assemblyFilePath, Assembly loadedAssembly) Type = type; AssemblyFilePath = assemblyFilePath; - HasSTAThreadAttribute = CheckForHardcodedSTARequirement(); LoadedAssemblyName = loadedAssembly.GetName(); // For inline tasks loaded from bytes, Assembly.Location is empty, so use the original path @@ -39,7 +38,6 @@ internal LoadedType(Type type, string assemblyFilePath, Assembly loadedAssembly) LoadedAssembly = loadedAssembly; HasLoadInSeparateAppDomainAttribute = Type.IsDefined(typeof(LoadInSeparateAppDomainAttribute), inherit: true); - HasSTAThreadAttribute = Type.IsDefined(typeof(RunInSTAAttribute), inherit: true); IsMarshalByRef = Type.IsMarshalByRef; } @@ -58,29 +56,6 @@ internal LoadedType(Type type, string assemblyFilePath, Assembly loadedAssembly) /// public bool IsMarshalByRef { get; } - /// - /// Determines if the task has a hardcoded requirement for STA thread usage. - /// - private bool CheckForHardcodedSTARequirement() - { - // Special hard-coded attributes for certain legacy tasks which need to run as STA because they were written before - // we changed to running all tasks in MTA. - if (String.Equals("Microsoft.Build.Tasks.Xaml.PartialClassGenerationTask", Type.FullName, StringComparison.OrdinalIgnoreCase)) - { - AssemblyName assemblyName = Type.Assembly.GetName(); - Version lastVersionToForce = new Version(3, 5); - if (assemblyName.Version?.CompareTo(lastVersionToForce) > 0) - { - if (String.Equals(assemblyName.Name, "PresentationBuildTasks", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - } - - return false; - } - /// /// Gets the type that was loaded from an assembly. /// diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 252ee002244..74820ead61c 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -63,7 +63,6 @@ - From b2062ace52760ba60ac849946d3e33bffaca0ef6 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 4 Feb 2026 09:08:35 -0800 Subject: [PATCH 083/136] MSBuildTaskHost: Remove HasSTAThreadAttribute & FEATURE_APARTMENT_STATE LoadedType.HasSTAThreadAttribute is always false in the MSBuildTaskHost. So, the code path that depends on this property returning true can be removed. This effectively removes all code blocks in MSBuildTaskHost that were conditionally compiled with FEATURE_APARTMENT_STATE. So, that can be removed for MSBuildTaskHost as well. --- src/Directory.BeforeCommon.targets | 4 - src/MSBuildTaskHost/LoadedType.cs | 6 - .../OutOfProcTaskAppDomainWrapper.cs | 134 ++---------------- 3 files changed, 13 insertions(+), 131 deletions(-) diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets index 556921d7930..e133bcca71d 100644 --- a/src/Directory.BeforeCommon.targets +++ b/src/Directory.BeforeCommon.targets @@ -71,10 +71,6 @@ $(DefineConstants);FEATURE_MSCOREE - - $(DefineConstants);FEATURE_APARTMENT_STATE - - $(DefineConstants);TEST_ISWINDOWS diff --git a/src/MSBuildTaskHost/LoadedType.cs b/src/MSBuildTaskHost/LoadedType.cs index e188949a2f1..3f3d1858c8c 100644 --- a/src/MSBuildTaskHost/LoadedType.cs +++ b/src/MSBuildTaskHost/LoadedType.cs @@ -3,7 +3,6 @@ using System; using System.Reflection; -using Microsoft.Build.Execution; using Microsoft.Build.Framework; namespace Microsoft.Build.Shared @@ -46,11 +45,6 @@ internal LoadedType(Type type, string assemblyFilePath, Assembly loadedAssembly) /// public bool HasLoadInSeparateAppDomainAttribute { get; } - /// - /// Gets whether there's a STAThread attribute on the Execute method of this type. - /// - public bool HasSTAThreadAttribute { get; } - /// /// Gets whether this type implements MarshalByRefObject. /// diff --git a/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs b/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs index d91f39c49de..9a280c48d66 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Reflection; -using System.Threading; using Microsoft.Build.BackEnd; using Microsoft.Build.Framework; using Microsoft.Build.Shared; @@ -121,48 +120,21 @@ internal OutOfProcTaskHostTaskResult ExecuteTask( TaskCompleteType.CrashedDuringInitialization, exceptionToReturn, "TaskInstantiationFailureError", - [taskName, taskLocation, String.Empty]); + [taskName, taskLocation, string.Empty]); } - OutOfProcTaskHostTaskResult taskResult; - if (taskType.HasSTAThreadAttribute) - { -#if FEATURE_APARTMENT_STATE - taskResult = InstantiateAndExecuteTaskInSTAThread( - oopTaskHostNode, - taskType, - taskName, - taskLocation, - taskFile, - taskLine, - taskColumn, - targetName, - projectFile, - appDomainSetup, - taskParams); -#else - return new OutOfProcTaskHostTaskResult( - TaskCompleteType.CrashedDuringInitialization, - null, - "TaskInstantiationFailureNotSupported", - [taskName, taskLocation, typeof(RunInSTAAttribute).FullName]); -#endif - } - else - { - taskResult = InstantiateAndExecuteTask( - oopTaskHostNode, - taskType, - taskName, - taskLocation, - taskFile, - taskLine, - taskColumn, - targetName, - projectFile, - appDomainSetup, - taskParams); - } + OutOfProcTaskHostTaskResult taskResult = InstantiateAndExecuteTask( + oopTaskHostNode, + taskType, + taskName, + taskLocation, + taskFile, + taskLine, + taskColumn, + targetName, + projectFile, + appDomainSetup, + taskParams); return taskResult; } @@ -183,86 +155,6 @@ internal void CleanupTask() wrappedTask = null; } -#if FEATURE_APARTMENT_STATE - /// - /// Execute a task on the STA thread. - /// - /// - /// STA thread launching code lifted from XMakeBuildEngine\BackEnd\Components\RequestBuilder\TaskBuilder.cs, ExecuteTaskInSTAThread method. - /// Any bug fixes made to this code, please ensure that you also fix that code. - /// - private OutOfProcTaskHostTaskResult InstantiateAndExecuteTaskInSTAThread( - IBuildEngine oopTaskHostNode, - LoadedType taskType, - string taskName, - string taskLocation, - string taskFile, - int taskLine, - int taskColumn, - string targetName, - string projectFile, - AppDomainSetup appDomainSetup, - IDictionary taskParams) - { - ManualResetEvent taskRunnerFinished = new ManualResetEvent(false); - OutOfProcTaskHostTaskResult taskResult = null; - Exception exceptionFromExecution = null; - - try - { - ThreadStart taskRunnerDelegate = delegate () - { - try - { - taskResult = InstantiateAndExecuteTask( - oopTaskHostNode, - taskType, - taskName, - taskLocation, - taskFile, - taskLine, - taskColumn, - targetName, - projectFile, - appDomainSetup, - taskParams); - } - catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) - { - exceptionFromExecution = e; - } - finally - { - taskRunnerFinished.Set(); - } - }; - - Thread staThread = new Thread(taskRunnerDelegate); - staThread.SetApartmentState(ApartmentState.STA); - staThread.Name = "MSBuild STA task runner thread"; - staThread.CurrentCulture = Thread.CurrentThread.CurrentCulture; - staThread.CurrentUICulture = Thread.CurrentThread.CurrentUICulture; - staThread.Start(); - - // TODO: Why not just Join on the thread??? - taskRunnerFinished.WaitOne(); - } - finally - { - taskRunnerFinished.Close(); - taskRunnerFinished = null; - } - - if (exceptionFromExecution != null) - { - // Unfortunately this will reset the callstack - throw exceptionFromExecution; - } - - return taskResult; - } -#endif - /// /// Do the work of actually instantiating and running the task. /// From 1a4be5f460d2fc29900e1c698714865ba55728c8 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 4 Feb 2026 10:25:40 -0800 Subject: [PATCH 084/136] MSBuildTaskHost: Clean up OutOfProcTaskAppDomainWrapper and *TaskResult - Extract code to set and get parameters to separate methods - Avoid creating finalParameterValues dictionary until it's needed. - Remove unnecessary fields - Rename fields to be underscore-prefixed - Use file-scoped namespace - Enable nullability - Add and use static factory methods to OutOfProcTaskHostTaskResult - Add named arguments for clarity - Implement IDisposable instead of CleanupTask method - Don't inherit from MarshalByRefObject and remove [Serializable], since OutOfProcTaskAppDomainWrapper is never remoted. --- .../MSBuild/OutOfProcTaskHostNode.cs | 28 +- .../OutOfProcTaskAppDomainWrapper.cs | 411 ++++++++---------- .../OutOfProcTaskHostTaskResult.cs | 165 +++---- src/MSBuildTaskHost/TaskLoader.cs | 2 +- 4 files changed, 244 insertions(+), 362 deletions(-) diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs index f2cf2450ce2..815dfbf0ee0 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs @@ -636,7 +636,6 @@ private void RunTask(object state) _isTaskExecuting = true; OutOfProcTaskHostTaskResult taskResult = null; TaskHostConfiguration taskConfiguration = state as TaskHostConfiguration; - IDictionary taskParams = taskConfiguration.TaskParameters; // We only really know the values of these variables for sure once we see what we received from our parent // environment -- otherwise if this was a completely new build, we could lose out on expected environment @@ -662,33 +661,28 @@ private void RunTask(object state) Thread.CurrentThread.CurrentCulture = taskConfiguration.Culture; Thread.CurrentThread.CurrentUICulture = taskConfiguration.UICulture; - string taskName = taskConfiguration.TaskName; - string taskLocation = taskConfiguration.TaskLocation; - // We will not create an appdomain now because of a bug // As a fix, we will create the class directly without wrapping it in a domain _taskWrapper = new OutOfProcTaskAppDomainWrapper(); taskResult = _taskWrapper.ExecuteTask( - this as IBuildEngine, - taskName, - taskLocation, + buildEngine: this, + taskConfiguration.TaskName, + taskConfiguration.TaskLocation, taskConfiguration.ProjectFileOfTask, taskConfiguration.LineNumberOfTask, taskConfiguration.ColumnNumberOfTask, - taskConfiguration.TargetName, - taskConfiguration.ProjectFile, taskConfiguration.AppDomainSetup, - taskParams); + taskConfiguration.TaskParameters); } catch (ThreadAbortException) { // This thread was aborted as part of Cancellation, we will return a failure task result - taskResult = new OutOfProcTaskHostTaskResult(TaskCompleteType.Failure); + taskResult = OutOfProcTaskHostTaskResult.Failure(); } catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) { - taskResult = new OutOfProcTaskHostTaskResult(TaskCompleteType.CrashedDuringExecution, e); + taskResult = OutOfProcTaskHostTaskResult.CrashedDuringExecution(e); } finally { @@ -699,14 +693,14 @@ private void RunTask(object state) IDictionary currentEnvironment = CommunicationsUtilities.GetEnvironmentVariables(); currentEnvironment = UpdateEnvironmentForMainNode(currentEnvironment); - taskResult ??= new OutOfProcTaskHostTaskResult(TaskCompleteType.Failure); + taskResult ??= OutOfProcTaskHostTaskResult.Failure(); lock (_taskCompleteLock) { _taskCompletePacket = new TaskHostTaskComplete(taskResult, currentEnvironment); } - foreach (TaskParameter param in taskParams.Values) + foreach (TaskParameter param in taskConfiguration.TaskParameters.Values) { // Tell remoting to forget connections to the parameter RemotingServices.Disconnect(param); @@ -721,14 +715,14 @@ private void RunTask(object state) { // Create a minimal taskCompletePacket to carry the exception so that the TaskHostTask does not hang while waiting _taskCompletePacket = new TaskHostTaskComplete( - new OutOfProcTaskHostTaskResult(TaskCompleteType.CrashedAfterExecution, e), + OutOfProcTaskHostTaskResult.CrashedAfterExecution(e), buildProcessEnvironment: null); } } finally { - // Call CleanupTask to unload any domains and other necessary cleanup in the taskWrapper - _taskWrapper.CleanupTask(); + // Call Dispose to unload any AppDomains and other necessary cleanup in the taskWrapper + _taskWrapper.Dispose(); // The task has now fully completed executing _taskCompleteEvent.Set(); diff --git a/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs b/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs index 9a280c48d66..9f43faec9f1 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs @@ -8,284 +8,233 @@ using Microsoft.Build.Framework; using Microsoft.Build.Shared; -#nullable disable +namespace Microsoft.Build.CommandLine; -namespace Microsoft.Build.CommandLine +/// +/// Class for executing a task in an AppDomain. +/// +internal sealed class OutOfProcTaskAppDomainWrapper : IDisposable { + private const BindingFlags PublicInstance = BindingFlags.Public | BindingFlags.Instance; + /// - /// Class for executing a task in an AppDomain + /// This is an appDomain instance if any is created for running this task. /// - [Serializable] - internal class OutOfProcTaskAppDomainWrapper : MarshalByRefObject - { - /// - /// This is the actual user task whose instance we will create and invoke Execute - /// - private ITask wrappedTask; - - /// - /// This is an appDomain instance if any is created for running this task - /// - /// - /// TaskAppDomain's non-serializability should never be an issue since even if we start running the wrapper - /// in a separate appdomain, we will not be trying to load the task on one side of the serialization - /// boundary and run it on the other. - /// - [NonSerialized] - private AppDomain _taskAppDomain; + private AppDomain? _taskAppDomain; - /// - /// Need to keep the build engine around in order to log from the task loader. - /// - private IBuildEngine buildEngine; - - /// - /// Need to keep track of the task name also so that we can log valid information - /// from the task loader. - /// - private string taskName; + /// + /// This is responsible for invoking Execute on the Task + /// Any method calling must remember to call . + /// + /// + /// We also allow the Task to have a reference to the BuildEngine by design + /// at ITask.BuildEngine. + /// + /// The to use. + /// The name of the task to be executed. + /// The path of the task binary. + /// The path to the project file in which the task invocation is located. + /// The line in the project file where the task invocation is located. + /// The column in the project file where the task invocation is located. + /// The that we want to use to launch AppDomain-isolated tasks. + /// Parameters that will be passed to the task when created. + /// Task completion result showing success, failure or if there was a crash. + public OutOfProcTaskHostTaskResult ExecuteTask( + IBuildEngine buildEngine, + string taskName, + string taskLocation, + string taskFile, + int taskLine, + int taskColumn, + AppDomainSetup appDomainSetup, + Dictionary taskParameters) + { + _taskAppDomain = null; - /// - /// This is the actual user task whose instance we will create and invoke Execute - /// - public ITask WrappedTask + LoadedType taskType; + try { - get { return wrappedTask; } + TypeLoader typeLoader = new(TaskLoader.IsTaskClass); + taskType = typeLoader.Load( + taskName, + taskLocation, + logWarning: (format, args) => { }, + useTaskHost: false, + taskHostParamsMatchCurrentProc: true); } - - /// - /// We have a cancel already requested - /// This can happen before we load the module and invoke execute. - /// - internal bool CancelPending + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) { - get; - set; + return OutOfProcTaskHostTaskResult.CrashedDuringInitialization( + GetRelevantException(e), + "TaskInstantiationFailureError", + [taskName, taskLocation, string.Empty]); } - /// - /// This is responsible for invoking Execute on the Task - /// Any method calling ExecuteTask must remember to call CleanupTask - /// - /// - /// We also allow the Task to have a reference to the BuildEngine by design - /// at ITask.BuildEngine - /// - /// The OutOfProcTaskHostNode as the BuildEngine - /// The name of the task to be executed - /// The path of the task binary - /// The path to the project file in which the task invocation is located. - /// The line in the project file where the task invocation is located. - /// The column in the project file where the task invocation is located. - /// The target name that invokes this task. - /// The AppDomainSetup that we want to use to launch our AppDomainIsolated tasks - /// Parameters that will be passed to the task when created - /// Task completion result showing success, failure or if there was a crash - internal OutOfProcTaskHostTaskResult ExecuteTask( - IBuildEngine oopTaskHostNode, - string taskName, - string taskLocation, - string taskFile, - int taskLine, - int taskColumn, - string targetName, - string projectFile, - AppDomainSetup appDomainSetup, - IDictionary taskParams) - { - buildEngine = oopTaskHostNode; - this.taskName = taskName; + return InstantiateAndExecuteTask( + buildEngine, + taskType, + taskName, + taskLocation, + taskFile, + taskLine, + taskColumn, + appDomainSetup, + taskParameters); + } - _taskAppDomain = null; - wrappedTask = null; + /// + /// This is responsible for cleaning up the task after the OutOfProcTaskHostNode has gathered everything it needs from this execution + /// For example: We will need to hold on new AppDomains created until we finish getting all outputs from the task + /// Add any other cleanup tasks here. Any method calling ExecuteTask must remember to call CleanupTask. + /// + public void Dispose() + { + if (_taskAppDomain != null) + { + AppDomain.Unload(_taskAppDomain); + } - LoadedType taskType = null; - try - { - TypeLoader typeLoader = new(TaskLoader.IsTaskClass); - taskType = typeLoader.Load( - taskName, - taskLocation, - logWarning: (format, args) => { }, - useTaskHost: false, - taskHostParamsMatchCurrentProc: true); - } - catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) - { - // If it's a TargetInvocationException, we only care about the contents of the inner exception, - // so just save that instead. - Exception exceptionToReturn = e is TargetInvocationException ? e.InnerException : e; + TaskLoader.RemoveAssemblyResolver(); + } - return new OutOfProcTaskHostTaskResult( - TaskCompleteType.CrashedDuringInitialization, - exceptionToReturn, - "TaskInstantiationFailureError", - [taskName, taskLocation, string.Empty]); - } + /// + /// Do the work of actually instantiating and running the task. + /// + private OutOfProcTaskHostTaskResult InstantiateAndExecuteTask( + IBuildEngine buildEngine, + LoadedType taskType, + string taskName, + string taskLocation, + string taskFile, + int taskLine, + int taskColumn, + AppDomainSetup appDomainSetup, + Dictionary taskParameters) + { + _taskAppDomain = null; + ITask wrappedTask; - OutOfProcTaskHostTaskResult taskResult = InstantiateAndExecuteTask( - oopTaskHostNode, + try + { + wrappedTask = TaskLoader.CreateTask( taskType, taskName, - taskLocation, taskFile, taskLine, taskColumn, - targetName, - projectFile, + LogErrorDelegate, appDomainSetup, - taskParams); + appDomainCreated: null, // custom app domain assembly loading won't be available for task host + isOutOfProc: true, + out _taskAppDomain)!; - return taskResult; + wrappedTask.BuildEngine = buildEngine; } - - /// - /// This is responsible for cleaning up the task after the OutOfProcTaskHostNode has gathered everything it needs from this execution - /// For example: We will need to hold on new AppDomains created until we finish getting all outputs from the task - /// Add any other cleanup tasks here. Any method calling ExecuteTask must remember to call CleanupTask. - /// - internal void CleanupTask() + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) { - if (_taskAppDomain != null) - { - AppDomain.Unload(_taskAppDomain); - } + return OutOfProcTaskHostTaskResult.CrashedDuringInitialization( + GetRelevantException(e), + resourceId: "TaskInstantiationFailureError", + resourceArgs: [taskName, taskLocation, string.Empty]); + } - TaskLoader.RemoveAssemblyResolver(); - wrappedTask = null; + if (TryAssignInputs(wrappedTask, taskName, taskParameters) is { } result) + { + return result; } - /// - /// Do the work of actually instantiating and running the task. - /// - private OutOfProcTaskHostTaskResult InstantiateAndExecuteTask( - IBuildEngine oopTaskHostNode, - LoadedType taskType, - string taskName, - string taskLocation, - string taskFile, - int taskLine, - int taskColumn, - string targetName, - string projectFile, - AppDomainSetup appDomainSetup, - IDictionary taskParams) + bool success = false; + try + { + // If it didn't crash and return before now, we're clear to go ahead and execute here. + success = wrappedTask.Execute(); + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) { - _taskAppDomain = null; - wrappedTask = null; + return OutOfProcTaskHostTaskResult.CrashedDuringExecution(e); + } - try - { -#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter - wrappedTask = TaskLoader.CreateTask( - taskType, - taskName, - taskFile, - taskLine, - taskColumn, - new TaskLoader.LogError(LogErrorDelegate), - appDomainSetup, - // custom app domain assembly loading won't be available for task host - null, - true, /* always out of proc */ - out _taskAppDomain); -#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter + Dictionary? finalParameterValues = CollectOutputs(wrappedTask); - wrappedTask.BuildEngine = oopTaskHostNode; - } - catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) - { - Exception exceptionToReturn = e; + return success + ? OutOfProcTaskHostTaskResult.Success(finalParameterValues) + : OutOfProcTaskHostTaskResult.Failure(finalParameterValues); - // If it's a TargetInvocationException, we only care about the contents of the inner exception, - // so just save that instead. - if (e is TargetInvocationException) - { - exceptionToReturn = e.InnerException; - } + void LogErrorDelegate(string taskLocation, int taskLine, int taskColumn, string message, params object[] messageArgs) + { + BuildErrorEventArgs error = new( + subcategory: null, + code: null, + file: taskLocation, + lineNumber: taskLine, + columnNumber: taskColumn, + endLineNumber: 0, + endColumnNumber: 0, + message: ResourceUtilities.FormatString(AssemblyResources.GetString(message), messageArgs), + helpKeyword: null, + senderName: taskName); + + buildEngine.LogErrorEvent(error); + } + } - return new OutOfProcTaskHostTaskResult( - TaskCompleteType.CrashedDuringInitialization, - exceptionToReturn, - "TaskInstantiationFailureError", - [taskName, taskLocation, String.Empty]); - } + private static OutOfProcTaskHostTaskResult? TryAssignInputs( + ITask wrappedTask, string taskName, Dictionary taskParameters) + { + Type wrappedTaskType = wrappedTask.GetType(); - foreach (KeyValuePair param in taskParams) - { - try - { - PropertyInfo paramInfo = wrappedTask.GetType().GetProperty(param.Key, BindingFlags.Instance | BindingFlags.Public); - paramInfo.SetValue(wrappedTask, param.Value?.WrappedParameter, null); - } - catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) - { - return new OutOfProcTaskHostTaskResult( - TaskCompleteType.CrashedDuringInitialization, - // If it's a TargetInvocationException, we only care about the contents of the inner exception, so save that instead. - e is TargetInvocationException ? e.InnerException : e, - "InvalidTaskAttributeError", - [param.Key, param.Value.ToString(), taskName]); - } - } + foreach (KeyValuePair kvp in taskParameters) + { + string name = kvp.Key; + TaskParameter parameter = kvp.Value; - bool success = false; try { - if (CancelPending) - { - return new OutOfProcTaskHostTaskResult(TaskCompleteType.Failure); - } - - // If it didn't crash and return before now, we're clear to go ahead and execute here. - success = wrappedTask.Execute(); + PropertyInfo property = wrappedTaskType.GetProperty(name, PublicInstance); + property.SetValue(wrappedTask, parameter?.WrappedParameter, index: null); } catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) { - return new OutOfProcTaskHostTaskResult(TaskCompleteType.CrashedDuringExecution, e); + return OutOfProcTaskHostTaskResult.CrashedDuringInitialization( + GetRelevantException(e), + resourceId: "InvalidTaskAttributeError", + resourceArgs: [name, parameter?.ToString() ?? string.Empty, taskName]); } + } + + return null; + } + + private static Dictionary? CollectOutputs(ITask wrappedTask) + { + Type wrappedTaskType = wrappedTask.GetType(); - PropertyInfo[] finalPropertyValues = wrappedTask.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); + Dictionary? outputs = null; - IDictionary finalParameterValues = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (PropertyInfo value in finalPropertyValues) + foreach (PropertyInfo property in wrappedTaskType.GetProperties(PublicInstance)) + { + // only record outputs + if (property.GetCustomAttributes(typeof(OutputAttribute), inherit: true).Length > 0) { - // only record outputs - if (value.GetCustomAttributes(typeof(OutputAttribute), true).Length > 0) + outputs ??= new(StringComparer.OrdinalIgnoreCase); + + try { - try - { - finalParameterValues[value.Name] = value.GetValue(wrappedTask, null); - } - catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) - { - // If it's not a critical exception, we assume there's some sort of problem in the parameter getter -- - // so save the exception, and we'll re-throw once we're back on the main node side of the - // communications pipe. - finalParameterValues[value.Name] = e; - } + outputs[property.Name] = property.GetValue(wrappedTask, index: null); + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + // If it's not a critical exception, we assume there's some sort of problem in the property getter. + // So, save the exception and we'll re-throw once we're back on the main node side of the + // communications pipe. + outputs[property.Name] = e; } } - - return new OutOfProcTaskHostTaskResult(success ? TaskCompleteType.Success : TaskCompleteType.Failure, finalParameterValues); } - /// - /// Logs errors from TaskLoader - /// - private void LogErrorDelegate(string taskLocation, int taskLine, int taskColumn, string message, params object[] messageArgs) - { - buildEngine.LogErrorEvent(new BuildErrorEventArgs( - null, - null, - taskLocation, - taskLine, - taskColumn, - 0, - 0, - ResourceUtilities.FormatString(AssemblyResources.GetString(message), messageArgs), - null, - taskName)); - } + return outputs; } + + private static Exception GetRelevantException(Exception e) + => e is TargetInvocationException ? e.InnerException : e; } diff --git a/src/MSBuildTaskHost/OutOfProcTaskHostTaskResult.cs b/src/MSBuildTaskHost/OutOfProcTaskHostTaskResult.cs index 25c00061733..6b1a3faed9f 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskHostTaskResult.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskHostTaskResult.cs @@ -3,134 +3,73 @@ using System; using System.Collections.Generic; - using Microsoft.Build.BackEnd; -#nullable disable +namespace Microsoft.Build.Shared; -namespace Microsoft.Build.Shared +/// +/// A result of executing a target or task. +/// +internal class OutOfProcTaskHostTaskResult { /// - /// A result of executing a target or task. + /// The overall result of the task execution. /// - internal class OutOfProcTaskHostTaskResult - { - /// - /// Constructor - /// - internal OutOfProcTaskHostTaskResult(TaskCompleteType result) - : this(result, null /* no final parameters */, null /* no exception */, null /* no exception message */, null /* and no args to go with it */) - { - // do nothing else - } + public TaskCompleteType Result { get; } - /// - /// Constructor - /// - internal OutOfProcTaskHostTaskResult(TaskCompleteType result, IDictionary finalParams) - : this(result, finalParams, null /* no exception */, null /* no exception message */, null /* and no args to go with it */) - { - // do nothing else - } - - /// - /// Constructor - /// - internal OutOfProcTaskHostTaskResult(TaskCompleteType result, Exception taskException) - : this(result, taskException, null /* no exception message */, null /* and no args to go with it */) - { - // do nothing else - } + /// + /// Dictionary of the final values of the task parameters. + /// + public Dictionary? FinalParameterValues { get; } - /// - /// Constructor - /// - internal OutOfProcTaskHostTaskResult(TaskCompleteType result, Exception taskException, string exceptionMessage, string[] exceptionMessageArgs) - : this(result, null /* no final parameters */, taskException, exceptionMessage, exceptionMessageArgs) - { - // do nothing else - } + /// + /// The exception thrown by the task during initialization or execution, if any. + /// + public Exception? TaskException { get; } - /// - /// Constructor - /// - internal OutOfProcTaskHostTaskResult(TaskCompleteType result, IDictionary finalParams, Exception taskException, string exceptionMessage, string[] exceptionMessageArgs) - { - // If we're returning a crashing result, we should always also be returning the exception that caused the crash, although - // we may not always be returning an accompanying message. - if (result == TaskCompleteType.CrashedDuringInitialization || - result == TaskCompleteType.CrashedDuringExecution || - result == TaskCompleteType.CrashedAfterExecution) - { - ErrorUtilities.VerifyThrowInternalNull(taskException); - } + /// + /// The name of the resource representing the message to be logged along with the + /// above exception. + /// + public string? ExceptionMessage { get; } - if (exceptionMessage != null) - { - ErrorUtilities.VerifyThrow( - result == TaskCompleteType.CrashedDuringInitialization || - result == TaskCompleteType.CrashedDuringExecution || - result == TaskCompleteType.CrashedAfterExecution, - "If we have an exception message, the result type should be 'crashed' of some variety."); - } + /// + /// The arguments to be used when formatting ExceptionMessage. + /// + public string[]? ExceptionMessageArgs { get; } - if (exceptionMessageArgs?.Length > 0) - { - ErrorUtilities.VerifyThrow(exceptionMessage != null, "If we have message args, we need a message."); - } + private OutOfProcTaskHostTaskResult( + TaskCompleteType result, + Dictionary? finalParameterValues) + { + Result = result; + FinalParameterValues = finalParameterValues; + } - Result = result; - FinalParameterValues = finalParams; - TaskException = taskException; - ExceptionMessage = exceptionMessage; - ExceptionMessageArgs = exceptionMessageArgs; - } + private OutOfProcTaskHostTaskResult( + TaskCompleteType result, + Exception? taskException = null, + string? exceptionMessage = null, + string[]? exceptionMessageArgs = null) + { + Result = result; + TaskException = taskException; + ExceptionMessage = exceptionMessage; + ExceptionMessageArgs = exceptionMessageArgs; + } - /// - /// The overall result of the task execution. - /// - public TaskCompleteType Result - { - get; - private set; - } + public static OutOfProcTaskHostTaskResult Success(Dictionary? finalParameterValues) + => new(TaskCompleteType.Success, finalParameterValues); - /// - /// Dictionary of the final values of the task parameters - /// - public IDictionary FinalParameterValues - { - get; - private set; - } + public static OutOfProcTaskHostTaskResult Failure(Dictionary? finalParameterValues = null) + => new(TaskCompleteType.Failure, finalParameterValues); - /// - /// The exception thrown by the task during initialization or execution, - /// if any. - /// - public Exception TaskException - { - get; - private set; - } + public static OutOfProcTaskHostTaskResult CrashedAfterExecution(Exception e) + => new(TaskCompleteType.CrashedAfterExecution, e); - /// - /// The name of the resource representing the message to be logged along with the - /// above exception. - /// - public string ExceptionMessage - { - get; - private set; - } + public static OutOfProcTaskHostTaskResult CrashedDuringExecution(Exception e) + => new(TaskCompleteType.CrashedDuringExecution, e); - /// - /// The arguments to be used when formatting ExceptionMessage - /// - public string[] ExceptionMessageArgs - { - get; - private set; - } - } + public static OutOfProcTaskHostTaskResult CrashedDuringInitialization(Exception e, string resourceId, string[] resourceArgs) + => new(TaskCompleteType.CrashedDuringInitialization, e, resourceId, resourceArgs); } diff --git a/src/MSBuildTaskHost/TaskLoader.cs b/src/MSBuildTaskHost/TaskLoader.cs index 0af02f5cb74..a29a34150ae 100644 --- a/src/MSBuildTaskHost/TaskLoader.cs +++ b/src/MSBuildTaskHost/TaskLoader.cs @@ -45,7 +45,7 @@ internal static bool IsTaskClass(Type type, object unused) int taskColumn, LogError logError, AppDomainSetup appDomainSetup, - Action appDomainCreated, + Action? appDomainCreated, bool isOutOfProc, out AppDomain? taskAppDomain) { From c2cdf0b1bcf8a0eb89087d20a83fcf8a17b07ab4 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 4 Feb 2026 11:13:56 -0800 Subject: [PATCH 085/136] MSBuildTaskHost: Remove BuildEnviromentHelper.TryFromVisualStudioProcess The MSBuildTaskHost always runs as a stand-along process. So, the current process can't ever be a Visual Studio process. --- src/MSBuildTaskHost/BuildEnvironmentHelper.cs | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs index 46e3265f7f4..baab726a03c 100644 --- a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs +++ b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs @@ -19,12 +19,6 @@ internal sealed class BuildEnvironmentHelper // MSBuildConstants.CurrentToolsVersion private const string CurrentToolsVersion = "Current"; - /// - /// Name of the Visual Studio (and Blend) process. - /// VS ASP intellisense server fails without Microsoft.VisualStudio.Web.Host. Remove when issue fixed: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/574986 - /// - private static readonly string[] s_visualStudioProcess = { "DEVENV", "BLEND", "Microsoft.VisualStudio.Web.Host" }; - /// /// Name of the MSBuild process(es) /// @@ -71,7 +65,6 @@ private static BuildEnvironment Initialize() var possibleLocations = new Func[] { TryFromEnvironmentVariable, - TryFromVisualStudioProcess, TryFromMSBuildProcess, TryFromMSBuildAssembly, TryFromDevConsole @@ -104,20 +97,6 @@ private static BuildEnvironment TryFromEnvironmentVariable() : TryFromMSBuildExeUnderVisualStudio(msBuildExePath, allowLegacyToolsVersion: true) ?? TryFromStandaloneMSBuildExe(msBuildExePath); } - private static BuildEnvironment TryFromVisualStudioProcess() - { - var vsProcess = GetProcessFromRunningProcess(); - if (!IsProcessInList(vsProcess, s_visualStudioProcess)) - { - return null; - } - - var vsRoot = FileUtilities.GetFolderAbove(vsProcess, 3); - string msBuildExe = GetMSBuildExeFromVsRoot(vsRoot); - - return new BuildEnvironment(BuildEnvironmentMode.VisualStudio, msBuildExe); - } - private static BuildEnvironment TryFromMSBuildProcess() { var msBuildExe = GetProcessFromRunningProcess(); From d868eba8830b87bd659f15dec95a41893d6b9028 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 4 Feb 2026 11:35:48 -0800 Subject: [PATCH 086/136] MSBuildTaskHost: Avoid BuildEnvironmentHelper in OutOfProcTaskHostNode On shutdown, set the current directory to the MSBuildTaskHost's directory rather than the computed CurrentMSBuildToolsDirectory. --- src/MSBuildTaskHost/FileUtilities.cs | 2 ++ src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/MSBuildTaskHost/FileUtilities.cs b/src/MSBuildTaskHost/FileUtilities.cs index a318f3aca68..695bebb2e13 100644 --- a/src/MSBuildTaskHost/FileUtilities.cs +++ b/src/MSBuildTaskHost/FileUtilities.cs @@ -148,6 +148,8 @@ private static string GetDirectory(string fileSpec) /// internal static string ExecutingAssemblyPath => Path.GetFullPath(typeof(FileUtilities).Assembly.Location); + public static string ExecutingAssemblyDirectory => Path.GetDirectoryName(ExecutingAssemblyPath); + /// /// Determines the full path for the given file-spec. /// ASSUMES INPUT IS STILL ESCAPED diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs index 815dfbf0ee0..b02e24172d8 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs @@ -575,7 +575,7 @@ private NodeEngineShutdownReason HandleShutdown() // On Windows, a process holds a handle to the current directory, // so reset it away from a user-requested folder that may get deleted. - NativeMethodsShared.SetCurrentDirectory(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory); + NativeMethodsShared.SetCurrentDirectory(FileUtilities.ExecutingAssemblyDirectory); // Restore the original environment, best effort. try From 0d8a66f2f4c9772d11212ac2c60be1efcf5fee1b Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 4 Feb 2026 11:54:15 -0800 Subject: [PATCH 087/136] MSBuildTaskHost: Remove uncalled members from CommunicationUtilities In particular, code paths targeting a .NET task host can be removed. Also, clean up a bit. --- .../CommunicationsUtilities.cs | 146 +++--------------- .../NodeEndpointOutOfProcTaskHost.cs | 2 +- 2 files changed, 25 insertions(+), 123 deletions(-) diff --git a/src/MSBuildTaskHost/CommunicationsUtilities.cs b/src/MSBuildTaskHost/CommunicationsUtilities.cs index d6953b7dc2a..e87e1904827 100644 --- a/src/MSBuildTaskHost/CommunicationsUtilities.cs +++ b/src/MSBuildTaskHost/CommunicationsUtilities.cs @@ -9,9 +9,7 @@ using System.IO.Pipes; using System.Reflection; using System.Runtime.InteropServices; -using System.Security.Cryptography; using System.Security.Principal; -using System.Text; using System.Threading; using Microsoft.Build.BackEnd; using Microsoft.Build.Framework; @@ -75,19 +73,6 @@ internal enum HandshakeOptions SidecarTaskHost = 256, } - /// - /// Represents a unique key for identifying task host nodes. - /// Combines HandshakeOptions (which specify runtime/architecture configuration) with - /// the scheduled node ID to uniquely identify task hosts in multi-threaded mode. - /// - /// The handshake options specifying runtime and architecture configuration. - /// - /// The scheduled node ID. In traditional multi-proc builds, this is -1 (meaning the task host - /// is identified by HandshakeOptions alone). In multi-threaded mode, each in-proc node has - /// its own task host, so the node ID is used to distinguish them. - /// - internal readonly record struct TaskHostNodeKey(HandshakeOptions HandshakeOptions, int NodeId); - /// /// Status codes for the handshake process. /// It aggregates return values across several functions so we use an aggregate instead of a separate class for each method. @@ -185,7 +170,8 @@ private HandshakeResult(HandshakeStatus status, int value, string errorMessage, /// The value returned from the handshake operation. /// The packet version received from the child node. /// A new instance representing a successful operation. - public static HandshakeResult Success(int value = 0, byte negotiatedPacketVersion = 1) => new(HandshakeStatus.Success, value, null, negotiatedPacketVersion); + public static HandshakeResult Success(int value = 0, byte negotiatedPacketVersion = 1) + => new(HandshakeStatus.Success, value, null, negotiatedPacketVersion); /// /// Creates a failed handshake result with the specified status and error message. @@ -193,20 +179,18 @@ private HandshakeResult(HandshakeStatus status, int value, string errorMessage, /// The error status code for the failure. /// A description of the error that occurred. /// A new instance representing a failed operation. - public static HandshakeResult Failure(HandshakeStatus status, string errorMessage) => new(status, 0, errorMessage); + public static HandshakeResult Failure(HandshakeStatus status, string errorMessage) + => new(status, 0, errorMessage); } - internal class Handshake + internal sealed class Handshake { /// /// Marker indicating that the next integer in the child handshake response is the PacketVersion. /// public const int PacketVersionFromChildMarker = -1; - // The number is selected as an arbitrary value that is unlikely to conflict with any future sdk version. - public const int NetTaskHostHandshakeVersion = 99; - - protected readonly HandshakeComponents _handshakeComponents; + private readonly HandshakeComponents _handshakeComponents; /// /// Initializes a new instance of the class with the specified node type @@ -222,30 +206,30 @@ internal class Handshake /// For non-.NET TaskHost nodes or on .NET Core, the MSBuildToolsDirectoryRoot is used instead. /// This parameter is ignored when not running .NET TaskHost on .NET Framework. /// - internal Handshake(HandshakeOptions nodeType, string predefinedToolsDirectory = null) + public Handshake(HandshakeOptions nodeType, string predefinedToolsDirectory = null) : this(nodeType, includeSessionId: true, predefinedToolsDirectory) { } // Helper method to validate handshake option presence - internal static bool IsHandshakeOptionEnabled(HandshakeOptions hostContext, HandshakeOptions option) => (hostContext & option) == option; + internal static bool IsHandshakeOptionEnabled(HandshakeOptions hostContext, HandshakeOptions option) + => (hostContext & option) == option; // Source options of the handshake. internal HandshakeOptions HandshakeOptions { get; } - protected Handshake(HandshakeOptions nodeType, bool includeSessionId, string predefinedToolsDirectory) + private Handshake(HandshakeOptions nodeType, bool includeSessionId, string predefinedToolsDirectory) { HandshakeOptions = nodeType; // Build handshake options with version in upper bits - const int handshakeVersion = (int)CommunicationsUtilities.handshakeVersion; + const int handshakeVersion = CommunicationsUtilities.HandshakeVersion; var options = (int)nodeType | (handshakeVersion << 24); CommunicationsUtilities.Trace("Building handshake for node type {0}, (version {1}): options {2}.", nodeType, handshakeVersion, options); // Calculate salt from environment and tools directory - bool isNetTaskHost = IsHandshakeOptionEnabled(nodeType, HandshakeOptions.NET | HandshakeOptions.TaskHost); string handshakeSalt = Environment.GetEnvironmentVariable("MSBUILDNODEHANDSHAKESALT") ?? ""; - string toolsDirectory = GetToolsDirectory(isNetTaskHost, predefinedToolsDirectory); + string toolsDirectory = BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot; int salt = CommunicationsUtilities.GetHashCode($"{handshakeSalt}{toolsDirectory}"); CommunicationsUtilities.Trace("Handshake salt is {0}", handshakeSalt); @@ -259,25 +243,9 @@ protected Handshake(HandshakeOptions nodeType, bool includeSessionId, string pre sessionId = currentProcess.SessionId; } - _handshakeComponents = isNetTaskHost - ? CreateNetTaskHostComponents(options, salt, sessionId) - : CreateStandardComponents(options, salt, sessionId); + _handshakeComponents = CreateStandardComponents(options, salt, sessionId); } - private string GetToolsDirectory(bool isNetTaskHost, string predefinedToolsDirectory) => - isNetTaskHost // For .NET TaskHost assembly directory we set the expectation for the child dotnet process to connect to. - ? predefinedToolsDirectory - : BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot; - - private static HandshakeComponents CreateNetTaskHostComponents(int options, int salt, int sessionId) => new( - options, - salt, - NetTaskHostHandshakeVersion, - NetTaskHostHandshakeVersion, - NetTaskHostHandshakeVersion, - NetTaskHostHandshakeVersion, - sessionId); - private static HandshakeComponents CreateStandardComponents(int options, int salt, int sessionId) { var fileVersion = new Version(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion); @@ -292,7 +260,7 @@ private static HandshakeComponents CreateStandardComponents(int options, int sal sessionId); } - public virtual HandshakeComponents RetrieveHandshakeComponents() => new HandshakeComponents( + public HandshakeComponents RetrieveHandshakeComponents() => new( CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.Options), CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.Salt), CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionMajor), @@ -300,54 +268,6 @@ private static HandshakeComponents CreateStandardComponents(int options, int sal CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionBuild), CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionPrivate), CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.SessionId)); - - public virtual string GetKey() => $"{_handshakeComponents.Options} {_handshakeComponents.Salt} {_handshakeComponents.FileVersionMajor} {_handshakeComponents.FileVersionMinor} {_handshakeComponents.FileVersionBuild} {_handshakeComponents.FileVersionPrivate} {_handshakeComponents.SessionId}".ToString(CultureInfo.InvariantCulture); - - public virtual byte? ExpectedVersionInFirstByte => CommunicationsUtilities.handshakeVersion; - } - - internal sealed class ServerNodeHandshake : Handshake - { - /// - /// Caching computed hash. - /// - private string _computedHash = null; - - public override byte? ExpectedVersionInFirstByte => null; - - internal ServerNodeHandshake(HandshakeOptions nodeType) - : base(nodeType, includeSessionId: false, predefinedToolsDirectory: null) - { - } - - public override HandshakeComponents RetrieveHandshakeComponents() => new HandshakeComponents( - CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.Options), - CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.Salt), - CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionMajor), - CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionMinor), - CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionBuild), - CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionPrivate)); - - public override string GetKey() => $"{_handshakeComponents.Options} {_handshakeComponents.Salt} {_handshakeComponents.FileVersionMajor} {_handshakeComponents.FileVersionMinor} {_handshakeComponents.FileVersionBuild} {_handshakeComponents.FileVersionPrivate}" - .ToString(CultureInfo.InvariantCulture); - - /// - /// Computes Handshake stable hash string representing whole state of handshake. - /// - public string ComputeHash() - { - if (_computedHash == null) - { - var input = GetKey(); - byte[] utf8 = Encoding.UTF8.GetBytes(input); - using var sha = SHA256.Create(); - var bytes = sha.ComputeHash(utf8); - _computedHash = Convert.ToBase64String(bytes) - .Replace("/", "_") - .Replace("=", string.Empty); - } - return _computedHash; - } } /// @@ -363,7 +283,7 @@ internal static class CommunicationsUtilities /// /// The version of the handshake. This should be updated each time the handshake structure is altered. /// - internal const byte handshakeVersion = 0x01; + internal const byte HandshakeVersion = 0x01; /// /// The timeout to connect to a node. @@ -390,18 +310,6 @@ internal static class CommunicationsUtilities /// private static long s_lastLoggedTicks = DateTime.UtcNow.Ticks; - /// - /// Delegate to debug the communication utilities. - /// - internal delegate void LogDebugCommunications(string format, params object[] stuff); - - /// - /// On Windows, environment variables should be case-insensitive; - /// on Unix-like systems, they should be case-sensitive, but this might be a breaking change in an edge case. - /// https://github.com/dotnet/msbuild/issues/12858 - /// - internal static StringComparer EnvironmentVariableComparer => StringComparer.OrdinalIgnoreCase; - /// /// Gets or sets the node connection timeout. /// @@ -627,8 +535,8 @@ internal static bool TryReadEndOfHandshakeSignal( // We detected packet version marker, now let's read actual PacketVersion if (!stream.TryReadIntForHandshake( - byteToAccept: null, - out HandshakeResult versionResult)) + byteToAccept: null, + out HandshakeResult versionResult)) { result = versionResult; return false; @@ -673,7 +581,6 @@ private static HandshakeResult CreateVersionMismatchResult(bool isProvider, int return HandshakeResult.Failure(HandshakeStatus.VersionMismatch, errorMessage); } -#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter /// /// Extension method to read a series of bytes from a stream. /// If specified, leading byte matches one in the supplied array if any, returns rejection byte and throws IOException. @@ -681,9 +588,7 @@ private static HandshakeResult CreateVersionMismatchResult(bool isProvider, int internal static bool TryReadIntForHandshake( this PipeStream stream, byte? byteToAccept, - out HandshakeResult result - ) -#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter + out HandshakeResult result) { byte[] bytes = new byte[4]; int bytesRead = stream.Read(bytes, 0, bytes.Length); @@ -868,7 +773,7 @@ private static void TraceCore(int nodeId, string message) { s_debugDumpPath ??= Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); - if (String.IsNullOrEmpty(s_debugDumpPath)) + if (string.IsNullOrEmpty(s_debugDumpPath)) { s_debugDumpPath = FileUtilities.TempFileDirectory; } @@ -879,16 +784,13 @@ private static void TraceCore(int nodeId, string message) try { - string fileName = @"MSBuild_CommTrace_PID_{0}"; - if (nodeId != -1) - { - fileName += "_node_" + nodeId; - } + string fileName = nodeId != -1 + ? $"MSBuild_CommTrace_PID_{EnvironmentUtilities.CurrentProcessId}_node_{nodeId}.txt" + : $"MSBuild_CommTrace_PID_{EnvironmentUtilities.CurrentProcessId}.txt"; - fileName += ".txt"; + string filePath = Path.Combine(s_debugDumpPath, fileName); - using (StreamWriter file = FileUtilities.OpenWrite( - string.Format(CultureInfo.CurrentCulture, Path.Combine(s_debugDumpPath, fileName), EnvironmentUtilities.CurrentProcessId, nodeId), append: true)) + using (StreamWriter file = FileUtilities.OpenWrite(filePath, append: true)) { long now = DateTime.UtcNow.Ticks; float millisecondsSinceLastLog = (float)(now - s_lastLoggedTicks) / 10000L; diff --git a/src/MSBuildTaskHost/NodeEndpointOutOfProcTaskHost.cs b/src/MSBuildTaskHost/NodeEndpointOutOfProcTaskHost.cs index dd2cf431514..ebb853b2931 100644 --- a/src/MSBuildTaskHost/NodeEndpointOutOfProcTaskHost.cs +++ b/src/MSBuildTaskHost/NodeEndpointOutOfProcTaskHost.cs @@ -342,7 +342,7 @@ private void PacketPumpProc() { if (!_pipeServer.TryReadIntForHandshake( - byteToAccept: index == 0 ? (byte?)CommunicationsUtilities.handshakeVersion : null, /* this will disconnect a < 16.8 host; it expects leading 00 or F5 or 06. 0x00 is a wildcard */ + byteToAccept: index == 0 ? (byte?)CommunicationsUtilities.HandshakeVersion : null, /* this will disconnect a < 16.8 host; it expects leading 00 or F5 or 06. 0x00 is a wildcard */ out HandshakeResult result)) { CommunicationsUtilities.Trace($"Handshake failed with error: {result.ErrorMessage}"); From 53b7826fc5e86af1f751a9c34c1d77977d7f09e1 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 4 Feb 2026 14:21:59 -0800 Subject: [PATCH 088/136] Adjust Handshake salt calculation for MSBuildTaskHost Rather than having MSBuildTaskHost poke around to find a reasonable tools directory, just use the MSBuildTaskHost.exe directory. On the client-side, I've introduced TaskHostLaunchArgs to help organize the logic around computing arguments for the various task host launch scenarios. --- .../NodeProviderOutOfProcTaskHost.cs | 82 ++++--------- .../Communications/TaskHostLaunchArgs.cs | 108 ++++++++++++++++++ src/Build/Microsoft.Build.csproj | 1 + .../CommunicationsUtilities.cs | 25 +--- 4 files changed, 136 insertions(+), 80 deletions(-) create mode 100644 src/Build/BackEnd/Components/Communications/TaskHostLaunchArgs.cs diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs index 8364ea58a29..428fe039b4d 100644 --- a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs +++ b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs @@ -451,15 +451,16 @@ internal static string GetMSBuildExecutablePathForNonNETRuntimes(HandshakeOption bool isArm64 = Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.Arm64); bool isCLR2 = Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.CLR2); - // Unsupported combinations - if (isArm64 && isCLR2) - { - ErrorUtilities.ThrowInternalError("ARM64 CLR2 task hosts are not supported."); - } - if (isCLR2) { - return isX64 ? Path.Combine(GetOrInitializeX64Clr2Path(toolName), toolName) : Path.Combine(GetOrInitializeX32Clr2Path(toolName), toolName); + if (isArm64) + { + ErrorUtilities.ThrowInternalError("ARM64 CLR2 task hosts are not supported."); + } + + return isX64 + ? Path.Combine(GetOrInitializeX64Clr2Path(toolName), toolName) + : Path.Combine(GetOrInitializeX32Clr2Path(toolName), toolName); } if (isX64) @@ -667,69 +668,32 @@ internal bool CreateNode(TaskHostNodeKey nodeKey, INodePacketFactory factory, IN // Create callbacks that capture the TaskHostNodeKey void OnNodeContextCreated(NodeContext context) => NodeContextCreated(context, nodeKey); - IList nodeContexts; - - // Handle .NET task host context -#if NETFRAMEWORK - if (Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.NET)) + if (!TaskHostLaunchArgs.TryCreate(in taskHostParameters, ComponentHost.BuildParameters, hostContext, out TaskHostLaunchArgs launchArgs)) { - (string runtimeHostPath, string msbuildAssemblyPath) = GetMSBuildLocationForNETRuntime(hostContext, taskHostParameters); - - CommunicationsUtilities.Trace("For a host context of {0}, spawning dotnet.exe from {1}.", hostContext.ToString(), runtimeHostPath); - - var handshake = new Handshake(hostContext, predefinedToolsDirectory: msbuildAssemblyPath); - - string commandLineArgs = $"\"{Path.Combine(msbuildAssemblyPath, Constants.MSBuildAssemblyName)}\" /nologo {NodeModeHelper.ToCommandLineArgument(NodeMode.OutOfProcTaskHostNode)} /nodereuse:{NodeReuseIsEnabled(hostContext).ToString().ToLower()} /low:{ComponentHost.BuildParameters.LowPriority.ToString().ToLower()} /parentpacketversion:{NodePacketTypeExtensions.PacketVersion}"; - - // There is always one task host per host context so we always create just 1 one task host node here. - nodeContexts = GetNodes( - runtimeHostPath, - commandLineArgs, - communicationNodeId, - this, - handshake, - OnNodeContextCreated, - NodeContextTerminated, - 1); - - return nodeContexts.Count == 1; + return false; } -#endif - string msbuildLocation = GetMSBuildExecutablePathForNonNETRuntimes(hostContext); - - // we couldn't even figure out the location we're trying to launch ... just go ahead and fail. - if (msbuildLocation == null) + if (launchArgs.UsingDotNetExe) { - return false; + CommunicationsUtilities.Trace("For a host context of {0}, spawning dotnet.exe from {1}.", hostContext.ToString(), launchArgs.ExePath); + } + else + { + CommunicationsUtilities.Trace("For a host context of {0}, spawning executable from {1}.", hostContext.ToString(), launchArgs.ExePath); } - CommunicationsUtilities.Trace("For a host context of {0}, spawning executable from {1}.", hostContext.ToString(), msbuildLocation ?? Constants.MSBuildExecutableName); - - string nonNetCommandLineArgs = $"/nologo {NodeModeHelper.ToCommandLineArgument(NodeMode.OutOfProcTaskHostNode)} /nodereuse:{NodeReuseIsEnabled(hostContext).ToString().ToLower()} /low:{ComponentHost.BuildParameters.LowPriority.ToString().ToLower()} /parentpacketversion:{NodePacketTypeExtensions.PacketVersion}"; - - nodeContexts = GetNodes( - msbuildLocation, - nonNetCommandLineArgs, + // There is always one task host per host context so we always create just 1 one task host node here. + IList nodeContexts = GetNodes( + launchArgs.ExePath, + launchArgs.CommandLineArgs, communicationNodeId, - this, - new Handshake(hostContext), + factory: this, + launchArgs.Handshake, OnNodeContextCreated, NodeContextTerminated, - 1); + numberOfNodesToCreate: 1); return nodeContexts.Count == 1; - - // Determines whether node reuse should be enabled for the given host context. - // Node reuse allows MSBuild to reuse existing task host processes for better performance, - // but is disabled for CLR2 because it uses legacy MSBuildTaskHost. - bool NodeReuseIsEnabled(HandshakeOptions hostContext) - { - bool isCLR2 = Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.CLR2); - - return Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.NodeReuse) - && !isCLR2; - } } /// diff --git a/src/Build/BackEnd/Components/Communications/TaskHostLaunchArgs.cs b/src/Build/BackEnd/Components/Communications/TaskHostLaunchArgs.cs new file mode 100644 index 00000000000..6047e8bfd37 --- /dev/null +++ b/src/Build/BackEnd/Components/Communications/TaskHostLaunchArgs.cs @@ -0,0 +1,108 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.Build.Execution; +using Microsoft.Build.Framework; +using Microsoft.Build.Internal; + +#if NET +#else +using Microsoft.IO; +#endif + +namespace Microsoft.Build.BackEnd; + +internal sealed class TaskHostLaunchArgs +{ + public string ExePath { get; } + + public string CommandLineArgs { get; } + + public Handshake Handshake { get; } + + public bool UsingDotNetExe { get; } + + private TaskHostLaunchArgs( + string exePath, + string commandLineArgs, + Handshake handshake, + bool usingDotNetExe = false) + { + ExePath = exePath; + CommandLineArgs = commandLineArgs; + Handshake = handshake; + UsingDotNetExe = usingDotNetExe; + } + + public static bool TryCreate( + ref readonly TaskHostParameters taskHostParameters, + BuildParameters buildParameters, + HandshakeOptions hostContext, + [NotNullWhen(true)] out TaskHostLaunchArgs? result) + { + string msbuildLocation; + string commandLineArgs; + Handshake handshake; + bool nodeReuse; + +#if NETFRAMEWORK + + // Handle scenario where a .NET task host is launched from .NET Framework + if (Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.NET)) + { + (string runtimeHostPath, string msbuildAssemblyDirectory) = NodeProviderOutOfProcTaskHost.GetMSBuildLocationForNETRuntime(hostContext, taskHostParameters); + + msbuildLocation = Path.Combine(msbuildAssemblyDirectory, Constants.MSBuildAssemblyName); + nodeReuse = Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.NodeReuse); + + commandLineArgs = $""" + "{msbuildLocation}" /nologo {NodeModeHelper.ToCommandLineArgument(NodeMode.OutOfProcTaskHostNode)} /nodereuse:{nodeReuse.ToString().ToLower()} /low:{buildParameters.LowPriority.ToString().ToLower()} /parentpacketversion:{NodePacketTypeExtensions.PacketVersion} + """; + + handshake = new Handshake(hostContext, predefinedToolsDirectory: msbuildAssemblyDirectory); + + result = new TaskHostLaunchArgs(runtimeHostPath, commandLineArgs, handshake, usingDotNetExe: true); + return true; + } +#endif + + msbuildLocation = NodeProviderOutOfProcTaskHost.GetMSBuildExecutablePathForNonNETRuntimes(hostContext); + + // we couldn't even figure out the location we're trying to launch ... just go ahead and fail. + if (msbuildLocation == null) + { + result = null; + return false; + } + +#if FEATURE_NET35_TASKHOST + + if (Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.CLR2)) + { + // The .NET 3.5 task host uses the directory of its EXE when calculating salt for the handshake. + string toolsDirectory = Path.GetDirectoryName(msbuildLocation) ?? string.Empty; + + // MSBuildTaskHost doesn't use command-line arguments. + commandLineArgs = ""; + handshake = new Handshake(hostContext, toolsDirectory); + + result = new TaskHostLaunchArgs(msbuildLocation, commandLineArgs, handshake); + return true; + } +#endif + + nodeReuse = Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.NodeReuse); + + // If the runtime host path is null it means we don't have MSBuild.dll path resolved and + // there is no need to include it in the command line arguments. + commandLineArgs = $""" + "" /nologo {NodeModeHelper.ToCommandLineArgument(NodeMode.OutOfProcTaskHostNode)} /nodereuse:{nodeReuse.ToString().ToLower()} /low:{buildParameters.LowPriority.ToString().ToLower()} /parentpacketversion:{NodePacketTypeExtensions.PacketVersion} + """; + + handshake = new Handshake(hostContext); + + result = new TaskHostLaunchArgs(msbuildLocation, commandLineArgs, handshake); + return true; + } +} diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 157bc6fe2bb..e73cd6c6cc6 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -176,6 +176,7 @@ + diff --git a/src/MSBuildTaskHost/CommunicationsUtilities.cs b/src/MSBuildTaskHost/CommunicationsUtilities.cs index e87e1904827..2f0f181cd97 100644 --- a/src/MSBuildTaskHost/CommunicationsUtilities.cs +++ b/src/MSBuildTaskHost/CommunicationsUtilities.cs @@ -192,25 +192,6 @@ internal sealed class Handshake private readonly HandshakeComponents _handshakeComponents; - /// - /// Initializes a new instance of the class with the specified node type - /// and optional predefined tools directory. - /// - /// - /// The that specifies the type of node and configuration options for the handshake operation. - /// - /// - /// An optional directory path used for .NET TaskHost handshake salt calculation (only on .NET Framework). - /// When specified for .NET TaskHost nodes, this directory path is included in the handshake salt - /// to ensure the child dotnet process connects with the expected tools directory context. - /// For non-.NET TaskHost nodes or on .NET Core, the MSBuildToolsDirectoryRoot is used instead. - /// This parameter is ignored when not running .NET TaskHost on .NET Framework. - /// - public Handshake(HandshakeOptions nodeType, string predefinedToolsDirectory = null) - : this(nodeType, includeSessionId: true, predefinedToolsDirectory) - { - } - // Helper method to validate handshake option presence internal static bool IsHandshakeOptionEnabled(HandshakeOptions hostContext, HandshakeOptions option) => (hostContext & option) == option; @@ -218,7 +199,7 @@ internal static bool IsHandshakeOptionEnabled(HandshakeOptions hostContext, Hand // Source options of the handshake. internal HandshakeOptions HandshakeOptions { get; } - private Handshake(HandshakeOptions nodeType, bool includeSessionId, string predefinedToolsDirectory) + public Handshake(HandshakeOptions nodeType, bool includeSessionId = true) { HandshakeOptions = nodeType; @@ -227,9 +208,11 @@ private Handshake(HandshakeOptions nodeType, bool includeSessionId, string prede var options = (int)nodeType | (handshakeVersion << 24); CommunicationsUtilities.Trace("Building handshake for node type {0}, (version {1}): options {2}.", nodeType, handshakeVersion, options); + // Tools directory is the path of MSBuildTaskHost.exe + string toolsDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + // Calculate salt from environment and tools directory string handshakeSalt = Environment.GetEnvironmentVariable("MSBUILDNODEHANDSHAKESALT") ?? ""; - string toolsDirectory = BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot; int salt = CommunicationsUtilities.GetHashCode($"{handshakeSalt}{toolsDirectory}"); CommunicationsUtilities.Trace("Handshake salt is {0}", handshakeSalt); From 471568cb120b74d33f4237bfb1306eb85daf5d1a Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 4 Feb 2026 14:30:42 -0800 Subject: [PATCH 089/136] MSBuildTaskHost: Remove BuildEnvironmentHelper Now that the tools directory used for handshake salt by MSBuildTaskHost is determinstic, BuildEnvironmentHelper is no longer needed. --- src/MSBuildTaskHost/BuildEnvironmentHelper.cs | 355 ------------------ src/MSBuildTaskHost/MSBuildTaskHost.csproj | 1 - 2 files changed, 356 deletions(-) delete mode 100644 src/MSBuildTaskHost/BuildEnvironmentHelper.cs diff --git a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs b/src/MSBuildTaskHost/BuildEnvironmentHelper.cs deleted file mode 100644 index baab726a03c..00000000000 --- a/src/MSBuildTaskHost/BuildEnvironmentHelper.cs +++ /dev/null @@ -1,355 +0,0 @@ -// 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.IO; -using System.Linq; -using System.Text.RegularExpressions; - -#nullable disable - -namespace Microsoft.Build.Shared -{ - internal sealed class BuildEnvironmentHelper - { - // Since this class is added as 'link' to shared source in multiple projects, - // MSBuildConstants.CurrentVisualStudioVersion is not available in all of them. - private const string CurrentVisualStudioVersion = "18.0"; - - // MSBuildConstants.CurrentToolsVersion - private const string CurrentToolsVersion = "Current"; - - /// - /// Name of the MSBuild process(es) - /// - private static readonly string[] s_msBuildProcess = { "MSBUILD", "MSBUILDTASKHOST" }; - - /// - /// Gets the cached Build Environment instance. - /// - public static BuildEnvironment Instance - { - get - { - try - { - return BuildEnvironmentHelperSingleton.s_instance; - } - catch (TypeInitializationException e) - { - if (e.InnerException != null) - { - // Throw the error that caused the TypeInitializationException. - // (likely InvalidOperationException) - throw e.InnerException; - } - - throw; - } - } - } - - /// - /// Find the location of MSBuild.exe based on the current environment. - /// - /// - /// This defines the order and precedence for various methods of discovering MSBuild and associated toolsets. - /// At a high level, an install under Visual Studio is preferred as the user may have SDKs installed to a - /// specific instance of Visual Studio and build will only succeed if we can discover those. See - /// https://github.com/dotnet/msbuild/issues/1461 for details. - /// - /// Build environment. - private static BuildEnvironment Initialize() - { - // See https://github.com/dotnet/msbuild/issues/1461 for specification of ordering and details. - var possibleLocations = new Func[] - { - TryFromEnvironmentVariable, - TryFromMSBuildProcess, - TryFromMSBuildAssembly, - TryFromDevConsole - }; - - foreach (var location in possibleLocations) - { - var env = location(); - if (env != null) - { - return env; - } - } - - // If we can't find a suitable environment, continue in the 'None' mode. - // We will use the current running process for the CurrentMSBuildExePath value. This is likely - // wrong, but many things use the CurrentMSBuildToolsDirectory value which must be set for basic - // functionality to work. - string msbuildExePath = GetProcessFromRunningProcess(); - - return new BuildEnvironment(BuildEnvironmentMode.None, msbuildExePath); - } - - private static BuildEnvironment TryFromEnvironmentVariable() - { - var msBuildExePath = GetEnvironmentVariable("MSBUILD_EXE_PATH"); - - return msBuildExePath == null - ? null - : TryFromMSBuildExeUnderVisualStudio(msBuildExePath, allowLegacyToolsVersion: true) ?? TryFromStandaloneMSBuildExe(msBuildExePath); - } - - private static BuildEnvironment TryFromMSBuildProcess() - { - var msBuildExe = GetProcessFromRunningProcess(); - if (!IsProcessInList(msBuildExe, s_msBuildProcess)) - { - return null; - } - - // First check if we're in a VS installation - if (Regex.IsMatch(msBuildExe, $@".*\\MSBuild\\{CurrentToolsVersion}\\Bin\\.*MSBuild(?:TaskHost)?\.exe", RegexOptions.IgnoreCase)) - { - return new BuildEnvironment(BuildEnvironmentMode.VisualStudio, msBuildExe); - } - - // Standalone mode running in MSBuild.exe - return new BuildEnvironment(BuildEnvironmentMode.Standalone, msBuildExe); - } - - private static BuildEnvironment TryFromMSBuildAssembly() - { - var buildAssembly = GetExecutingAssemblyPath(); - if (buildAssembly == null) - { - return null; - } - - // Check for MSBuild.[exe|dll] next to the current assembly - var msBuildExe = Path.Combine(FileUtilities.GetFolderAbove(buildAssembly), "MSBuild.exe"); - var msBuildDll = Path.Combine(FileUtilities.GetFolderAbove(buildAssembly), "MSBuild.dll"); - - // First check if we're in a VS installation - var environment = TryFromMSBuildExeUnderVisualStudio(msBuildExe); - if (environment != null) - { - return environment; - } - - // We're not in VS, check for MSBuild.exe / dll to consider this a standalone environment. - string msBuildPath = null; - if (NativeMethodsShared.FileExists(msBuildExe)) - { - msBuildPath = msBuildExe; - } - else if (NativeMethodsShared.FileExists(msBuildDll)) - { - msBuildPath = msBuildDll; - } - - if (!string.IsNullOrEmpty(msBuildPath)) - { - // Standalone mode with toolset - return new BuildEnvironment(BuildEnvironmentMode.Standalone, msBuildPath); - } - - return null; - } - - private static BuildEnvironment TryFromMSBuildExeUnderVisualStudio(string msbuildExe, bool allowLegacyToolsVersion = false) - { - string msBuildPathPattern = allowLegacyToolsVersion - ? $@".*\\MSBuild\\({CurrentToolsVersion}|\d+\.0)\\Bin\\.*" - : $@".*\\MSBuild\\{CurrentToolsVersion}\\Bin\\.*"; - - if (Regex.IsMatch(msbuildExe, msBuildPathPattern, RegexOptions.IgnoreCase)) - { - string visualStudioRoot = GetVsRootFromMSBuildAssembly(msbuildExe); - return new BuildEnvironment( - BuildEnvironmentMode.VisualStudio, - GetMSBuildExeFromVsRoot(visualStudioRoot)); - } - - return null; - } - - private static BuildEnvironment TryFromDevConsole() - { - // VSINSTALLDIR and VisualStudioVersion are set from the Developer Command Prompt. - var vsInstallDir = GetEnvironmentVariable("VSINSTALLDIR"); - var vsVersion = GetEnvironmentVariable("VisualStudioVersion"); - - if (string.IsNullOrEmpty(vsInstallDir) || string.IsNullOrEmpty(vsVersion) || - vsVersion != CurrentVisualStudioVersion || !NativeMethodsShared.DirectoryExists(vsInstallDir)) - { - return null; - } - - return new BuildEnvironment( - BuildEnvironmentMode.VisualStudio, - GetMSBuildExeFromVsRoot(vsInstallDir)); - } - - private static BuildEnvironment TryFromStandaloneMSBuildExe(string msBuildExePath) - { - if (!string.IsNullOrEmpty(msBuildExePath) && NativeMethodsShared.FileExists(msBuildExePath)) - { - // MSBuild.exe was found outside of Visual Studio. Assume Standalone mode. - return new BuildEnvironment(BuildEnvironmentMode.Standalone, msBuildExePath); - } - - return null; - } - - private static string GetVsRootFromMSBuildAssembly(string msBuildAssembly) - { - string directory = Path.GetDirectoryName(msBuildAssembly); - return FileUtilities.GetFolderAbove(msBuildAssembly, - directory.EndsWith(@"\amd64", StringComparison.OrdinalIgnoreCase) - ? 5 - : 4); - } - - private static string GetMSBuildExeFromVsRoot(string visualStudioRoot) - => FileUtilities.CombinePaths( - visualStudioRoot, - "MSBuild", - CurrentToolsVersion, - "Bin", - NativeMethodsShared.Is64Bit ? "amd64" : string.Empty, - "MSBuild.exe"); - - /// - /// Returns true if processName appears in the processList - /// - /// Name of the process - /// List of processes to check - /// - private static bool IsProcessInList(string processName, string[] processList) - { - var processFileName = Path.GetFileNameWithoutExtension(processName); - - if (string.IsNullOrEmpty(processFileName)) - { - return false; - } - - return processList.Any(s => - processFileName.Equals(s, StringComparison.OrdinalIgnoreCase)); - } - - private static string GetProcessFromRunningProcess() - => EnvironmentUtilities.ProcessPath; - - private static string GetExecutingAssemblyPath() - => FileUtilities.ExecutingAssemblyPath; - - private static string GetEnvironmentVariable(string variable) - => Environment.GetEnvironmentVariable(variable); - - private static class BuildEnvironmentHelperSingleton - { - // Explicit static constructor to tell C# compiler - // not to mark type as beforefieldinit - static BuildEnvironmentHelperSingleton() - { - } - - public static BuildEnvironment s_instance = Initialize(); - } - } - - /// - /// Enum which defines which environment / mode MSBuild is currently running. - /// - internal enum BuildEnvironmentMode - { - /// - /// Running from Visual Studio directly or from MSBuild installed under an instance of Visual Studio. - /// Toolsets and extensions will be loaded from the Visual Studio instance. - /// - VisualStudio, - - /// - /// Running in a standalone toolset mode. All toolsets and extensions paths are relative to the app - /// running and not dependent on Visual Studio. (e.g. dotnet CLI, open source clone of our repo) - /// - Standalone, - - /// - /// Running without any defined toolsets. Most functionality limited. Likely will not be able to - /// build or evaluate a project. (e.g. reference to Microsoft.*.dll without a toolset definition - /// or Visual Studio instance installed). - /// - None - } - - /// - /// Defines the current environment for build tools. - /// - internal sealed class BuildEnvironment - { - public BuildEnvironment(BuildEnvironmentMode mode, string currentMSBuildExePath) - { - FileInfo currentMSBuildExeFile = null; - DirectoryInfo currentToolsDirectory = null; - - CurrentMSBuildExePath = currentMSBuildExePath; - - if (!string.IsNullOrEmpty(currentMSBuildExePath)) - { - currentMSBuildExeFile = new FileInfo(currentMSBuildExePath); - currentToolsDirectory = currentMSBuildExeFile.Directory; - - CurrentMSBuildToolsDirectory = currentMSBuildExeFile.DirectoryName; - MSBuildToolsDirectoryRoot = CurrentMSBuildToolsDirectory; - } - - // We can't detect an environment, don't try to set other paths. - if (mode == BuildEnvironmentMode.None || currentMSBuildExeFile == null || currentToolsDirectory == null) - { - return; - } - - var msBuildExeName = currentMSBuildExeFile.Name; - - if (mode == BuildEnvironmentMode.VisualStudio) - { - // In Visual Studio, the entry-point MSBuild.exe is often from an arch-specific subfolder - MSBuildToolsDirectoryRoot = !NativeMethodsShared.Is64Bit - ? CurrentMSBuildToolsDirectory - : currentToolsDirectory.Parent?.FullName; - } - else - { - // In the .NET SDK, there's one copy of MSBuild.dll and it's in the root folder. - MSBuildToolsDirectoryRoot = CurrentMSBuildToolsDirectory; - - // If we're standalone, we might not be in the SDK. Rely on folder paths at this point. - if (string.Equals(currentToolsDirectory.Name, "amd64", StringComparison.OrdinalIgnoreCase)) - { - MSBuildToolsDirectoryRoot = currentToolsDirectory.Parent?.FullName; - } - } - } - - /// - /// Path to the root of the MSBuild folder (in VS scenarios, MSBuild\Current\bin). - /// - internal string MSBuildToolsDirectoryRoot { get; } - - /// - /// Full path to current MSBuild.exe. - /// - /// This path is likely not the current running process. We may be inside - /// Visual Studio or a test harness. In that case this will point to the - /// version of MSBuild found to be associated with the current environment. - /// - /// - internal string CurrentMSBuildExePath { get; private set; } - - /// - /// Full path to the current MSBuild tools directory. This will be 32-bit unless - /// we're executing from the 'AMD64' folder. - /// - internal string CurrentMSBuildToolsDirectory { get; } - } -} diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 74820ead61c..143ce798954 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -41,7 +41,6 @@ - From 2821ed777861e3811d5409f9a1e4a59058c4eefe Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 4 Feb 2026 14:39:12 -0800 Subject: [PATCH 090/136] MSBuildTaskHost: Remove uncalled members from FileUtilities --- .../CommunicationsUtilities.cs | 3 +- src/MSBuildTaskHost/FileUtilities.cs | 117 +----------------- .../MSBuild/OutOfProcTaskHostNode.cs | 7 +- 3 files changed, 8 insertions(+), 119 deletions(-) diff --git a/src/MSBuildTaskHost/CommunicationsUtilities.cs b/src/MSBuildTaskHost/CommunicationsUtilities.cs index 2f0f181cd97..aa9d4b128b8 100644 --- a/src/MSBuildTaskHost/CommunicationsUtilities.cs +++ b/src/MSBuildTaskHost/CommunicationsUtilities.cs @@ -208,8 +208,7 @@ public Handshake(HandshakeOptions nodeType, bool includeSessionId = true) var options = (int)nodeType | (handshakeVersion << 24); CommunicationsUtilities.Trace("Building handshake for node type {0}, (version {1}): options {2}.", nodeType, handshakeVersion, options); - // Tools directory is the path of MSBuildTaskHost.exe - string toolsDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + string toolsDirectory = FileUtilities.MSBuildTaskHostDirectory; // Calculate salt from environment and tools directory string handshakeSalt = Environment.GetEnvironmentVariable("MSBUILDNODEHANDSHAKESALT") ?? ""; diff --git a/src/MSBuildTaskHost/FileUtilities.cs b/src/MSBuildTaskHost/FileUtilities.cs index 695bebb2e13..e02dc1d676f 100644 --- a/src/MSBuildTaskHost/FileUtilities.cs +++ b/src/MSBuildTaskHost/FileUtilities.cs @@ -4,7 +4,6 @@ using System; using System.IO; using System.Text; -using Microsoft.Build.Framework; #nullable disable @@ -141,14 +140,10 @@ private static string GetDirectory(string fileSpec) } // ISO 8601 Universal time with sortable format - internal const string FileTimeFormat = "yyyy'-'MM'-'dd HH':'mm':'ss'.'fffffff"; + private const string FileTimeFormat = "yyyy'-'MM'-'dd HH':'mm':'ss'.'fffffff"; - /// - /// Get the currently executing assembly path - /// - internal static string ExecutingAssemblyPath => Path.GetFullPath(typeof(FileUtilities).Assembly.Location); - - public static string ExecutingAssemblyDirectory => Path.GetDirectoryName(ExecutingAssemblyPath); + public static string MSBuildTaskHostDirectory + => field ??= Path.GetDirectoryName(Path.GetFullPath(typeof(FileUtilities).Assembly.Location)); /// /// Determines the full path for the given file-spec. @@ -285,112 +280,6 @@ private static bool IsRootedNoThrow(string path) } } - /// - /// Get the folder N levels above the given. Will stop and return current path when rooted. - /// - /// Path to get the folder above. - /// Number of levels up to walk. - /// Full path to the folder N levels above the path. - internal static string GetFolderAbove(string path, int count = 1) - { - if (count < 1) - { - return path; - } - - var parent = Directory.GetParent(path); - - while (count > 1 && parent?.Parent != null) - { - parent = parent.Parent; - count--; - } - - return parent?.FullName ?? path; - } - - /// - /// Combine multiple paths. Should only be used when compiling against .NET 2.0. - /// - /// Root path. - /// Paths to concatenate. - /// Combined path. - /// - /// Only use in .NET 2.0. Otherwise, use System.IO.Path.Combine(...) - /// - internal static string CombinePaths(params string[] paths) - { - // The code below is derived from .NET Framework reference source: - // https://github.com/microsoft/referencesource/blob/ec9fa9ae770d522a5b5f0607898044b7478574a3/mscorlib/system/io/path.cs#L1230-L1285 - - ErrorUtilities.VerifyThrowArgumentNull(paths); - - int firstComponent = 0; - int finalSize = paths.Length; - - for (int i = 0; i < paths.Length; i++) - { - string path = paths[i]; - ErrorUtilities.VerifyThrowArgumentNull(path, nameof(paths)); - - if (path.Length == 0) - { - continue; - } - - if (Path.IsPathRooted(path)) - { - firstComponent = i; - finalSize = path.Length; - } - else - { - finalSize += path.Length; - } - - char ch = path[path.Length - 1]; - if (!IsDirectorySeparator(ch)) - { - finalSize++; - } - } - - StringBuilder builder = StringBuilderCache.Acquire(finalSize); - try - { - for (int i = firstComponent; i < paths.Length; i++) - { - string path = paths[i]; - - if (path.Length == 0) - { - continue; - } - - if (builder.Length > 0) - { - char ch = builder[builder.Length - 1]; - - if (!IsDirectorySeparator(ch) && ch != Path.VolumeSeparatorChar) - { - builder.Append(Path.DirectorySeparatorChar); - } - } - - builder.Append(path); - } - - return builder.ToString(); - } - finally - { - StringBuilderCache.Release(builder); - } - - static bool IsDirectorySeparator(char ch) - => ch == Path.DirectorySeparatorChar || ch == Path.AltDirectorySeparatorChar; - } - internal static StreamWriter OpenWrite(string path, bool append, Encoding encoding = null) { const int DefaultFileStreamBufferSize = 4096; diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs index b02e24172d8..73689fe4248 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Reflection; using System.Runtime.Remoting; using System.Threading; using Microsoft.Build.BackEnd; @@ -568,14 +569,14 @@ private NodeEngineShutdownReason HandleShutdown() _taskRunnerThread?.Join(); using StreamWriter debugWriter = _debugCommunications - ? File.CreateText(string.Format(CultureInfo.CurrentCulture, Path.Combine(FileUtilities.TempFileDirectory, @"MSBuild_NodeShutdown_{0}.txt"), EnvironmentUtilities.CurrentProcessId)) - : null; + ? File.CreateText(Path.Combine(FileUtilities.TempFileDirectory, $"MSBuild_NodeShutdown_{EnvironmentUtilities.CurrentProcessId}.txt")) + : null; debugWriter?.WriteLine("Node shutting down with reason {0}.", _shutdownReason); // On Windows, a process holds a handle to the current directory, // so reset it away from a user-requested folder that may get deleted. - NativeMethodsShared.SetCurrentDirectory(FileUtilities.ExecutingAssemblyDirectory); + NativeMethodsShared.SetCurrentDirectory(FileUtilities.MSBuildTaskHostDirectory); // Restore the original environment, best effort. try From b9a2e3042e245de64366a45e36239357246402e3 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 4 Feb 2026 15:56:30 -0800 Subject: [PATCH 091/136] MSBuildTaskHost: Reorganize files and namespaces Move source files into sensible folders and update namespaces (rooted with Microsoft.Build.TaskHost). --- src/MSBuildTaskHost/AssemblyInfo.cs | 8 -- .../{ => BackEnd}/BinaryReaderExtensions.cs | 2 +- .../BinaryReaderFactory.cs | 2 +- .../BinaryTranslator.cs | 11 +- .../{ => BackEnd}/BinaryWriterExtensions.cs | 2 +- .../{ => BackEnd}/BufferedReadStream.cs | 3 +- .../{ => BackEnd}/INodeEndpoint.cs | 2 +- .../{ => BackEnd}/INodePacket.cs | 3 +- .../{ => BackEnd}/INodePacketFactory.cs | 2 +- .../{ => BackEnd}/INodePacketHandler.cs | 2 +- .../{Framework => BackEnd}/ITranslatable.cs | 2 +- .../{Framework => BackEnd}/ITranslator.cs | 2 +- .../{ => BackEnd}/InterningBinaryReader.cs | 10 +- .../{ => BackEnd}/LogMessagePacketBase.cs | 6 +- .../{ => BackEnd}/NodeBuildComplete.cs | 2 +- .../NodeEndpointOutOfProcTaskHost.cs | 7 +- .../{ => BackEnd}/NodeEngineShutdownReason.cs | 2 +- .../{ => BackEnd}/NodePacketFactory.cs | 4 +- .../{ => BackEnd}/NodeShutdown.cs | 2 +- .../{ => BackEnd}/TaskHostConfiguration.cs | 5 +- .../{ => BackEnd}/TaskHostTaskCancelled.cs | 2 +- .../{ => BackEnd}/TaskHostTaskComplete.cs | 4 +- .../{ => BackEnd}/TaskParameter.cs | 4 +- .../TaskParameterTypeVerifier.cs | 4 +- .../{ => BackEnd}/TranslatorHelpers.cs | 7 +- .../{ => Collections}/CollectionHelpers.cs | 2 +- .../ConcurrentDictionary.cs | 5 +- .../ConcurrentQueue.cs | 4 +- .../CommunicationsUtilities.cs | 11 +- .../BuildExceptionBase.cs | 5 +- .../BuildExceptionRemoteState.cs | 2 +- .../BuildExceptionSerializationHelper.cs | 9 +- .../GenericBuildTransferredException.cs | 2 +- .../InternalErrorException.cs | 3 +- src/MSBuildTaskHost/GlobalUsings.cs | 4 - src/MSBuildTaskHost/LoadedType.cs | 3 +- src/MSBuildTaskHost/{MSBuild => }/MSBuild.ico | Bin src/MSBuildTaskHost/MSBuildTaskHost.csproj | 104 +++++++++--------- .../OutOfProcTaskAppDomainWrapper.cs | 7 +- src/MSBuildTaskHost/OutOfProcTaskHost.cs | 8 +- .../{MSBuild => }/OutOfProcTaskHostNode.cs | 18 ++- .../OutOfProcTaskHostTaskResult.cs | 4 +- .../Properties/AssemblyInfo.cs | 3 + .../{ => Resources}/AssemblyResources.cs | 3 +- src/MSBuildTaskHost/TaskLoader.cs | 3 +- src/MSBuildTaskHost/{Framework => }/Traits.cs | 15 +-- src/MSBuildTaskHost/TypeLoader.cs | 7 +- .../{ => Utilities}/EnvironmentUtilities.cs | 2 +- .../{ => Utilities}/ErrorUtilities.cs | 4 +- .../{ => Utilities}/EscapingUtilities.cs | 4 +- .../{ => Utilities}/ExceptionHandling.cs | 4 +- .../{ => Utilities}/FileUtilities.cs | 15 +-- .../{ => Utilities}/FileUtilitiesRegex.cs | 4 +- .../{ => Utilities}/Modifiers.cs | 6 +- .../{Framework => Utilities}/NativeMethods.cs | 4 +- .../{ => Utilities}/ResourceUtilities.cs | 9 +- .../StringBuilderCache.cs | 2 +- 57 files changed, 170 insertions(+), 206 deletions(-) delete mode 100644 src/MSBuildTaskHost/AssemblyInfo.cs rename src/MSBuildTaskHost/{ => BackEnd}/BinaryReaderExtensions.cs (98%) rename src/MSBuildTaskHost/{Framework => BackEnd}/BinaryReaderFactory.cs (88%) rename src/MSBuildTaskHost/{Framework => BackEnd}/BinaryTranslator.cs (98%) rename src/MSBuildTaskHost/{ => BackEnd}/BinaryWriterExtensions.cs (98%) rename src/MSBuildTaskHost/{ => BackEnd}/BufferedReadStream.cs (98%) rename src/MSBuildTaskHost/{ => BackEnd}/INodeEndpoint.cs (98%) rename src/MSBuildTaskHost/{ => BackEnd}/INodePacket.cs (99%) rename src/MSBuildTaskHost/{ => BackEnd}/INodePacketFactory.cs (98%) rename src/MSBuildTaskHost/{ => BackEnd}/INodePacketHandler.cs (94%) rename src/MSBuildTaskHost/{Framework => BackEnd}/ITranslatable.cs (92%) rename src/MSBuildTaskHost/{Framework => BackEnd}/ITranslator.cs (99%) rename src/MSBuildTaskHost/{ => BackEnd}/InterningBinaryReader.cs (99%) rename src/MSBuildTaskHost/{ => BackEnd}/LogMessagePacketBase.cs (99%) rename src/MSBuildTaskHost/{ => BackEnd}/NodeBuildComplete.cs (98%) rename src/MSBuildTaskHost/{ => BackEnd}/NodeEndpointOutOfProcTaskHost.cs (99%) rename src/MSBuildTaskHost/{ => BackEnd}/NodeEngineShutdownReason.cs (95%) rename src/MSBuildTaskHost/{ => BackEnd}/NodePacketFactory.cs (98%) rename src/MSBuildTaskHost/{ => BackEnd}/NodeShutdown.cs (98%) rename src/MSBuildTaskHost/{ => BackEnd}/TaskHostConfiguration.cs (99%) rename src/MSBuildTaskHost/{ => BackEnd}/TaskHostTaskCancelled.cs (97%) rename src/MSBuildTaskHost/{ => BackEnd}/TaskHostTaskComplete.cs (98%) rename src/MSBuildTaskHost/{ => BackEnd}/TaskParameter.cs (99%) rename src/MSBuildTaskHost/{ => BackEnd}/TaskParameterTypeVerifier.cs (97%) rename src/MSBuildTaskHost/{ => BackEnd}/TranslatorHelpers.cs (91%) rename src/MSBuildTaskHost/{ => Collections}/CollectionHelpers.cs (95%) rename src/MSBuildTaskHost/{Concurrent => Collections}/ConcurrentDictionary.cs (99%) rename src/MSBuildTaskHost/{Concurrent => Collections}/ConcurrentQueue.cs (99%) rename src/MSBuildTaskHost/{Framework/BuildException => Exceptions}/BuildExceptionBase.cs (97%) rename src/MSBuildTaskHost/{Framework/BuildException => Exceptions}/BuildExceptionRemoteState.cs (95%) rename src/MSBuildTaskHost/{Framework/BuildException => Exceptions}/BuildExceptionSerializationHelper.cs (88%) rename src/MSBuildTaskHost/{Framework/BuildException => Exceptions}/GenericBuildTransferredException.cs (91%) rename src/MSBuildTaskHost/{Framework => Exceptions}/InternalErrorException.cs (98%) delete mode 100644 src/MSBuildTaskHost/GlobalUsings.cs rename src/MSBuildTaskHost/{MSBuild => }/MSBuild.ico (100%) rename src/MSBuildTaskHost/{MSBuild => }/OutOfProcTaskHostNode.cs (98%) rename src/MSBuildTaskHost/{ => Resources}/AssemblyResources.cs (93%) rename src/MSBuildTaskHost/{Framework => }/Traits.cs (78%) rename src/MSBuildTaskHost/{ => Utilities}/EnvironmentUtilities.cs (98%) rename src/MSBuildTaskHost/{ => Utilities}/ErrorUtilities.cs (98%) rename src/MSBuildTaskHost/{ => Utilities}/EscapingUtilities.cs (99%) rename src/MSBuildTaskHost/{ => Utilities}/ExceptionHandling.cs (98%) rename src/MSBuildTaskHost/{ => Utilities}/FileUtilities.cs (96%) rename src/MSBuildTaskHost/{ => Utilities}/FileUtilitiesRegex.cs (98%) rename src/MSBuildTaskHost/{ => Utilities}/Modifiers.cs (99%) rename src/MSBuildTaskHost/{Framework => Utilities}/NativeMethods.cs (99%) rename src/MSBuildTaskHost/{ => Utilities}/ResourceUtilities.cs (97%) rename src/MSBuildTaskHost/{Framework => Utilities}/StringBuilderCache.cs (99%) diff --git a/src/MSBuildTaskHost/AssemblyInfo.cs b/src/MSBuildTaskHost/AssemblyInfo.cs deleted file mode 100644 index 191d7a3e309..00000000000 --- a/src/MSBuildTaskHost/AssemblyInfo.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -global using NativeMethodsShared = Microsoft.Build.Framework.NativeMethods; - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.Build.Engine.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] diff --git a/src/MSBuildTaskHost/BinaryReaderExtensions.cs b/src/MSBuildTaskHost/BackEnd/BinaryReaderExtensions.cs similarity index 98% rename from src/MSBuildTaskHost/BinaryReaderExtensions.cs rename to src/MSBuildTaskHost/BackEnd/BinaryReaderExtensions.cs index 55f3111717b..9156f3cca31 100644 --- a/src/MSBuildTaskHost/BinaryReaderExtensions.cs +++ b/src/MSBuildTaskHost/BackEnd/BinaryReaderExtensions.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.IO; -namespace Microsoft.Build.Shared +namespace Microsoft.Build.TaskHost.BackEnd { internal static class BinaryReaderExtensions { diff --git a/src/MSBuildTaskHost/Framework/BinaryReaderFactory.cs b/src/MSBuildTaskHost/BackEnd/BinaryReaderFactory.cs similarity index 88% rename from src/MSBuildTaskHost/Framework/BinaryReaderFactory.cs rename to src/MSBuildTaskHost/BackEnd/BinaryReaderFactory.cs index 5cc76fe84ea..f32c2b39c1b 100644 --- a/src/MSBuildTaskHost/Framework/BinaryReaderFactory.cs +++ b/src/MSBuildTaskHost/BackEnd/BinaryReaderFactory.cs @@ -3,7 +3,7 @@ using System.IO; -namespace Microsoft.Build; +namespace Microsoft.Build.TaskHost.BackEnd; /// /// Opaque holder of shared buffer. diff --git a/src/MSBuildTaskHost/Framework/BinaryTranslator.cs b/src/MSBuildTaskHost/BackEnd/BinaryTranslator.cs similarity index 98% rename from src/MSBuildTaskHost/Framework/BinaryTranslator.cs rename to src/MSBuildTaskHost/BackEnd/BinaryTranslator.cs index 658f08a656c..c2c404a7d43 100644 --- a/src/MSBuildTaskHost/Framework/BinaryTranslator.cs +++ b/src/MSBuildTaskHost/BackEnd/BinaryTranslator.cs @@ -6,12 +6,12 @@ using System.Diagnostics; using System.Globalization; using System.IO; -using Microsoft.Build.Framework; -using Microsoft.Build.Framework.BuildException; +using Microsoft.Build.TaskHost.Exceptions; +using Microsoft.Build.TaskHost.Utilities; #nullable disable -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { /// /// This class is responsible for serializing and deserializing simple types to and @@ -85,7 +85,7 @@ public BinaryWriter Writer { get { - EscapeHatches.ThrowInternalError("Cannot get writer from reader."); + ErrorUtilities.ThrowInternalError("Cannot get writer from reader."); return null; } } @@ -365,7 +365,6 @@ public void TranslateException(ref Exception value) value = BuildExceptionBase.ReadExceptionFromTranslator(this); } - /// /// Translates a dictionary of { string, string }. /// @@ -459,7 +458,7 @@ public BinaryReader Reader { get { - EscapeHatches.ThrowInternalError("Cannot get reader from writer."); + ErrorUtilities.ThrowInternalError("Cannot get reader from writer."); return null; } } diff --git a/src/MSBuildTaskHost/BinaryWriterExtensions.cs b/src/MSBuildTaskHost/BackEnd/BinaryWriterExtensions.cs similarity index 98% rename from src/MSBuildTaskHost/BinaryWriterExtensions.cs rename to src/MSBuildTaskHost/BackEnd/BinaryWriterExtensions.cs index cda207f3932..bd416d42dd9 100644 --- a/src/MSBuildTaskHost/BinaryWriterExtensions.cs +++ b/src/MSBuildTaskHost/BackEnd/BinaryWriterExtensions.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.IO; -namespace Microsoft.Build.Shared +namespace Microsoft.Build.TaskHost.BackEnd { internal static class BinaryWriterExtensions { diff --git a/src/MSBuildTaskHost/BufferedReadStream.cs b/src/MSBuildTaskHost/BackEnd/BufferedReadStream.cs similarity index 98% rename from src/MSBuildTaskHost/BufferedReadStream.cs rename to src/MSBuildTaskHost/BackEnd/BufferedReadStream.cs index ee43cfc2b41..832a0fb023c 100644 --- a/src/MSBuildTaskHost/BufferedReadStream.cs +++ b/src/MSBuildTaskHost/BackEnd/BufferedReadStream.cs @@ -4,11 +4,10 @@ using System; using System.IO; using System.IO.Pipes; -using System.Threading; #nullable disable -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { internal class BufferedReadStream : Stream { diff --git a/src/MSBuildTaskHost/INodeEndpoint.cs b/src/MSBuildTaskHost/BackEnd/INodeEndpoint.cs similarity index 98% rename from src/MSBuildTaskHost/INodeEndpoint.cs rename to src/MSBuildTaskHost/BackEnd/INodeEndpoint.cs index 373250acce2..d1d6b4df01a 100644 --- a/src/MSBuildTaskHost/INodeEndpoint.cs +++ b/src/MSBuildTaskHost/BackEnd/INodeEndpoint.cs @@ -3,7 +3,7 @@ #nullable disable -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { #region Delegates /// diff --git a/src/MSBuildTaskHost/INodePacket.cs b/src/MSBuildTaskHost/BackEnd/INodePacket.cs similarity index 99% rename from src/MSBuildTaskHost/INodePacket.cs rename to src/MSBuildTaskHost/BackEnd/INodePacket.cs index af4502e57f0..f25d8a0a22f 100644 --- a/src/MSBuildTaskHost/INodePacket.cs +++ b/src/MSBuildTaskHost/BackEnd/INodePacket.cs @@ -5,9 +5,8 @@ using System; using System.IO; -using Microsoft.Build.Internal; -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { #region Enums diff --git a/src/MSBuildTaskHost/INodePacketFactory.cs b/src/MSBuildTaskHost/BackEnd/INodePacketFactory.cs similarity index 98% rename from src/MSBuildTaskHost/INodePacketFactory.cs rename to src/MSBuildTaskHost/BackEnd/INodePacketFactory.cs index 63d469eb021..db8493c0157 100644 --- a/src/MSBuildTaskHost/INodePacketFactory.cs +++ b/src/MSBuildTaskHost/BackEnd/INodePacketFactory.cs @@ -3,7 +3,7 @@ #nullable disable -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { /// /// A delegate representing factory methods used to re-create packets deserialized from a stream. diff --git a/src/MSBuildTaskHost/INodePacketHandler.cs b/src/MSBuildTaskHost/BackEnd/INodePacketHandler.cs similarity index 94% rename from src/MSBuildTaskHost/INodePacketHandler.cs rename to src/MSBuildTaskHost/BackEnd/INodePacketHandler.cs index 8d72f9dd053..3ab8dd022b3 100644 --- a/src/MSBuildTaskHost/INodePacketHandler.cs +++ b/src/MSBuildTaskHost/BackEnd/INodePacketHandler.cs @@ -3,7 +3,7 @@ #nullable disable -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { /// /// Objects which wish to receive packets from the NodePacketRouter must implement this interface. diff --git a/src/MSBuildTaskHost/Framework/ITranslatable.cs b/src/MSBuildTaskHost/BackEnd/ITranslatable.cs similarity index 92% rename from src/MSBuildTaskHost/Framework/ITranslatable.cs rename to src/MSBuildTaskHost/BackEnd/ITranslatable.cs index fdddd566135..fdf48d25a7a 100644 --- a/src/MSBuildTaskHost/Framework/ITranslatable.cs +++ b/src/MSBuildTaskHost/BackEnd/ITranslatable.cs @@ -3,7 +3,7 @@ #nullable disable -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { /// /// An interface representing an object which may be serialized by the node packet serializer. diff --git a/src/MSBuildTaskHost/Framework/ITranslator.cs b/src/MSBuildTaskHost/BackEnd/ITranslator.cs similarity index 99% rename from src/MSBuildTaskHost/Framework/ITranslator.cs rename to src/MSBuildTaskHost/BackEnd/ITranslator.cs index 21c7e755354..30be4c837af 100644 --- a/src/MSBuildTaskHost/Framework/ITranslator.cs +++ b/src/MSBuildTaskHost/BackEnd/ITranslator.cs @@ -8,7 +8,7 @@ #nullable disable -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { /// /// This delegate is used for objects which do not have public parameterless constructors and must be constructed using diff --git a/src/MSBuildTaskHost/InterningBinaryReader.cs b/src/MSBuildTaskHost/BackEnd/InterningBinaryReader.cs similarity index 99% rename from src/MSBuildTaskHost/InterningBinaryReader.cs rename to src/MSBuildTaskHost/BackEnd/InterningBinaryReader.cs index 1aca5dff8a4..8eb0b1a8a25 100644 --- a/src/MSBuildTaskHost/InterningBinaryReader.cs +++ b/src/MSBuildTaskHost/BackEnd/InterningBinaryReader.cs @@ -2,18 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Text; -using System.IO; using System.Diagnostics; +using System.IO; +using System.Text; using System.Threading; - -using ErrorUtilities = Microsoft.Build.Shared.ErrorUtilities; - +using Microsoft.Build.TaskHost.Utilities; using Microsoft.NET.StringTools; #nullable disable -namespace Microsoft.Build +namespace Microsoft.Build.TaskHost.BackEnd { /// /// Replacement for BinaryReader which attempts to intern the strings read by ReadString. diff --git a/src/MSBuildTaskHost/LogMessagePacketBase.cs b/src/MSBuildTaskHost/BackEnd/LogMessagePacketBase.cs similarity index 99% rename from src/MSBuildTaskHost/LogMessagePacketBase.cs rename to src/MSBuildTaskHost/BackEnd/LogMessagePacketBase.cs index 2e95a4f300f..97a28d4a578 100644 --- a/src/MSBuildTaskHost/LogMessagePacketBase.cs +++ b/src/MSBuildTaskHost/BackEnd/LogMessagePacketBase.cs @@ -5,13 +5,13 @@ using System.Collections.Generic; using System.IO; using System.Reflection; - -using Microsoft.Build.BackEnd; using Microsoft.Build.Framework; +using Microsoft.Build.TaskHost.Exceptions; +using Microsoft.Build.TaskHost.Utilities; #nullable disable -namespace Microsoft.Build.Shared +namespace Microsoft.Build.TaskHost.BackEnd { // On .NET Framework 3.5, Microsoft.Build.Framework includes the following concrete event args types: // diff --git a/src/MSBuildTaskHost/NodeBuildComplete.cs b/src/MSBuildTaskHost/BackEnd/NodeBuildComplete.cs similarity index 98% rename from src/MSBuildTaskHost/NodeBuildComplete.cs rename to src/MSBuildTaskHost/BackEnd/NodeBuildComplete.cs index 25aa30675f7..4a56debc189 100644 --- a/src/MSBuildTaskHost/NodeBuildComplete.cs +++ b/src/MSBuildTaskHost/BackEnd/NodeBuildComplete.cs @@ -5,7 +5,7 @@ #nullable disable -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { /// /// The NodeBuildComplete packet is used to indicate to a node that it should clean up its current build and diff --git a/src/MSBuildTaskHost/NodeEndpointOutOfProcTaskHost.cs b/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs similarity index 99% rename from src/MSBuildTaskHost/NodeEndpointOutOfProcTaskHost.cs rename to src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs index ebb853b2931..e2943c27e4b 100644 --- a/src/MSBuildTaskHost/NodeEndpointOutOfProcTaskHost.cs +++ b/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs @@ -9,13 +9,12 @@ using System.Security.AccessControl; using System.Security.Principal; using System.Threading; -using Microsoft.Build.Internal; -using Microsoft.Build.Shared; -using Microsoft.Build.Shared.Concurrent; +using Microsoft.Build.TaskHost.Collections; +using Microsoft.Build.TaskHost.Utilities; #nullable disable -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { /// /// This is an implementation of INodeEndpoint for the out-of-proc nodes. It acts only as a client. diff --git a/src/MSBuildTaskHost/NodeEngineShutdownReason.cs b/src/MSBuildTaskHost/BackEnd/NodeEngineShutdownReason.cs similarity index 95% rename from src/MSBuildTaskHost/NodeEngineShutdownReason.cs rename to src/MSBuildTaskHost/BackEnd/NodeEngineShutdownReason.cs index dc4f0f3069c..8df1134faf4 100644 --- a/src/MSBuildTaskHost/NodeEngineShutdownReason.cs +++ b/src/MSBuildTaskHost/BackEnd/NodeEngineShutdownReason.cs @@ -3,7 +3,7 @@ #nullable disable -namespace Microsoft.Build.Execution +namespace Microsoft.Build.TaskHost.BackEnd { #region Enums /// diff --git a/src/MSBuildTaskHost/NodePacketFactory.cs b/src/MSBuildTaskHost/BackEnd/NodePacketFactory.cs similarity index 98% rename from src/MSBuildTaskHost/NodePacketFactory.cs rename to src/MSBuildTaskHost/BackEnd/NodePacketFactory.cs index 478c88310eb..a18e2c97972 100644 --- a/src/MSBuildTaskHost/NodePacketFactory.cs +++ b/src/MSBuildTaskHost/BackEnd/NodePacketFactory.cs @@ -2,11 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using Microsoft.Build.Shared; +using Microsoft.Build.TaskHost.Utilities; #nullable disable -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { /// /// Implementation of INodePacketFactory as a helper class for classes which expose this interface publicly. diff --git a/src/MSBuildTaskHost/NodeShutdown.cs b/src/MSBuildTaskHost/BackEnd/NodeShutdown.cs similarity index 98% rename from src/MSBuildTaskHost/NodeShutdown.cs rename to src/MSBuildTaskHost/BackEnd/NodeShutdown.cs index 9ce9426799e..e63d6b0b115 100644 --- a/src/MSBuildTaskHost/NodeShutdown.cs +++ b/src/MSBuildTaskHost/BackEnd/NodeShutdown.cs @@ -5,7 +5,7 @@ #nullable disable -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { /// /// Reasons why the node shut down. diff --git a/src/MSBuildTaskHost/TaskHostConfiguration.cs b/src/MSBuildTaskHost/BackEnd/TaskHostConfiguration.cs similarity index 99% rename from src/MSBuildTaskHost/TaskHostConfiguration.cs rename to src/MSBuildTaskHost/BackEnd/TaskHostConfiguration.cs index f473dd7031b..beb8c8bbcc2 100644 --- a/src/MSBuildTaskHost/TaskHostConfiguration.cs +++ b/src/MSBuildTaskHost/BackEnd/TaskHostConfiguration.cs @@ -5,12 +5,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using Microsoft.Build.Execution; -using Microsoft.Build.Shared; +using Microsoft.Build.TaskHost.Utilities; #nullable disable -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { /// /// TaskHostConfiguration contains information needed for the task host to diff --git a/src/MSBuildTaskHost/TaskHostTaskCancelled.cs b/src/MSBuildTaskHost/BackEnd/TaskHostTaskCancelled.cs similarity index 97% rename from src/MSBuildTaskHost/TaskHostTaskCancelled.cs rename to src/MSBuildTaskHost/BackEnd/TaskHostTaskCancelled.cs index f56e61a34d9..7a2beb05a2f 100644 --- a/src/MSBuildTaskHost/TaskHostTaskCancelled.cs +++ b/src/MSBuildTaskHost/BackEnd/TaskHostTaskCancelled.cs @@ -3,7 +3,7 @@ #nullable disable -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { /// /// TaskHostTaskCancelled informs the task host that the task it is diff --git a/src/MSBuildTaskHost/TaskHostTaskComplete.cs b/src/MSBuildTaskHost/BackEnd/TaskHostTaskComplete.cs similarity index 98% rename from src/MSBuildTaskHost/TaskHostTaskComplete.cs rename to src/MSBuildTaskHost/BackEnd/TaskHostTaskComplete.cs index 57ca8ed7e83..163f7200a5e 100644 --- a/src/MSBuildTaskHost/TaskHostTaskComplete.cs +++ b/src/MSBuildTaskHost/BackEnd/TaskHostTaskComplete.cs @@ -4,11 +4,11 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using Microsoft.Build.Shared; +using Microsoft.Build.TaskHost.Utilities; #nullable disable -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { /// /// How the task completed -- successful, failed, or crashed diff --git a/src/MSBuildTaskHost/TaskParameter.cs b/src/MSBuildTaskHost/BackEnd/TaskParameter.cs similarity index 99% rename from src/MSBuildTaskHost/TaskParameter.cs rename to src/MSBuildTaskHost/BackEnd/TaskParameter.cs index d49f461de17..43c5118519c 100644 --- a/src/MSBuildTaskHost/TaskParameter.cs +++ b/src/MSBuildTaskHost/BackEnd/TaskParameter.cs @@ -7,11 +7,11 @@ using System.Globalization; using System.Security; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; +using Microsoft.Build.TaskHost.Utilities; #nullable disable -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { /// /// Type of parameter, used to figure out how to serialize it. diff --git a/src/MSBuildTaskHost/TaskParameterTypeVerifier.cs b/src/MSBuildTaskHost/BackEnd/TaskParameterTypeVerifier.cs similarity index 97% rename from src/MSBuildTaskHost/TaskParameterTypeVerifier.cs rename to src/MSBuildTaskHost/BackEnd/TaskParameterTypeVerifier.cs index 0f971502be3..e95cc309118 100644 --- a/src/MSBuildTaskHost/TaskParameterTypeVerifier.cs +++ b/src/MSBuildTaskHost/BackEnd/TaskParameterTypeVerifier.cs @@ -2,13 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Reflection; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; #nullable disable -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { /// /// Provide a class which can verify the correct type for both input and output parameters. diff --git a/src/MSBuildTaskHost/TranslatorHelpers.cs b/src/MSBuildTaskHost/BackEnd/TranslatorHelpers.cs similarity index 91% rename from src/MSBuildTaskHost/TranslatorHelpers.cs rename to src/MSBuildTaskHost/BackEnd/TranslatorHelpers.cs index 17c78804c62..f231634e7bf 100644 --- a/src/MSBuildTaskHost/TranslatorHelpers.cs +++ b/src/MSBuildTaskHost/BackEnd/TranslatorHelpers.cs @@ -1,16 +1,11 @@ // 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; -using System.Configuration.Assemblies; -using System.Globalization; -using System.Reflection; -using AssemblyHashAlgorithm = System.Configuration.Assemblies.AssemblyHashAlgorithm; #nullable disable -namespace Microsoft.Build.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd { /// /// This class provides helper methods to adapt from to diff --git a/src/MSBuildTaskHost/CollectionHelpers.cs b/src/MSBuildTaskHost/Collections/CollectionHelpers.cs similarity index 95% rename from src/MSBuildTaskHost/CollectionHelpers.cs rename to src/MSBuildTaskHost/Collections/CollectionHelpers.cs index 6d8ba5188c7..05a4a55e982 100644 --- a/src/MSBuildTaskHost/CollectionHelpers.cs +++ b/src/MSBuildTaskHost/Collections/CollectionHelpers.cs @@ -6,7 +6,7 @@ #nullable disable -namespace Microsoft.Build.Shared +namespace Microsoft.Build.TaskHost.Collections { /// /// Utilities for collections diff --git a/src/MSBuildTaskHost/Concurrent/ConcurrentDictionary.cs b/src/MSBuildTaskHost/Collections/ConcurrentDictionary.cs similarity index 99% rename from src/MSBuildTaskHost/Concurrent/ConcurrentDictionary.cs rename to src/MSBuildTaskHost/Collections/ConcurrentDictionary.cs index 8cb007b093b..644c1c13c6e 100644 --- a/src/MSBuildTaskHost/Concurrent/ConcurrentDictionary.cs +++ b/src/MSBuildTaskHost/Collections/ConcurrentDictionary.cs @@ -5,10 +5,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.Threading; +using Microsoft.Build.TaskHost.Utilities; #nullable disable -namespace Microsoft.Build.Shared.Concurrent +namespace Microsoft.Build.TaskHost.Collections { // The following class is back-ported from .NET 4.X CoreFX library because // MSBuildTaskHost requires 3.5 .NET Framework. Only GetOrAdd method kept. @@ -98,7 +99,7 @@ private static bool IsValueWriteAtomic() /// public ConcurrentDictionary(IEqualityComparer comparer = null) { - int concurrencyLevel = NativeMethodsShared.GetLogicalCoreCount(); + int concurrencyLevel = NativeMethods.GetLogicalCoreCount(); int capacity = DefaultCapacity; // The capacity should be at least as large as the concurrency level. Otherwise, we would have locks that don't guard diff --git a/src/MSBuildTaskHost/Concurrent/ConcurrentQueue.cs b/src/MSBuildTaskHost/Collections/ConcurrentQueue.cs similarity index 99% rename from src/MSBuildTaskHost/Concurrent/ConcurrentQueue.cs rename to src/MSBuildTaskHost/Collections/ConcurrentQueue.cs index b1b05998bda..dab75693e91 100644 --- a/src/MSBuildTaskHost/Concurrent/ConcurrentQueue.cs +++ b/src/MSBuildTaskHost/Collections/ConcurrentQueue.cs @@ -8,7 +8,7 @@ #nullable disable -namespace Microsoft.Build.Shared.Concurrent +namespace Microsoft.Build.TaskHost.Collections { // The following class is back-ported from .NET 4.X CoreFX library because // MSBuildTaskHost requires 3.5 .NET Framework. Only important methods (Enqueue, TryDequeue) are kept. @@ -188,7 +188,7 @@ private bool TryDequeueSlow(out T item) /// /// /// When this method returns, contains an object from - /// the beginning of the or default(T) + /// the beginning of the or default(T) /// if the operation failed. /// /// true if and object was returned successfully; otherwise, false. diff --git a/src/MSBuildTaskHost/CommunicationsUtilities.cs b/src/MSBuildTaskHost/CommunicationsUtilities.cs index aa9d4b128b8..b8dd6e4350c 100644 --- a/src/MSBuildTaskHost/CommunicationsUtilities.cs +++ b/src/MSBuildTaskHost/CommunicationsUtilities.cs @@ -11,13 +11,12 @@ using System.Runtime.InteropServices; using System.Security.Principal; using System.Threading; -using Microsoft.Build.BackEnd; -using Microsoft.Build.Framework; -using Microsoft.Build.Shared; +using Microsoft.Build.TaskHost.BackEnd; +using Microsoft.Build.TaskHost.Utilities; #nullable disable -namespace Microsoft.Build.Internal +namespace Microsoft.Build.TaskHost { /// /// Enumeration of all possible (currently supported) options for handshakes. @@ -280,7 +279,7 @@ internal static class CommunicationsUtilities /// /// Lock trace to ensure we are logging in serial fashion. /// - private static readonly LockType s_traceLock = new LockType(); + private static readonly object s_traceLock = new(); /// /// Place to dump trace @@ -624,7 +623,7 @@ internal static HandshakeOptions GetHandshakeOptions() options |= HandshakeOptions.CLR2; - if (NativeMethodsShared.Is64Bit) + if (NativeMethods.Is64Bit) { options |= HandshakeOptions.X64; } diff --git a/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionBase.cs b/src/MSBuildTaskHost/Exceptions/BuildExceptionBase.cs similarity index 97% rename from src/MSBuildTaskHost/Framework/BuildException/BuildExceptionBase.cs rename to src/MSBuildTaskHost/Exceptions/BuildExceptionBase.cs index 0c32be246fc..ce40d4ea35e 100644 --- a/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionBase.cs +++ b/src/MSBuildTaskHost/Exceptions/BuildExceptionBase.cs @@ -6,10 +6,9 @@ using System.Diagnostics; using System.IO; using System.Runtime.Serialization; -using Microsoft.Build.BackEnd; -using Microsoft.Build.Shared; +using Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.Framework.BuildException; +namespace Microsoft.Build.TaskHost.Exceptions; public abstract class BuildExceptionBase : Exception { diff --git a/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionRemoteState.cs b/src/MSBuildTaskHost/Exceptions/BuildExceptionRemoteState.cs similarity index 95% rename from src/MSBuildTaskHost/Framework/BuildException/BuildExceptionRemoteState.cs rename to src/MSBuildTaskHost/Exceptions/BuildExceptionRemoteState.cs index b4d8786f43d..8517b08415e 100644 --- a/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionRemoteState.cs +++ b/src/MSBuildTaskHost/Exceptions/BuildExceptionRemoteState.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Build.Framework.BuildException; +namespace Microsoft.Build.TaskHost.Exceptions; /// /// Remote exception internal data serving as the source for the exception deserialization. diff --git a/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionSerializationHelper.cs b/src/MSBuildTaskHost/Exceptions/BuildExceptionSerializationHelper.cs similarity index 88% rename from src/MSBuildTaskHost/Framework/BuildException/BuildExceptionSerializationHelper.cs rename to src/MSBuildTaskHost/Exceptions/BuildExceptionSerializationHelper.cs index 030fd532e00..1ccc5171097 100644 --- a/src/MSBuildTaskHost/Framework/BuildException/BuildExceptionSerializationHelper.cs +++ b/src/MSBuildTaskHost/Exceptions/BuildExceptionSerializationHelper.cs @@ -4,8 +4,9 @@ using System; using System.Collections.Generic; using System.Threading; +using Microsoft.Build.TaskHost.Utilities; -namespace Microsoft.Build.Framework.BuildException +namespace Microsoft.Build.TaskHost.Exceptions { internal static class BuildExceptionSerializationHelper { @@ -55,7 +56,7 @@ internal static void InitializeSerializationContract(IEnumerable? factory = null; if (s_exceptionFactories == null) { - EscapeHatches.ThrowInternalError("Serialization contract was not initialized."); + ErrorUtilities.ThrowInternalError("Serialization contract was not initialized."); } else { diff --git a/src/MSBuildTaskHost/Framework/BuildException/GenericBuildTransferredException.cs b/src/MSBuildTaskHost/Exceptions/GenericBuildTransferredException.cs similarity index 91% rename from src/MSBuildTaskHost/Framework/BuildException/GenericBuildTransferredException.cs rename to src/MSBuildTaskHost/Exceptions/GenericBuildTransferredException.cs index 0c0261c80d5..abad08ed095 100644 --- a/src/MSBuildTaskHost/Framework/BuildException/GenericBuildTransferredException.cs +++ b/src/MSBuildTaskHost/Exceptions/GenericBuildTransferredException.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Build.Framework.BuildException; +namespace Microsoft.Build.TaskHost.Exceptions; /// /// A catch-all type for remote exceptions that we don't know how to deserialize. diff --git a/src/MSBuildTaskHost/Framework/InternalErrorException.cs b/src/MSBuildTaskHost/Exceptions/InternalErrorException.cs similarity index 98% rename from src/MSBuildTaskHost/Framework/InternalErrorException.cs rename to src/MSBuildTaskHost/Exceptions/InternalErrorException.cs index 53c847f741f..bad35205d2f 100644 --- a/src/MSBuildTaskHost/Framework/InternalErrorException.cs +++ b/src/MSBuildTaskHost/Exceptions/InternalErrorException.cs @@ -4,11 +4,10 @@ using System; using System.Diagnostics; using System.Runtime.Serialization; -using Microsoft.Build.Framework.BuildException; #nullable disable -namespace Microsoft.Build.Framework +namespace Microsoft.Build.TaskHost.Exceptions { /// /// This exception is to be thrown whenever an assumption we have made in the code turns out to be false. Thus, if this diff --git a/src/MSBuildTaskHost/GlobalUsings.cs b/src/MSBuildTaskHost/GlobalUsings.cs deleted file mode 100644 index 81647980ca2..00000000000 --- a/src/MSBuildTaskHost/GlobalUsings.cs +++ /dev/null @@ -1,4 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -global using LockType = System.Object; diff --git a/src/MSBuildTaskHost/LoadedType.cs b/src/MSBuildTaskHost/LoadedType.cs index 3f3d1858c8c..2ef10ee0c51 100644 --- a/src/MSBuildTaskHost/LoadedType.cs +++ b/src/MSBuildTaskHost/LoadedType.cs @@ -4,8 +4,9 @@ using System; using System.Reflection; using Microsoft.Build.Framework; +using Microsoft.Build.TaskHost.Utilities; -namespace Microsoft.Build.Shared +namespace Microsoft.Build.TaskHost { /// /// This class packages information about a type loaded from an assembly: for example, diff --git a/src/MSBuildTaskHost/MSBuild/MSBuild.ico b/src/MSBuildTaskHost/MSBuild.ico similarity index 100% rename from src/MSBuildTaskHost/MSBuild/MSBuild.ico rename to src/MSBuildTaskHost/MSBuild.ico diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 143ce798954..1ae4329fdd7 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -17,18 +17,20 @@ true false - $(DefineConstants);CLR2COMPATIBILITY;TASKHOST;NO_FRAMEWORK_IVT - + + Microsoft.Build.TaskHost true + true - MSBuild\MSBuild.ico + MSBuild.ico true - full + + full @@ -37,64 +39,62 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - + + - - - - - - - - - + - - - + + + + + + + + + + + @@ -106,7 +106,7 @@ - + diff --git a/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs b/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs index 9f43faec9f1..bd1edc867e8 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs @@ -4,11 +4,12 @@ using System; using System.Collections.Generic; using System.Reflection; -using Microsoft.Build.BackEnd; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; +using Microsoft.Build.TaskHost.BackEnd; +using Microsoft.Build.TaskHost.Resources; +using Microsoft.Build.TaskHost.Utilities; -namespace Microsoft.Build.CommandLine; +namespace Microsoft.Build.TaskHost; /// /// Class for executing a task in an AppDomain. diff --git a/src/MSBuildTaskHost/OutOfProcTaskHost.cs b/src/MSBuildTaskHost/OutOfProcTaskHost.cs index 9ee7b001bca..4fa41c6a2ee 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskHost.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskHost.cs @@ -3,14 +3,12 @@ using System; using System.Diagnostics; - -// CR: We could move MSBuildApp.ExitType out of MSBuildApp -using Microsoft.Build.Execution; -using Microsoft.Build.Shared; +using Microsoft.Build.TaskHost.BackEnd; +using Microsoft.Build.TaskHost.Utilities; #nullable disable -namespace Microsoft.Build.CommandLine +namespace Microsoft.Build.TaskHost { /// /// This is the Out-Of-Proc Task Host for supporting Cross-Targeting tasks. diff --git a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs similarity index 98% rename from src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs rename to src/MSBuildTaskHost/OutOfProcTaskHostNode.cs index 73689fe4248..39c0e405869 100644 --- a/src/MSBuildTaskHost/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs @@ -4,20 +4,18 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Globalization; using System.IO; -using System.Reflection; using System.Runtime.Remoting; using System.Threading; -using Microsoft.Build.BackEnd; -using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Internal; -using Microsoft.Build.Shared; +using Microsoft.Build.TaskHost.BackEnd; +using Microsoft.Build.TaskHost.Collections; +using Microsoft.Build.TaskHost.Resources; +using Microsoft.Build.TaskHost.Utilities; #nullable disable -namespace Microsoft.Build.CommandLine +namespace Microsoft.Build.TaskHost { /// /// This class represents an implementation of INode for out-of-proc node for hosting tasks. @@ -109,7 +107,7 @@ internal class OutOfProcTaskHostNode : MarshalByRefObject, INodePacketFactory, I /// /// Object used to synchronize access to taskCompletePacket /// - private LockType _taskCompleteLock = new(); + private object _taskCompleteLock = new(); /// /// The event which is set when a task is cancelled @@ -576,7 +574,7 @@ private NodeEngineShutdownReason HandleShutdown() // On Windows, a process holds a handle to the current directory, // so reset it away from a user-requested folder that may get deleted. - NativeMethodsShared.SetCurrentDirectory(FileUtilities.MSBuildTaskHostDirectory); + NativeMethods.SetCurrentDirectory(FileUtilities.MSBuildTaskHostDirectory); // Restore the original environment, best effort. try @@ -648,7 +646,7 @@ private void RunTask(object state) try { // Change to the startup directory - NativeMethodsShared.SetCurrentDirectory(taskConfiguration.StartupDirectory); + NativeMethods.SetCurrentDirectory(taskConfiguration.StartupDirectory); if (_updateEnvironment) { diff --git a/src/MSBuildTaskHost/OutOfProcTaskHostTaskResult.cs b/src/MSBuildTaskHost/OutOfProcTaskHostTaskResult.cs index 6b1a3faed9f..f536fab7f99 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskHostTaskResult.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskHostTaskResult.cs @@ -3,9 +3,9 @@ using System; using System.Collections.Generic; -using Microsoft.Build.BackEnd; +using Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.Shared; +namespace Microsoft.Build.TaskHost; /// /// A result of executing a target or task. diff --git a/src/MSBuildTaskHost/Properties/AssemblyInfo.cs b/src/MSBuildTaskHost/Properties/AssemblyInfo.cs index 2e74dfc6607..d975ac97636 100644 --- a/src/MSBuildTaskHost/Properties/AssemblyInfo.cs +++ b/src/MSBuildTaskHost/Properties/AssemblyInfo.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM @@ -9,3 +10,5 @@ [assembly: ComVisible(false)] [assembly: CLSCompliant(true)] + +[assembly: InternalsVisibleTo("Microsoft.Build.Engine.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] diff --git a/src/MSBuildTaskHost/AssemblyResources.cs b/src/MSBuildTaskHost/Resources/AssemblyResources.cs similarity index 93% rename from src/MSBuildTaskHost/AssemblyResources.cs rename to src/MSBuildTaskHost/Resources/AssemblyResources.cs index 6ee4f10b194..8ec548f86d0 100644 --- a/src/MSBuildTaskHost/AssemblyResources.cs +++ b/src/MSBuildTaskHost/Resources/AssemblyResources.cs @@ -4,10 +4,11 @@ using System.Globalization; using System.Reflection; using System.Resources; +using Microsoft.Build.TaskHost.Utilities; #nullable disable -namespace Microsoft.Build.Shared +namespace Microsoft.Build.TaskHost.Resources { /// /// This class provides access to the assembly's resources. diff --git a/src/MSBuildTaskHost/TaskLoader.cs b/src/MSBuildTaskHost/TaskLoader.cs index a29a34150ae..8b388df408b 100644 --- a/src/MSBuildTaskHost/TaskLoader.cs +++ b/src/MSBuildTaskHost/TaskLoader.cs @@ -4,8 +4,9 @@ using System; using System.Reflection; using Microsoft.Build.Framework; +using Microsoft.Build.TaskHost.Utilities; -namespace Microsoft.Build.Shared +namespace Microsoft.Build.TaskHost { /// /// Class for loading tasks diff --git a/src/MSBuildTaskHost/Framework/Traits.cs b/src/MSBuildTaskHost/Traits.cs similarity index 78% rename from src/MSBuildTaskHost/Framework/Traits.cs rename to src/MSBuildTaskHost/Traits.cs index 44f1e552b93..a17263de39c 100644 --- a/src/MSBuildTaskHost/Framework/Traits.cs +++ b/src/MSBuildTaskHost/Traits.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Build.Framework +namespace Microsoft.Build.TaskHost { /// /// Represents toggleable features of the MSBuild engine. @@ -12,7 +12,7 @@ internal class Traits { public static Traits Instance { get; } = new Traits(); - public Traits() + private Traits() { EscapeHatches = new EscapeHatches(); @@ -37,16 +37,5 @@ internal class EscapeHatches /// Disable the use of paths longer than Windows MAX_PATH limits (260 characters) when running on a long path enabled OS. /// public readonly bool DisableLongPaths = Environment.GetEnvironmentVariable("MSBUILDDISABLELONGPATHS") == "1"; - - /// - /// Throws InternalErrorException. - /// - /// - /// Clone of ErrorUtilities.ThrowInternalError which isn't available in Framework. - /// - public static void ThrowInternalError(string message) - { - throw new InternalErrorException(message); - } } } diff --git a/src/MSBuildTaskHost/TypeLoader.cs b/src/MSBuildTaskHost/TypeLoader.cs index 63d9cc29147..3911607260b 100644 --- a/src/MSBuildTaskHost/TypeLoader.cs +++ b/src/MSBuildTaskHost/TypeLoader.cs @@ -7,11 +7,12 @@ using System.Reflection; using System.Threading; using Microsoft.Build.Framework; -using Microsoft.Build.Shared.Concurrent; +using Microsoft.Build.TaskHost.Collections; +using Microsoft.Build.TaskHost.Utilities; #nullable disable -namespace Microsoft.Build.Shared +namespace Microsoft.Build.TaskHost { /// /// This class is used to load types from their assemblies. @@ -175,7 +176,7 @@ private class AssemblyInfoToLoadedTypes /// Lock to prevent two threads from using this object at the same time. /// Since we fill up internal structures with what is in the assembly /// - private readonly LockType _lockObject = new(); + private readonly object _lockObject = new(); /// /// Type filter to pick the correct types out of an assembly diff --git a/src/MSBuildTaskHost/EnvironmentUtilities.cs b/src/MSBuildTaskHost/Utilities/EnvironmentUtilities.cs similarity index 98% rename from src/MSBuildTaskHost/EnvironmentUtilities.cs rename to src/MSBuildTaskHost/Utilities/EnvironmentUtilities.cs index 1f70e51f3ff..8a430cf3a5a 100644 --- a/src/MSBuildTaskHost/EnvironmentUtilities.cs +++ b/src/MSBuildTaskHost/Utilities/EnvironmentUtilities.cs @@ -6,7 +6,7 @@ using System.Diagnostics; using System.Threading; -namespace Microsoft.Build.Shared +namespace Microsoft.Build.TaskHost.Utilities { internal static partial class EnvironmentUtilities { diff --git a/src/MSBuildTaskHost/ErrorUtilities.cs b/src/MSBuildTaskHost/Utilities/ErrorUtilities.cs similarity index 98% rename from src/MSBuildTaskHost/ErrorUtilities.cs rename to src/MSBuildTaskHost/Utilities/ErrorUtilities.cs index 6862a18fa2f..22545e2be7c 100644 --- a/src/MSBuildTaskHost/ErrorUtilities.cs +++ b/src/MSBuildTaskHost/Utilities/ErrorUtilities.cs @@ -4,9 +4,9 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using Microsoft.Build.Framework; +using Microsoft.Build.TaskHost.Exceptions; -namespace Microsoft.Build.Shared +namespace Microsoft.Build.TaskHost.Utilities { /// /// This class contains methods that are useful for error checking and validation. diff --git a/src/MSBuildTaskHost/EscapingUtilities.cs b/src/MSBuildTaskHost/Utilities/EscapingUtilities.cs similarity index 99% rename from src/MSBuildTaskHost/EscapingUtilities.cs rename to src/MSBuildTaskHost/Utilities/EscapingUtilities.cs index 8bde0027840..88df273001f 100644 --- a/src/MSBuildTaskHost/EscapingUtilities.cs +++ b/src/MSBuildTaskHost/Utilities/EscapingUtilities.cs @@ -4,13 +4,11 @@ using System; using System.Collections.Generic; using System.Text; - -using Microsoft.Build.Framework; using Microsoft.NET.StringTools; #nullable disable -namespace Microsoft.Build.Shared +namespace Microsoft.Build.TaskHost.Utilities { /// /// This class implements static methods to assist with unescaping of %XX codes diff --git a/src/MSBuildTaskHost/ExceptionHandling.cs b/src/MSBuildTaskHost/Utilities/ExceptionHandling.cs similarity index 98% rename from src/MSBuildTaskHost/ExceptionHandling.cs rename to src/MSBuildTaskHost/Utilities/ExceptionHandling.cs index a24ba3c0e20..2028e9a8a08 100644 --- a/src/MSBuildTaskHost/ExceptionHandling.cs +++ b/src/MSBuildTaskHost/Utilities/ExceptionHandling.cs @@ -9,9 +9,9 @@ using System.IO; using System.Security; using System.Threading; -using Microsoft.Build.Framework; +using Microsoft.Build.TaskHost.Exceptions; -namespace Microsoft.Build.Shared +namespace Microsoft.Build.TaskHost.Utilities { /// /// Utility methods for classifying and handling exceptions. diff --git a/src/MSBuildTaskHost/FileUtilities.cs b/src/MSBuildTaskHost/Utilities/FileUtilities.cs similarity index 96% rename from src/MSBuildTaskHost/FileUtilities.cs rename to src/MSBuildTaskHost/Utilities/FileUtilities.cs index e02dc1d676f..57de6288749 100644 --- a/src/MSBuildTaskHost/FileUtilities.cs +++ b/src/MSBuildTaskHost/Utilities/FileUtilities.cs @@ -4,10 +4,11 @@ using System; using System.IO; using System.Text; +using Microsoft.Build.TaskHost.Resources; #nullable disable -namespace Microsoft.Build.Shared +namespace Microsoft.Build.TaskHost.Utilities { /// /// This class contains utility methods for file IO. @@ -61,11 +62,11 @@ private static string NormalizePath(string path) private static string GetFullPath(string path) { - string uncheckedFullPath = NativeMethodsShared.GetFullPath(path); + string uncheckedFullPath = NativeMethods.GetFullPath(path); if (IsPathTooLong(uncheckedFullPath)) { - string message = ResourceUtilities.FormatString(AssemblyResources.GetString("Shared.PathTooLong"), path, NativeMethodsShared.MaxPath); + string message = ResourceUtilities.FormatString(AssemblyResources.GetString("Shared.PathTooLong"), path, NativeMethods.MaxPath); throw new PathTooLongException(message); } @@ -254,15 +255,15 @@ private static string AttemptToShortenPath(string path) private static bool IsPathTooLong(string path) { // >= not > because MAX_PATH assumes a trailing null - return path.Length >= NativeMethodsShared.MaxPath; + return path.Length >= NativeMethods.MaxPath; } private static bool IsPathTooLongIfRooted(string path) { - bool hasMaxPath = NativeMethodsShared.HasMaxPath; - int maxPath = NativeMethodsShared.MaxPath; + bool hasMaxPath = NativeMethods.HasMaxPath; + int maxPath = NativeMethods.MaxPath; // >= not > because MAX_PATH assumes a trailing null - return hasMaxPath && !IsRootedNoThrow(path) && NativeMethodsShared.GetCurrentDirectory().Length + path.Length + 1 /* slash */ >= maxPath; + return hasMaxPath && !IsRootedNoThrow(path) && NativeMethods.GetCurrentDirectory().Length + path.Length + 1 /* slash */ >= maxPath; } /// diff --git a/src/MSBuildTaskHost/FileUtilitiesRegex.cs b/src/MSBuildTaskHost/Utilities/FileUtilitiesRegex.cs similarity index 98% rename from src/MSBuildTaskHost/FileUtilitiesRegex.cs rename to src/MSBuildTaskHost/Utilities/FileUtilitiesRegex.cs index ce0bf2c8d0f..e70a12bb7bf 100644 --- a/src/MSBuildTaskHost/FileUtilitiesRegex.cs +++ b/src/MSBuildTaskHost/Utilities/FileUtilitiesRegex.cs @@ -1,11 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Runtime.CompilerServices; - #nullable disable -namespace Microsoft.Build.Shared +namespace Microsoft.Build.TaskHost.Utilities { /// /// This class contains utility methods for file IO. diff --git a/src/MSBuildTaskHost/Modifiers.cs b/src/MSBuildTaskHost/Utilities/Modifiers.cs similarity index 99% rename from src/MSBuildTaskHost/Modifiers.cs rename to src/MSBuildTaskHost/Utilities/Modifiers.cs index 4e05a68d659..e8788106ff0 100644 --- a/src/MSBuildTaskHost/Modifiers.cs +++ b/src/MSBuildTaskHost/Utilities/Modifiers.cs @@ -8,7 +8,7 @@ #nullable disable -namespace Microsoft.Build.Shared +namespace Microsoft.Build.TaskHost.Utilities { /// /// This class contains utility methods for file IO. @@ -294,7 +294,7 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS // to unescape first. string unescapedItemSpec = EscapingUtilities.UnescapeAll(itemSpec); - if (NativeMethodsShared.FileExists(unescapedItemSpec)) + if (NativeMethods.FileExists(unescapedItemSpec)) { modifiedItemSpec = File.GetCreationTime(unescapedItemSpec).ToString(FileTimeFormat, null); } @@ -310,7 +310,7 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS // to unescape first. string unescapedItemSpec = EscapingUtilities.UnescapeAll(itemSpec); - if (NativeMethodsShared.FileExists(unescapedItemSpec)) + if (NativeMethods.FileExists(unescapedItemSpec)) { modifiedItemSpec = File.GetLastAccessTime(unescapedItemSpec).ToString(FileTimeFormat, null); } diff --git a/src/MSBuildTaskHost/Framework/NativeMethods.cs b/src/MSBuildTaskHost/Utilities/NativeMethods.cs similarity index 99% rename from src/MSBuildTaskHost/Framework/NativeMethods.cs rename to src/MSBuildTaskHost/Utilities/NativeMethods.cs index c0355210591..1ee011465d9 100644 --- a/src/MSBuildTaskHost/Framework/NativeMethods.cs +++ b/src/MSBuildTaskHost/Utilities/NativeMethods.cs @@ -8,7 +8,7 @@ #nullable disable -namespace Microsoft.Build.Framework; +namespace Microsoft.Build.TaskHost.Utilities; internal static class NativeMethods { @@ -163,7 +163,7 @@ internal static int MaxPath private static bool IsMaxPathSet { get; set; } - private static readonly LockType MaxPathLock = new LockType(); + private static readonly object MaxPathLock = new(); private static void SetMaxPath() { diff --git a/src/MSBuildTaskHost/ResourceUtilities.cs b/src/MSBuildTaskHost/Utilities/ResourceUtilities.cs similarity index 97% rename from src/MSBuildTaskHost/ResourceUtilities.cs rename to src/MSBuildTaskHost/Utilities/ResourceUtilities.cs index e35a3b6dec5..d4ff30eb58e 100644 --- a/src/MSBuildTaskHost/ResourceUtilities.cs +++ b/src/MSBuildTaskHost/Utilities/ResourceUtilities.cs @@ -2,12 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Resources; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Resources; +using Microsoft.Build.TaskHost.Resources; -namespace Microsoft.Build.Shared +namespace Microsoft.Build.TaskHost.Utilities { /// /// This class contains utility methods for dealing with resources. @@ -143,7 +144,7 @@ internal static string GetResourceString(string resourceName) /// Resource string to load. /// Optional arguments for formatting the resource string. /// The formatted resource string. - internal static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName, params object?[]? args) + private static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName, params object?[]? args) { helpKeyword = GetHelpKeyword(resourceName); @@ -159,7 +160,7 @@ internal static string FormatResourceStringStripCodeAndKeyword(out string? code, /// [out] The MSBuild F1-help keyword for the host IDE, or null. /// Resource string to load. /// Argument for formatting the resource string. - internal static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName, object? arg1) + private static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName, object? arg1) { helpKeyword = GetHelpKeyword(resourceName); return ExtractMessageCode(true, FormatString(GetResourceString(resourceName), arg1), out code); diff --git a/src/MSBuildTaskHost/Framework/StringBuilderCache.cs b/src/MSBuildTaskHost/Utilities/StringBuilderCache.cs similarity index 99% rename from src/MSBuildTaskHost/Framework/StringBuilderCache.cs rename to src/MSBuildTaskHost/Utilities/StringBuilderCache.cs index 68c849ac118..dd1f231e4bd 100644 --- a/src/MSBuildTaskHost/Framework/StringBuilderCache.cs +++ b/src/MSBuildTaskHost/Utilities/StringBuilderCache.cs @@ -7,7 +7,7 @@ #nullable disable -namespace Microsoft.Build.Framework +namespace Microsoft.Build.TaskHost.Utilities { /// /// A cached reusable instance of StringBuilder. From 0fdc494746ba9d51ad770ec2b25cd70b2f2c1376 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 4 Feb 2026 16:23:00 -0800 Subject: [PATCH 092/136] MSBuildTaskHost: Clean up and fix exception serialization --- .../Exceptions/BuildExceptionBase.cs | 49 ++-- .../Exceptions/BuildExceptionRemoteState.cs | 19 +- .../BuildExceptionSerializationHelper.cs | 82 +------ ...cs => GeneralBuildTransferredException.cs} | 16 +- .../Exceptions/InternalErrorException.cs | 225 +++++++++--------- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 2 +- src/MSBuildTaskHost/OutOfProcTaskHost.cs | 1 + 7 files changed, 173 insertions(+), 221 deletions(-) rename src/MSBuildTaskHost/Exceptions/{GenericBuildTransferredException.cs => GeneralBuildTransferredException.cs} (56%) diff --git a/src/MSBuildTaskHost/Exceptions/BuildExceptionBase.cs b/src/MSBuildTaskHost/Exceptions/BuildExceptionBase.cs index ce40d4ea35e..5f4cb280367 100644 --- a/src/MSBuildTaskHost/Exceptions/BuildExceptionBase.cs +++ b/src/MSBuildTaskHost/Exceptions/BuildExceptionBase.cs @@ -10,63 +10,67 @@ namespace Microsoft.Build.TaskHost.Exceptions; -public abstract class BuildExceptionBase : Exception +internal abstract class BuildExceptionBase : Exception { private string? _remoteTypeName; private string? _remoteStackTrace; private protected BuildExceptionBase() : base() - { } + { + } private protected BuildExceptionBase(string? message) : base(message) - { } + { + } - private protected BuildExceptionBase( - string? message, - Exception? inner) + private protected BuildExceptionBase(string? message, Exception? inner) : base(message, inner) - { } + { + } // This is needed to allow opting back in to BinaryFormatter serialization private protected BuildExceptionBase(SerializationInfo info, StreamingContext context) : base(info, context) - { } + { + } - public override string? StackTrace => string.IsNullOrEmpty(_remoteStackTrace) ? base.StackTrace : _remoteStackTrace; + public override string? StackTrace + => string.IsNullOrEmpty(_remoteStackTrace) ? base.StackTrace : _remoteStackTrace; - public override string ToString() => string.IsNullOrEmpty(_remoteTypeName) ? base.ToString() : $"{_remoteTypeName}->{base.ToString()}"; + public override string ToString() + => string.IsNullOrEmpty(_remoteTypeName) ? base.ToString() : $"{_remoteTypeName}->{base.ToString()}"; /// /// Override this method to recover subtype-specific state from the remote exception. /// protected virtual void InitializeCustomState(IDictionary? customKeyedSerializedData) - { } + { + } /// /// Override this method to provide subtype-specific state to be serialized. /// /// - protected virtual IDictionary? FlushCustomState() - { - return null; - } + protected virtual IDictionary? FlushCustomState() => null; private void InitializeFromRemoteState(BuildExceptionRemoteState remoteState) { _remoteTypeName = remoteState.RemoteTypeName; _remoteStackTrace = remoteState.RemoteStackTrace; - base.Source = remoteState.Source; - base.HelpLink = remoteState.HelpLink; - base.HResult = remoteState.HResult; + + Source = remoteState.Source; + HelpLink = remoteState.HelpLink; + HResult = remoteState.HResult; + if (remoteState.Source != null) { InitializeCustomState(remoteState.CustomKeyedSerializedData); } } - internal static void WriteExceptionToTranslator(ITranslator translator, Exception exception) + public static void WriteExceptionToTranslator(ITranslator translator, Exception exception) { BinaryWriter writer = translator.Writer; writer.Write(exception.InnerException != null); @@ -75,12 +79,13 @@ internal static void WriteExceptionToTranslator(ITranslator translator, Exceptio WriteExceptionToTranslator(translator, exception.InnerException); } - string serializationType = BuildExceptionSerializationHelper.GetExceptionSerializationKey(exception.GetType()); + string serializationType = BuildExceptionSerializationHelper.GetSerializationKey(exception.GetType()); writer.Write(serializationType); writer.Write(exception.Message); writer.WriteOptionalString(exception.StackTrace); writer.WriteOptionalString(exception.Source); writer.WriteOptionalString(exception.HelpLink); + // HResult is completely protected up till net4.5 int? hresult = null; writer.WriteOptionalInt32(hresult); @@ -105,7 +110,7 @@ internal static void WriteExceptionToTranslator(ITranslator translator, Exceptio "Exception Data is not supported in BuildTransferredException"); } - internal static Exception ReadExceptionFromTranslator(ITranslator translator) + public static Exception ReadExceptionFromTranslator(ITranslator translator) { BinaryReader reader = translator.Reader; Exception? innerException = null; @@ -133,7 +138,7 @@ internal static Exception ReadExceptionFromTranslator(ITranslator translator) } } - BuildExceptionBase exception = BuildExceptionSerializationHelper.CreateExceptionFactory(serializationType)(message, innerException); + BuildExceptionBase exception = BuildExceptionSerializationHelper.DeserializeException(serializationType, message, innerException); exception.InitializeFromRemoteState( new BuildExceptionRemoteState( diff --git a/src/MSBuildTaskHost/Exceptions/BuildExceptionRemoteState.cs b/src/MSBuildTaskHost/Exceptions/BuildExceptionRemoteState.cs index 8517b08415e..1281506277c 100644 --- a/src/MSBuildTaskHost/Exceptions/BuildExceptionRemoteState.cs +++ b/src/MSBuildTaskHost/Exceptions/BuildExceptionRemoteState.cs @@ -10,6 +10,18 @@ namespace Microsoft.Build.TaskHost.Exceptions; /// internal class BuildExceptionRemoteState { + public string RemoteTypeName { get; } + + public string? RemoteStackTrace { get; } + + public string? Source { get; } + + public string? HelpLink { get; } + + public int HResult { get; } + + public IDictionary? CustomKeyedSerializedData { get; } + public BuildExceptionRemoteState( string remoteTypeName, string? remoteStackTrace, @@ -25,11 +37,4 @@ public BuildExceptionRemoteState( HResult = hResult; CustomKeyedSerializedData = customKeyedSerializedData; } - - public string RemoteTypeName { get; init; } - public string? RemoteStackTrace { get; init; } - public string? Source { get; init; } - public string? HelpLink { get; init; } - public int HResult { get; init; } - public IDictionary? CustomKeyedSerializedData { get; init; } } diff --git a/src/MSBuildTaskHost/Exceptions/BuildExceptionSerializationHelper.cs b/src/MSBuildTaskHost/Exceptions/BuildExceptionSerializationHelper.cs index 1ccc5171097..1c1ddbb4103 100644 --- a/src/MSBuildTaskHost/Exceptions/BuildExceptionSerializationHelper.cs +++ b/src/MSBuildTaskHost/Exceptions/BuildExceptionSerializationHelper.cs @@ -3,90 +3,34 @@ using System; using System.Collections.Generic; -using System.Threading; -using Microsoft.Build.TaskHost.Utilities; namespace Microsoft.Build.TaskHost.Exceptions { internal static class BuildExceptionSerializationHelper { - public class TypeConstructionTuple + private static Dictionary> s_exceptionFactories = new() { - public TypeConstructionTuple(Type type, Func factory) - { - Type = type; - Factory = factory; - } - - public Type Type { get; } - public Func Factory { get; } - } - - private static Dictionary>? s_exceptionFactories; + { GetSerializationKey(), InternalErrorException.CreateFromRemote } + }; private static readonly Func s_defaultFactory = - (message, innerException) => new GenericBuildTransferredException(message, innerException); - - internal static bool IsSupportedExceptionType(Type type) - { - return type.IsClass && - !type.IsAbstract && - type.IsSubclassOf(typeof(Exception)) && - type.IsSubclassOf(typeof(BuildExceptionBase)); - } - - internal static void InitializeSerializationContract(params TypeConstructionTuple[] exceptionsAllowlist) - { - InitializeSerializationContract((IEnumerable)exceptionsAllowlist); - } - - internal static void InitializeSerializationContract(IEnumerable exceptionsAllowlist) - { - if (s_exceptionFactories != null) - { - return; - } - - var exceptionFactories = new Dictionary>(); - - foreach (TypeConstructionTuple typeConstructionTuple in exceptionsAllowlist) - { - Type exceptionType = typeConstructionTuple.Type; - Func exceptionFactory = typeConstructionTuple.Factory; - - if (!IsSupportedExceptionType(exceptionType)) - { - ErrorUtilities.ThrowInternalError($"Type {exceptionType.FullName} is not recognized as a build exception type."); - } - - string key = GetExceptionSerializationKey(exceptionType); - exceptionFactories[key] = exceptionFactory; - } + (message, innerException) => new GeneralBuildTransferredException(message, innerException); - if (Interlocked.Exchange(ref s_exceptionFactories, exceptionFactories) != null) - { - ErrorUtilities.ThrowInternalError("Serialization contract was already initialized."); - } - } + public static string GetSerializationKey() + where T : BuildExceptionBase + => GetSerializationKey(typeof(T)); - internal static string GetExceptionSerializationKey(Type exceptionType) - { - return exceptionType.FullName ?? exceptionType.ToString(); - } + public static string GetSerializationKey(Type exceptionType) + => exceptionType.FullName ?? exceptionType.ToString(); - internal static Func CreateExceptionFactory(string serializationType) + public static BuildExceptionBase DeserializeException(string serializationType, string message, Exception? innerException) { - Func? factory = null; - if (s_exceptionFactories == null) - { - ErrorUtilities.ThrowInternalError("Serialization contract was not initialized."); - } - else + if (s_exceptionFactories.TryGetValue(serializationType, out var factory)) { - s_exceptionFactories.TryGetValue(serializationType, out factory); + factory = s_defaultFactory; } - return factory ?? s_defaultFactory; + return factory(message, innerException); } } } diff --git a/src/MSBuildTaskHost/Exceptions/GenericBuildTransferredException.cs b/src/MSBuildTaskHost/Exceptions/GeneralBuildTransferredException.cs similarity index 56% rename from src/MSBuildTaskHost/Exceptions/GenericBuildTransferredException.cs rename to src/MSBuildTaskHost/Exceptions/GeneralBuildTransferredException.cs index abad08ed095..9a524d89cd7 100644 --- a/src/MSBuildTaskHost/Exceptions/GenericBuildTransferredException.cs +++ b/src/MSBuildTaskHost/Exceptions/GeneralBuildTransferredException.cs @@ -8,15 +8,15 @@ namespace Microsoft.Build.TaskHost.Exceptions; /// /// A catch-all type for remote exceptions that we don't know how to deserialize. /// -internal sealed class GenericBuildTransferredException : BuildExceptionBase +internal sealed class GeneralBuildTransferredException : BuildExceptionBase { - public GenericBuildTransferredException() + public GeneralBuildTransferredException() : base() - { } + { + } - internal GenericBuildTransferredException( - string message, - Exception? inner) - : base(message, inner) - { } + internal GeneralBuildTransferredException(string message, Exception? innerException) + : base(message, innerException) + { + } } diff --git a/src/MSBuildTaskHost/Exceptions/InternalErrorException.cs b/src/MSBuildTaskHost/Exceptions/InternalErrorException.cs index bad35205d2f..1207c4c459f 100644 --- a/src/MSBuildTaskHost/Exceptions/InternalErrorException.cs +++ b/src/MSBuildTaskHost/Exceptions/InternalErrorException.cs @@ -7,141 +7,138 @@ #nullable disable -namespace Microsoft.Build.TaskHost.Exceptions +namespace Microsoft.Build.TaskHost.Exceptions; + +/// +/// This exception is to be thrown whenever an assumption we have made in the code turns out to be false. Thus, if this +/// exception ever gets thrown, it is because of a bug in our own code, not because of something the user or project author +/// did wrong. +/// +[Serializable] +internal sealed class InternalErrorException : BuildExceptionBase { /// - /// This exception is to be thrown whenever an assumption we have made in the code turns out to be false. Thus, if this - /// exception ever gets thrown, it is because of a bug in our own code, not because of something the user or project author - /// did wrong. + /// Default constructor. + /// SHOULD ONLY BE CALLED BY DESERIALIZER. + /// SUPPLY A MESSAGE INSTEAD. /// - [Serializable] - internal sealed class InternalErrorException : BuildExceptionBase + private InternalErrorException() + : base() { - /// - /// Default constructor. - /// SHOULD ONLY BE CALLED BY DESERIALIZER. - /// SUPPLY A MESSAGE INSTEAD. - /// - internal InternalErrorException() : base() - { - // do nothing - } + } - /// - /// Creates an instance of this exception using the given message. - /// - internal InternalErrorException( - String message) : - base("MSB0001: Internal MSBuild Error: " + message) - { - ConsiderDebuggerLaunch(message, null); - } + /// + /// Creates an instance of this exception using the given message. + /// + internal InternalErrorException(string message) + : base("MSB0001: Internal MSBuild Error: " + message) + { + ConsiderDebuggerLaunch(message, null); + } - /// - /// Creates an instance of this exception using the given message and inner exception. - /// Adds the inner exception's details to the exception message because most bug reporters don't bother - /// to provide the inner exception details which is typically what we care about. - /// - internal InternalErrorException( - String message, - Exception innerException) : - this(message, innerException, false) - { } + /// + /// Creates an instance of this exception using the given message and inner exception. + /// Adds the inner exception's details to the exception message because most bug reporters don't bother + /// to provide the inner exception details which is typically what we care about. + /// + internal InternalErrorException(string message, Exception innerException) + : this(message, innerException, false) + { + } - internal static InternalErrorException CreateFromRemote(string message, Exception innerException) - { - return new InternalErrorException(message, innerException, true /* calledFromDeserialization */); - } + internal static InternalErrorException CreateFromRemote(string message, Exception innerException) + { + return new InternalErrorException(message, innerException, calledFromDeserialization: true); + } - private InternalErrorException(string message, Exception innerException, bool calledFromDeserialization) - : base( - calledFromDeserialization - ? message - : "MSB0001: Internal MSBuild Error: " + message + (innerException == null - ? String.Empty - : $"\n=============\n{innerException}\n\n"), - innerException) + private InternalErrorException(string message, Exception innerException, bool calledFromDeserialization) + : base( + calledFromDeserialization + ? message + : "MSB0001: Internal MSBuild Error: " + message + (innerException == null + ? string.Empty + : $"\n=============\n{innerException}\n\n"), + innerException) + { + if (!calledFromDeserialization) { - if (!calledFromDeserialization) - { - ConsiderDebuggerLaunch(message, innerException); - } + ConsiderDebuggerLaunch(message, innerException); } + } - #region Serialization (update when adding new class members) + #region Serialization (update when adding new class members) - /// - /// Private constructor used for (de)serialization. The constructor is private as this class is sealed - /// If we ever add new members to this class, we'll need to update this. - /// - private InternalErrorException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - // Do nothing: no fields - } + /// + /// Private constructor used for (de)serialization. The constructor is private as this class is sealed + /// If we ever add new members to this class, we'll need to update this. + /// + private InternalErrorException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + // Do nothing: no fields + } - // Base implementation of GetObjectData() is sufficient; we have no fields - #endregion + // Base implementation of GetObjectData() is sufficient; we have no fields + #endregion - #region ConsiderDebuggerLaunch - /// - /// A fatal internal error due to a bug has occurred. Give the dev a chance to debug it, if possible. - /// - /// Will in all cases launch the debugger, if the environment variable "MSBUILDLAUNCHDEBUGGER" is set. - /// - /// In DEBUG build, will always launch the debugger, unless we are in razzle (_NTROOT is set) or in NUnit, - /// or MSBUILDDONOTLAUNCHDEBUGGER is set (that could be useful in suite runs). - /// We don't launch in retail or LKG so builds don't jam; they get a callstack, and continue or send a mail, etc. - /// We don't launch in NUnit as tests often intentionally cause InternalErrorExceptions. - /// - /// Because we only call this method from this class, just before throwing an InternalErrorException, there is - /// no danger that this suppression will cause a bug to only manifest itself outside NUnit - /// (which would be most unfortunate!). Do not make this non-private. - /// - /// Unfortunately NUnit can't handle unhandled exceptions like InternalErrorException on anything other than - /// the main test thread. However, there's still a callstack displayed before it quits. - /// - /// If it is going to launch the debugger, it first does a Debug.Fail to give information about what needs to - /// be debugged -- the exception hasn't been thrown yet. This automatically displays the current callstack. - /// - private static void ConsiderDebuggerLaunch(string message, Exception innerException) - { - string innerMessage = (innerException == null) ? String.Empty : innerException.ToString(); + #region ConsiderDebuggerLaunch + /// + /// A fatal internal error due to a bug has occurred. Give the dev a chance to debug it, if possible. + /// + /// Will in all cases launch the debugger, if the environment variable "MSBUILDLAUNCHDEBUGGER" is set. + /// + /// In DEBUG build, will always launch the debugger, unless we are in razzle (_NTROOT is set) or in NUnit, + /// or MSBUILDDONOTLAUNCHDEBUGGER is set (that could be useful in suite runs). + /// We don't launch in retail or LKG so builds don't jam; they get a callstack, and continue or send a mail, etc. + /// We don't launch in NUnit as tests often intentionally cause InternalErrorExceptions. + /// + /// Because we only call this method from this class, just before throwing an InternalErrorException, there is + /// no danger that this suppression will cause a bug to only manifest itself outside NUnit + /// (which would be most unfortunate!). Do not make this non-private. + /// + /// Unfortunately NUnit can't handle unhandled exceptions like InternalErrorException on anything other than + /// the main test thread. However, there's still a callstack displayed before it quits. + /// + /// If it is going to launch the debugger, it first does a Debug.Fail to give information about what needs to + /// be debugged -- the exception hasn't been thrown yet. This automatically displays the current callstack. + /// + private static void ConsiderDebuggerLaunch(string message, Exception innerException) + { + string innerMessage = (innerException == null) ? String.Empty : innerException.ToString(); - if (Environment.GetEnvironmentVariable("MSBUILDLAUNCHDEBUGGER") != null) - { - LaunchDebugger(message, innerMessage); - return; - } + if (Environment.GetEnvironmentVariable("MSBUILDLAUNCHDEBUGGER") != null) + { + LaunchDebugger(message, innerMessage); + return; + } #if DEBUG - if (Environment.GetEnvironmentVariable("MSBUILDDONOTLAUNCHDEBUGGER") == null - && Environment.GetEnvironmentVariable("_NTROOT") == null) - { - LaunchDebugger(message, innerMessage); - return; - } -#endif + if (Environment.GetEnvironmentVariable("MSBUILDDONOTLAUNCHDEBUGGER") == null + && Environment.GetEnvironmentVariable("_NTROOT") == null) + { + LaunchDebugger(message, innerMessage); + return; } +#endif + } - private static void LaunchDebugger(string message, string innerMessage) - { + private static void LaunchDebugger(string message, string innerMessage) + { #if FEATURE_DEBUG_LAUNCH - Debug.Fail(message, innerMessage); - Debugger.Launch(); + Debug.Fail(message, innerMessage); + Debugger.Launch(); #else - Console.WriteLine("MSBuild Failure: " + message); - if (!string.IsNullOrEmpty(innerMessage)) - { - Console.WriteLine(innerMessage); - } - Console.WriteLine("Waiting for debugger to attach to process: " + Process.GetCurrentProcess().Id); - while (!Debugger.IsAttached) - { - System.Threading.Thread.Sleep(100); - } -#endif + Console.WriteLine("MSBuild Failure: " + message); + if (!string.IsNullOrEmpty(innerMessage)) + { + Console.WriteLine(innerMessage); } - #endregion + Console.WriteLine("Waiting for debugger to attach to process: " + Process.GetCurrentProcess().Id); + while (!Debugger.IsAttached) + { + System.Threading.Thread.Sleep(100); + } +#endif } + #endregion } diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 1ae4329fdd7..94e4a5d6dbf 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -69,7 +69,7 @@ - + diff --git a/src/MSBuildTaskHost/OutOfProcTaskHost.cs b/src/MSBuildTaskHost/OutOfProcTaskHost.cs index 4fa41c6a2ee..c6f4d280f06 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskHost.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskHost.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using Microsoft.Build.TaskHost.BackEnd; +using Microsoft.Build.TaskHost.Exceptions; using Microsoft.Build.TaskHost.Utilities; #nullable disable From 32146e9bd4f6053ffb8878fec8a855bf005653c7 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 4 Feb 2026 16:27:47 -0800 Subject: [PATCH 093/136] MSBuildTaskHost: Enable default items in project file --- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 87 ++++------------------ 1 file changed, 15 insertions(+), 72 deletions(-) diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 94e4a5d6dbf..85b3aacadef 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -16,8 +16,6 @@ win7-x86;win7-x64 true - false - Microsoft.Build.TaskHost true @@ -32,88 +30,33 @@ full + + + + + + true + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + + - + $(AssemblyName).Strings.shared.resources Designer - - - - - - - - - - + From 3a92cf3a9985347046c76a9be5232031334c1f1c Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 4 Feb 2026 17:20:09 -0800 Subject: [PATCH 094/136] MSBuildTaskHost: Rework and prune string resources The key change here is to allow the resx generator to produce source code for the strings in MSBuildTaskHost. ErrorUtilities has been updated to take format strings rather than resource IDs. --- .../BackEnd/InterningBinaryReader.cs | 4 +- .../BackEnd/LogMessagePacketBase.cs | 2 +- .../BackEnd/NodeEndpointOutOfProcTaskHost.cs | 6 +- .../BackEnd/NodePacketFactory.cs | 6 +- src/MSBuildTaskHost/BackEnd/TaskParameter.cs | 21 +- src/MSBuildTaskHost/MSBuildTaskHost.csproj | 8 +- .../OutOfProcTaskAppDomainWrapper.cs | 4 +- src/MSBuildTaskHost/OutOfProcTaskHostNode.cs | 81 ++-- .../Resources/AssemblyResources.cs | 38 -- src/MSBuildTaskHost/Resources/SR.resx | 160 ++++++++ .../Resources/Strings.shared.resx | 379 ------------------ src/MSBuildTaskHost/Resources/xlf/SR.cs.xlf | 62 +++ src/MSBuildTaskHost/Resources/xlf/SR.de.xlf | 62 +++ src/MSBuildTaskHost/Resources/xlf/SR.es.xlf | 62 +++ src/MSBuildTaskHost/Resources/xlf/SR.fr.xlf | 62 +++ src/MSBuildTaskHost/Resources/xlf/SR.it.xlf | 62 +++ src/MSBuildTaskHost/Resources/xlf/SR.ja.xlf | 62 +++ src/MSBuildTaskHost/Resources/xlf/SR.ko.xlf | 62 +++ src/MSBuildTaskHost/Resources/xlf/SR.pl.xlf | 62 +++ .../Resources/xlf/SR.pt-BR.xlf | 62 +++ src/MSBuildTaskHost/Resources/xlf/SR.ru.xlf | 62 +++ src/MSBuildTaskHost/Resources/xlf/SR.tr.xlf | 62 +++ .../xlf/{Strings.shared.xlf => SR.xlf} | 0 .../Resources/xlf/SR.zh-Hans.xlf | 62 +++ .../Resources/xlf/SR.zh-Hant.xlf | 62 +++ .../Resources/xlf/Strings.shared.cs.xlf | 349 ---------------- .../Resources/xlf/Strings.shared.de.xlf | 349 ---------------- .../Resources/xlf/Strings.shared.es.xlf | 349 ---------------- .../Resources/xlf/Strings.shared.fr.xlf | 349 ---------------- .../Resources/xlf/Strings.shared.it.xlf | 349 ---------------- .../Resources/xlf/Strings.shared.ja.xlf | 349 ---------------- .../Resources/xlf/Strings.shared.ko.xlf | 349 ---------------- .../Resources/xlf/Strings.shared.pl.xlf | 349 ---------------- .../Resources/xlf/Strings.shared.pt-BR.xlf | 349 ---------------- .../Resources/xlf/Strings.shared.ru.xlf | 349 ---------------- .../Resources/xlf/Strings.shared.tr.xlf | 349 ---------------- .../Resources/xlf/Strings.shared.zh-Hans.xlf | 349 ---------------- .../Resources/xlf/Strings.shared.zh-Hant.xlf | 349 ---------------- src/MSBuildTaskHost/TaskLoader.cs | 20 +- src/MSBuildTaskHost/TypeLoader.cs | 2 +- .../Utilities/ErrorUtilities.cs | 112 +++--- .../Utilities/FileUtilities.cs | 2 +- src/MSBuildTaskHost/Utilities/Modifiers.cs | 7 +- .../Utilities/ResourceUtilities.cs | 314 --------------- 44 files changed, 1100 insertions(+), 5409 deletions(-) delete mode 100644 src/MSBuildTaskHost/Resources/AssemblyResources.cs create mode 100644 src/MSBuildTaskHost/Resources/SR.resx delete mode 100644 src/MSBuildTaskHost/Resources/Strings.shared.resx create mode 100644 src/MSBuildTaskHost/Resources/xlf/SR.cs.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/SR.de.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/SR.es.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/SR.fr.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/SR.it.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/SR.ja.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/SR.ko.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/SR.pl.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/SR.pt-BR.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/SR.ru.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/SR.tr.xlf rename src/MSBuildTaskHost/Resources/xlf/{Strings.shared.xlf => SR.xlf} (100%) create mode 100644 src/MSBuildTaskHost/Resources/xlf/SR.zh-Hans.xlf create mode 100644 src/MSBuildTaskHost/Resources/xlf/SR.zh-Hant.xlf delete mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.cs.xlf delete mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.de.xlf delete mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.es.xlf delete mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.fr.xlf delete mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.it.xlf delete mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.ja.xlf delete mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.ko.xlf delete mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.pl.xlf delete mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.pt-BR.xlf delete mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.ru.xlf delete mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.tr.xlf delete mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.zh-Hans.xlf delete mode 100644 src/MSBuildTaskHost/Resources/xlf/Strings.shared.zh-Hant.xlf delete mode 100644 src/MSBuildTaskHost/Utilities/ResourceUtilities.cs diff --git a/src/MSBuildTaskHost/BackEnd/InterningBinaryReader.cs b/src/MSBuildTaskHost/BackEnd/InterningBinaryReader.cs index 8eb0b1a8a25..3e03e92d26b 100644 --- a/src/MSBuildTaskHost/BackEnd/InterningBinaryReader.cs +++ b/src/MSBuildTaskHost/BackEnd/InterningBinaryReader.cs @@ -117,7 +117,7 @@ public override String ReadString() // the actual error seems most likely to be occurring. if (n < 0) { - ErrorUtilities.ThrowInternalError("From calculating based on the memorystream, about to read n = {0}. length = {1}, rawPosition = {2}, readLength = {3}, stringLength = {4}, currPos = {5}.", n, length, rawPosition, readLength, stringLength, currPos); + ErrorUtilities.ThrowInternalError($"From calculating based on the memorystream, about to read n = {n}. length = {length}, rawPosition = {rawPosition}, readLength = {readLength}, stringLength = {stringLength}, currPos = {currPos}."); } memoryStream.Seek(n, SeekOrigin.Current); @@ -132,7 +132,7 @@ public override String ReadString() // See above explanation -- the OutOfRange exception may also be coming from our setting of n here ... if (n < 0) { - ErrorUtilities.ThrowInternalError("From getting the length out of BaseStream.Read directly, about to read n = {0}. readLength = {1}, stringLength = {2}, currPos = {3}", n, readLength, stringLength, currPos); + ErrorUtilities.ThrowInternalError($"From getting the length out of BaseStream.Read directly, about to read n = {n}. readLength = {readLength}, stringLength = {stringLength}, currPos = {currPos}"); } } diff --git a/src/MSBuildTaskHost/BackEnd/LogMessagePacketBase.cs b/src/MSBuildTaskHost/BackEnd/LogMessagePacketBase.cs index 97a28d4a578..10586d7f673 100644 --- a/src/MSBuildTaskHost/BackEnd/LogMessagePacketBase.cs +++ b/src/MSBuildTaskHost/BackEnd/LogMessagePacketBase.cs @@ -489,7 +489,7 @@ protected virtual void WriteEventToStream(BuildEventArgs buildEvent, LoggingEven WriteBuildWarningEventToStream((BuildWarningEventArgs)buildEvent, translator); break; default: - ErrorUtilities.ThrowInternalError("Not Supported LoggingEventType {0}", eventType.ToString()); + ErrorUtilities.ThrowInternalError($"Not Supported LoggingEventType {eventType}"); break; } } diff --git a/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs b/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs index e2943c27e4b..ab86d54fe5d 100644 --- a/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs +++ b/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs @@ -170,7 +170,7 @@ public LinkStatus LinkStatus public void Listen(INodePacketFactory factory) { ErrorUtilities.VerifyThrow(_status == LinkStatus.Inactive, "Link not inactive. Status is {0}", _status); - ErrorUtilities.VerifyThrowArgumentNull(factory, nameof(factory)); + ErrorUtilities.VerifyThrowArgumentNull(factory); _packetFactory = factory; InitializeAsyncPacketThread(); @@ -262,7 +262,7 @@ private void InternalDisconnect() /// The packet to be transmitted. private void EnqueuePacket(INodePacket packet) { - ErrorUtilities.VerifyThrowArgumentNull(packet, nameof(packet)); + ErrorUtilities.VerifyThrowArgumentNull(packet); ErrorUtilities.VerifyThrow(_packetQueue != null, "packetQueue is null"); ErrorUtilities.VerifyThrow(_packetAvailable != null, "packetAvailable is null"); _packetQueue.Enqueue(packet); @@ -640,7 +640,7 @@ private void RunReadLoop( break; default: - ErrorUtilities.ThrowInternalError("waitId {0} out of range.", waitId); + ErrorUtilities.ThrowInternalError($"waitId {waitId} out of range."); break; } } diff --git a/src/MSBuildTaskHost/BackEnd/NodePacketFactory.cs b/src/MSBuildTaskHost/BackEnd/NodePacketFactory.cs index a18e2c97972..5aa2ec6c265 100644 --- a/src/MSBuildTaskHost/BackEnd/NodePacketFactory.cs +++ b/src/MSBuildTaskHost/BackEnd/NodePacketFactory.cs @@ -52,7 +52,7 @@ public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITr // PERF: Not using VerifyThrow to avoid boxing of packetType in the non-error case if (!_packetFactories.TryGetValue(packetType, out PacketFactoryRecord record)) { - ErrorUtilities.ThrowInternalError("No packet handler for type {0}", packetType); + ErrorUtilities.ThrowInternalError($"No packet handler for type {packetType}"); } INodePacket packet = record.DeserializePacket(translator); @@ -67,7 +67,7 @@ public INodePacket DeserializePacket(NodePacketType packetType, ITranslator tran // PERF: Not using VerifyThrow to avoid boxing of packetType in the non-error case if (!_packetFactories.TryGetValue(packetType, out PacketFactoryRecord record)) { - ErrorUtilities.ThrowInternalError("No packet handler for type {0}", packetType); + ErrorUtilities.ThrowInternalError($"No packet handler for type {packetType}"); } return record.DeserializePacket(translator); @@ -81,7 +81,7 @@ public void RoutePacket(int nodeId, INodePacket packet) // PERF: Not using VerifyThrow to avoid boxing of packetType in the non-error case if (!_packetFactories.TryGetValue(packet.Type, out PacketFactoryRecord record)) { - ErrorUtilities.ThrowInternalError("No packet handler for type {0}", packet.Type); + ErrorUtilities.ThrowInternalError($"No packet handler for type {packet.Type}"); } record.RoutePacket(nodeId, packet); diff --git a/src/MSBuildTaskHost/BackEnd/TaskParameter.cs b/src/MSBuildTaskHost/BackEnd/TaskParameter.cs index 43c5118519c..e206faf84a3 100644 --- a/src/MSBuildTaskHost/BackEnd/TaskParameter.cs +++ b/src/MSBuildTaskHost/BackEnd/TaskParameter.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.Security; using Microsoft.Build.Framework; +using Microsoft.Build.TaskHost.Resources; using Microsoft.Build.TaskHost.Utilities; #nullable disable @@ -104,9 +105,9 @@ public TaskParameter(object wrappedParameter) // It's not null or invalid, so it should be a valid parameter type. ErrorUtilities.VerifyThrow( - TaskParameterTypeVerifier.IsValidInputParameter(wrappedParameterType) || TaskParameterTypeVerifier.IsValidOutputParameter(wrappedParameterType), - "How did we manage to get a task parameter of type {0} that isn't a valid parameter type?", - wrappedParameterType); + TaskParameterTypeVerifier.IsValidInputParameter(wrappedParameterType) || TaskParameterTypeVerifier.IsValidOutputParameter(wrappedParameterType), + "How did we manage to get a task parameter of type {0} that isn't a valid parameter type?", + wrappedParameterType); if (wrappedParameterType.IsArray) { @@ -655,7 +656,10 @@ public void SetMetadata(string metadataName, string metadataValue) // Non-derivable metadata can only be set at construction time. // That's why this is IsItemSpecModifier and not IsDerivableItemSpecModifier. - ErrorUtilities.VerifyThrowArgument(!FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(metadataName), "Shared.CannotChangeItemSpecModifiers", metadataName); + ErrorUtilities.VerifyThrowArgument( + !FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(metadataName), + SR.Shared_CannotChangeItemSpecModifiers, + metadataName); _customEscapedMetadata ??= new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -669,7 +673,10 @@ public void SetMetadata(string metadataName, string metadataValue) public void RemoveMetadata(string metadataName) { ErrorUtilities.VerifyThrowArgumentNull(metadataName); - ErrorUtilities.VerifyThrowArgument(!FileUtilities.ItemSpecModifiers.IsItemSpecModifier(metadataName), "Shared.CannotChangeItemSpecModifiers", metadataName); + ErrorUtilities.VerifyThrowArgument( + !FileUtilities.ItemSpecModifiers.IsItemSpecModifier(metadataName), + SR.Shared_CannotChangeItemSpecModifiers, + metadataName); if (_customEscapedMetadata == null) { @@ -703,14 +710,14 @@ public void CopyMetadataTo(ITaskItem destinationItem) { string value = destinationItem.GetMetadata(entry.Key); - if (String.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) { destinationItem.SetMetadata(entry.Key, entry.Value); } } } - if (String.IsNullOrEmpty(originalItemSpec)) + if (string.IsNullOrEmpty(originalItemSpec)) { destinationItem.SetMetadata("OriginalItemSpec", EscapingUtilities.Escape(ItemSpec)); } diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 85b3aacadef..56f0e0b4b02 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -29,6 +29,9 @@ full + + + $(DefineConstants);NET20 @@ -53,9 +56,8 @@ - - $(AssemblyName).Strings.shared.resources - Designer + + true diff --git a/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs b/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs index bd1edc867e8..815201fcc1b 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs @@ -161,7 +161,7 @@ private OutOfProcTaskHostTaskResult InstantiateAndExecuteTask( ? OutOfProcTaskHostTaskResult.Success(finalParameterValues) : OutOfProcTaskHostTaskResult.Failure(finalParameterValues); - void LogErrorDelegate(string taskLocation, int taskLine, int taskColumn, string message, params object[] messageArgs) + void LogErrorDelegate(string taskLocation, int taskLine, int taskColumn, string message) { BuildErrorEventArgs error = new( subcategory: null, @@ -171,7 +171,7 @@ void LogErrorDelegate(string taskLocation, int taskLine, int taskColumn, string columnNumber: taskColumn, endLineNumber: 0, endColumnNumber: 0, - message: ResourceUtilities.FormatString(AssemblyResources.GetString(message), messageArgs), + message, helpKeyword: null, senderName: taskName); diff --git a/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs index 39c0e405869..ebac8961e09 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs @@ -299,7 +299,7 @@ bool IBuildEngine.BuildProjectFile(string projectFileName, string[] targetNames, /// bool IBuildEngine2.BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion) { - LogErrorFromResource("BuildEngineCallbacksInTaskHostUnsupported"); + LogErrorFromResource(SR.BuildEngineCallbacksInTaskHostUnsupported); return false; } @@ -309,7 +309,7 @@ bool IBuildEngine2.BuildProjectFile(string projectFileName, string[] targetNames /// bool IBuildEngine2.BuildProjectFilesInParallel(string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IDictionary[] targetOutputsPerProject, string[] toolsVersion, bool useResultsCache, bool unloadProjectsOnCompletion) { - LogErrorFromResource("BuildEngineCallbacksInTaskHostUnsupported"); + LogErrorFromResource(SR.BuildEngineCallbacksInTaskHostUnsupported); return false; } @@ -758,7 +758,7 @@ private void SetTaskHostEnvironment(IDictionary environment) { if (_updateEnvironmentAndLog) { - LogMessageFromResource(MessageImportance.Low, "ModifyingTaskHostEnvironmentHeader"); + LogMessageFromResource(MessageImportance.Low, SR.ModifyingTaskHostEnvironmentHeader); } updatedEnvironment = new Dictionary(environment, StringComparer.OrdinalIgnoreCase); @@ -768,7 +768,7 @@ private void SetTaskHostEnvironment(IDictionary environment) { if (_updateEnvironmentAndLog) { - LogMessageFromResource(MessageImportance.Low, "ModifyingTaskHostEnvironmentVariable", variable.Key, newValue, environmentValue ?? String.Empty); + LogMessageFromResource(MessageImportance.Low, string.Format(SR.ModifyingTaskHostEnvironmentVariable, variable.Key, newValue, environmentValue ?? string.Empty)); } updatedEnvironment[variable.Key] = newValue; @@ -913,7 +913,7 @@ private void SendBuildEvent(BuildEventArgs e) { // log a warning and bail. This will end up re-calling SendBuildEvent, but we know for a fact // that the warning that we constructed is serializable, so everything should be good. - LogWarningFromResource("ExpectedEventToBeSerializable", e.GetType().Name); + LogWarningFromResource(string.Format(SR.ExpectedEventToBeSerializable, e.GetType().Name)); return; } @@ -925,64 +925,61 @@ private void SendBuildEvent(BuildEventArgs e) /// /// Generates the message event corresponding to a particular resource string and set of args /// - private void LogMessageFromResource(MessageImportance importance, string messageResource, params object[] messageArgs) + private void LogMessageFromResource(MessageImportance importance, string message) { ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration when we're trying to log messages!"); - // Using the CLR 2 build event because this class is shared between MSBuildTaskHost.exe (CLR2) and MSBuild.exe (CLR4+) - BuildMessageEventArgs message = new BuildMessageEventArgs( - ResourceUtilities.FormatString(AssemblyResources.GetString(messageResource), messageArgs), - null, - _currentConfiguration.TaskName, - importance); + BuildMessageEventArgs messageArgs = new( + message, + helpKeyword: null, + _currentConfiguration.TaskName, + importance); - LogMessageEvent(message); + LogMessageEvent(messageArgs); } /// /// Generates the error event corresponding to a particular resource string and set of args /// - private void LogWarningFromResource(string messageResource, params object[] messageArgs) + private void LogWarningFromResource(string message) { ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration when we're trying to log warnings!"); - // Using the CLR 2 build event because this class is shared between MSBuildTaskHost.exe (CLR2) and MSBuild.exe (CLR4+) - BuildWarningEventArgs warning = new BuildWarningEventArgs( - null, - null, - ProjectFileOfTaskNode, - LineNumberOfTaskNode, - ColumnNumberOfTaskNode, - 0, - 0, - ResourceUtilities.FormatString(AssemblyResources.GetString(messageResource), messageArgs), - null, - _currentConfiguration.TaskName); + BuildWarningEventArgs warningArgs = new( + subcategory: null, + code: null, + file: ProjectFileOfTaskNode, + lineNumber: LineNumberOfTaskNode, + columnNumber: ColumnNumberOfTaskNode, + endLineNumber: 0, + endColumnNumber: 0, + message: message, + helpKeyword: null, + senderName: _currentConfiguration.TaskName); - LogWarningEvent(warning); + LogWarningEvent(warningArgs); } /// /// Generates the error event corresponding to a particular resource string and set of args /// - private void LogErrorFromResource(string messageResource) + private void LogErrorFromResource(string message) { ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration when we're trying to log errors!"); - // Using the CLR 2 build event because this class is shared between MSBuildTaskHost.exe (CLR2) and MSBuild.exe (CLR4+) - BuildErrorEventArgs error = new BuildErrorEventArgs( - null, - null, - ProjectFileOfTaskNode, - LineNumberOfTaskNode, - ColumnNumberOfTaskNode, - 0, - 0, - AssemblyResources.GetString(messageResource), - null, - _currentConfiguration.TaskName); - - LogErrorEvent(error); + BuildErrorEventArgs errorArgs = new( + subcategory: null, + code: null, + file: ProjectFileOfTaskNode, + lineNumber: LineNumberOfTaskNode, + columnNumber: ColumnNumberOfTaskNode, + endLineNumber: 0, + endColumnNumber: 0, + message: message, + helpKeyword: null, + senderName: _currentConfiguration.TaskName); + + LogErrorEvent(errorArgs); } } } diff --git a/src/MSBuildTaskHost/Resources/AssemblyResources.cs b/src/MSBuildTaskHost/Resources/AssemblyResources.cs deleted file mode 100644 index 8ec548f86d0..00000000000 --- a/src/MSBuildTaskHost/Resources/AssemblyResources.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Globalization; -using System.Reflection; -using System.Resources; -using Microsoft.Build.TaskHost.Utilities; - -#nullable disable - -namespace Microsoft.Build.TaskHost.Resources -{ - /// - /// This class provides access to the assembly's resources. - /// - internal static class AssemblyResources - { - /// - /// Actual source of the resource string we'll be reading. - /// - private static readonly ResourceManager s_resources = new ResourceManager("MSBuildTaskHost.Strings.Shared", Assembly.GetExecutingAssembly()); - - /// - /// Loads the specified resource string, either from the assembly's primary resources, or its shared resources. - /// - /// This method is thread-safe. - /// The resource string, or null if not found. - internal static string GetString(string name) - { - // NOTE: the ResourceManager.GetString() method is thread-safe - string resource = s_resources.GetString(name, CultureInfo.CurrentUICulture); - - ErrorUtilities.VerifyThrow(resource != null, "Missing resource '{0}'", name); - - return resource; - } - } -} diff --git a/src/MSBuildTaskHost/Resources/SR.resx b/src/MSBuildTaskHost/Resources/SR.resx new file mode 100644 index 00000000000..a602e1ccfa1 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/SR.resx @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + Parameter "{0}" cannot be null. + + + Parameter "{0}" cannot have zero length. + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/Strings.shared.resx b/src/MSBuildTaskHost/Resources/Strings.shared.resx deleted file mode 100644 index 6e29c8e68af..00000000000 --- a/src/MSBuildTaskHost/Resources/Strings.shared.resx +++ /dev/null @@ -1,379 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - MSB4188: Build was canceled. - {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. - - - MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. - {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. - - - Build started. - - - MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". - {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. - - - Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. - - - Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs - - - {0} ({1},{2}) - A file location to be embedded in a string. - - - Making the following modifications to the environment received from the parent node before applying it to the task host: - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - MSB4025: The project file could not be loaded. {0} - {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is - invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is - provided separately to loggers. - LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. - - - MSB4103: "{0}" is not a valid logger verbosity level. - {StrBegin="MSB4103: "} - - - MSB4233: There was an exception while reading the log file: {0} - {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. - - - MSBuild is expecting a valid "{0}" object. - - - MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. - {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. - - - MSB5016: The name "{0}" contains an invalid character "{1}". - {StrBegin="MSB5016: "} - - - "{0}" is a reserved item metadata, and cannot be modified or deleted. - UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. - - - The string "{0}" cannot be converted to a boolean (true/false) value. - - - MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} - {StrBegin="MSB5003: "} - - - MSB5018: Failed to delete the temporary file "{0}". {1} {2} - {StrBegin="MSB5018: "} - - - The item metadata "%({0})" cannot be applied to the path "{1}". {2} - UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. - - - MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. - {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. - - - .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - - - .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. - - - Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. - - - When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} - No Error code because this resource will be used in an exception. The error code is discarded if it is included - - - Could not find directory path: {0} - Directory must exist - - - You do not have access to: {0} - Directory must have access - - - Schema validation - - UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: - "MSBUILD : Schema validation error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - - MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. - {StrBegin="MSB5002: "} - - - Parameter "{0}" cannot be null. - - - Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. - - - Parameter "{0}" cannot have zero length. - - - Parameters "{0}" and "{1}" must have the same number of elements. - - - The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. - - - The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. - LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. - - - MSB5004: The solution file has two projects named "{0}". - {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. - - - MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. - {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. - - - MSB5006: Error parsing project section for project "{0}". The project file name is empty. - {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. - - - MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. - {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. - - - MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. - {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the nested project section in solution file. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. - {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. - - - MSB5010: No file format header found. - {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. - - - MSB5011: Parent project GUID not found in "{0}" project dependency section. - {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. - - - MSB5012: Unexpected end-of-file reached inside "{0}" project section. - {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. - - - MSB5013: Error parsing a project section. - {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. - - - MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. - {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. - - - MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. - {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. - - - Unrecognized solution version "{0}", attempting to continue. - - - Solution file - UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: - "MSBUILD : Solution file error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - MSB5019: The project file is malformed: "{0}". {1} - {StrBegin="MSB5019: "} - - - MSB5020: Could not load the project file: "{0}". {1} - {StrBegin="MSB5020: "} - - - MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. - {StrBegin="MSB5021: "} - - - MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. - {StrBegin="MSB5024: "} - - - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. - - - An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} - - - MSB5025: Json in solution filter file "{0}" is incorrectly formatted. - {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. - - - MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. - {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. - - - MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". - {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. - - - MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. - {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its - attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. - - - MSB6005: Task attempted to log before it was initialized. Message was: {0} - {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. - - - This collection is read-only. - - - The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). - - - MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. - {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. - The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown - LOCALIZATION: "Importance" should not be localized. - High should not be localized. - Normal should not be localized. - Low should not be localized. - - diff --git a/src/MSBuildTaskHost/Resources/xlf/SR.cs.xlf b/src/MSBuildTaskHost/Resources/xlf/SR.cs.xlf new file mode 100644 index 00000000000..bb7a286e7bb --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/SR.cs.xlf @@ -0,0 +1,62 @@ + + + + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: Hostitel úlohy nástroje MSBuild nepodporuje spouštění úloh, které provádějí zpětná volání rozhraní IBuildEngine. Pokud chcete tyto operace provádět, spusťte svou úlohu v hlavním procesu nástroje MSBuild. Úloha bude automaticky provedena v hostiteli úlohy, pokud v deklaraci UsingTask byly použity atributy s hodnotami Runtime nebo Architecture nebo pokud pro volání úlohy byly použity atributy s hodnotami MSBuildRuntime nebo MSBuildArchitecture, které neodpovídají aktuálnímu modulu runtime nebo architektuře nástroje MSBuild. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: Bylo nalezeno konfliktní sestavení pro sestavení úlohy {0} v umístění {1}. + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + Očekávalo se, že typ události {0} bude možné serializovat pomocí serializátoru .NET. Událost nebylo možné serializovat a byla ignorována. + + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Předtím, než bude pro hostitele úlohy použito prostředí přijaté z nadřazeného uzlu, budou provedeny jeho následující úpravy: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Nastavení proměnné {0} na hodnotu {1} místo hodnoty {2} proměnné prostředí nadřazeného uzlu + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + Cesta: {0} překračuje maximální limit pro cestu k OS. Plně kvalifikovaný název souboru musí být kratší než {1} znaků. + + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" jsou vyhrazená metadata položky a nemohou být změněna nebo smazána. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + Metadata položky %({0}) nelze použít na cestu {1}. {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: Úloha {0} byla označena atributem LoadInSeparateAppDomain, není ale odvozena od třídy MarshalByRefObject. Zkontrolujte, jestli je úloha odvozena od třídy MarshalByRefObject nebo AppDomainIsolatedTask. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + Parameter "{0}" cannot be null. + Parametr {0} nesmí mít hodnotu Null. + + + + Parameter "{0}" cannot have zero length. + Parametr {0} nesmí mít nulovou délku. + + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/SR.de.xlf b/src/MSBuildTaskHost/Resources/xlf/SR.de.xlf new file mode 100644 index 00000000000..ce1f99f0250 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/SR.de.xlf @@ -0,0 +1,62 @@ + + + + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: Der MSBuild-Aufgabenhost unterstützt das Ausführen von Aufgaben nicht, die IBuildEngine-Rückrufe tätigen. Führen Sie Ihre Aufgabe stattdessen im MSBuild-Kernprozess aus, wenn Sie diese Vorgänge durchführen möchten. Wenn UsingTask mit einem Runtime- oder Architecture-Wert oder der Aufgabenaufruf mit einem MSBuildRuntime- oder MSBuildArchitecture-Wert attribuiert wurde, der nicht mit der aktuellen Laufzeit oder Architektur von MSBuild übereinstimmt, wird eine Aufgabe automatisch im Aufgabenhost ausgeführt. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: Eine mit der Aufgabenassembly "{0}" in Konflikt stehende Assembly wurde in "{1}" gefunden. + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + Es wurde erwartet, dass der Ereignistyp "{0}" mithilfe des .NET-Serialisierers serialisierbar ist. Das Ereignis war nicht serialisierbar und wurde ignoriert. + + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Es werden folgende vom übergeordneten Knoten empfangene Änderungen an der Umgebung vorgenommen, bevor sie auf den Aufgabenhost angewendet wird: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Festlegen von "{0}" auf "{1}" statt auf den Wert "{2}" der übergeordneten Umgebung. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + Der Pfad "{0}" überschreitet das maximale Pfadlimit des Betriebssystems. Der vollqualifizierte Dateiname muss weniger als {1} Zeichen umfassen. + + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" sind reservierte Elementmetadaten, die nicht geändert oder gelöscht werden können. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + Die %({0})-Elementmetadaten können nicht auf den Pfad "{1}" angewendet werden. {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: Die Aufgabe "{0}" wurde mit dem LoadInSeparateAppDomain-Attribut markiert, ist jedoch nicht von MarshalByRefObject abgeleitet. Stellen Sie sicher, dass die Aufgabe von MarshalByRefObject oder AppDomainIsolatedTask abgeleitet wird. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + Parameter "{0}" cannot be null. + Der Parameter "{0}" darf nicht NULL sein. + + + + Parameter "{0}" cannot have zero length. + Parameter "{0}" darf nicht die Länge NULL haben. + + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/SR.es.xlf b/src/MSBuildTaskHost/Resources/xlf/SR.es.xlf new file mode 100644 index 00000000000..9b8aa0f86df --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/SR.es.xlf @@ -0,0 +1,62 @@ + + + + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: El host de tareas de MSBuild no admite la ejecución de tareas que realizan devoluciones de llamadas de IBuildEngine. Si desea realizar estas operaciones, ejecute la tarea en el proceso de MSBuild principal en su lugar. Se ejecutará una tarea automáticamente en el host de tareas si a UsingTask se le ha agregado un atributo con un valor "Runtime" o "Arquitectura", o bien a la invocación de tareas se le ha agregado un atributo con un valor "MSBuildRuntime" o "MSBuildArchitecture", que no coincide con el runtime o la arquitectura actual de MSBuild. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: Se detectó un ensamblado conflictivo para el ensamblado de tarea "{0}" en "{1}". + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + Se esperaba que el tipo de evento "{0}" fuera serializable con el serializador .NET. El evento no era serializable y se ha omitido. + + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Se están realizando las siguientes modificaciones en el entorno recibido del nodo primario antes de aplicarlo al host de tareas: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Estableciendo '{0}' en '{1}' en lugar del valor del entorno primario, '{2}'. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + La ruta de acceso {0} supera el límite máximo para la ruta de acceso del sistema operativo. El nombre de archivo completo debe ser inferior a {1} caracteres. + + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" es un metadato de elemento reservado y no se puede modificar ni eliminar. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + Los metadatos "%({0})" del elemento no pueden aplicarse a la ruta de acceso "{1}". {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: La tarea "{0}" se marcó con el atributo LoadInSeparateAppDomain, pero esta no deriva de MarshalByRefObject. Asegúrese de que la tarea deriva de MarshalByRefObject o de AppDomainIsolatedTask. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + Parameter "{0}" cannot be null. + El parámetro "{0}" no puede ser NULL. + + + + Parameter "{0}" cannot have zero length. + La longitud del parámetro "{0}" no puede ser cero. + + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/SR.fr.xlf b/src/MSBuildTaskHost/Resources/xlf/SR.fr.xlf new file mode 100644 index 00000000000..aa78715eb49 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/SR.fr.xlf @@ -0,0 +1,62 @@ + + + + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: L'hôte de tâche MSBuild ne prend pas en charge l'exécution de tâches qui effectuent des rappels IBuildEngine. Pour effectuer ces opérations, exécutez plutôt votre tâche dans le processus MSBuild de base. Une tâche s'exécute automatiquement dans l'hôte de tâche si UsingTask a été défini sur la valeur "Runtime" ou "Architecture", ou si l'appel de la tâche a été défini sur la valeur "MSBuildRuntime" ou "MSBuildArchitecture", qui ne correspond pas à l'architecture ou au runtime actuel de MSBuild. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: un assembly en conflit avec l'assembly de tâche "{0}" a été trouvé sur "{1}". + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + Le type d'événement "{0}" devait être sérialisable à l'aide du sérialiseur .NET. L'événement n'était pas sérialisable et a été ignoré. + + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Modifications suivantes en cours sur l'environnement reçu du nœud parent avant son application à l'hôte de tâche : + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Définition de '{0}' sur '{1}' plutôt que sur la valeur de l'environnement parent, '{2}'. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + Le chemin {0} dépasse la limite maximale de chemin du système d'exploitation. Le nom du fichier qualifié complet doit contenir moins de {1} caractères. + + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" est une métadonnée d'élément réservée qui ne peut être ni modifiée ni supprimée. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + Impossible d'appliquer la métadonnée d'élément "%({0})" au chemin d'accès "{1}". {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: la tâche "{0}" a été marquée avec l'attribut LoadInSeparateAppDomain, mais ne dérive pas de MarshalByRefObject. Vérifiez que la tâche dérive de MarshalByRefObject ou de AppDomainIsolatedTask. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + Parameter "{0}" cannot be null. + Le paramètre "{0}" ne peut pas être null. + + + + Parameter "{0}" cannot have zero length. + La longueur du paramètre "{0}" ne peut pas être égale à zéro. + + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/SR.it.xlf b/src/MSBuildTaskHost/Resources/xlf/SR.it.xlf new file mode 100644 index 00000000000..a4cee485754 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/SR.it.xlf @@ -0,0 +1,62 @@ + + + + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: l'host attività MSBuild non supporta l'esecuzione di attività che eseguono callback IBuildEngine. Per eseguire tali operazioni, eseguire l'attività nel processo MSBuild di base. Un'attività verrà automaticamente eseguita nell'host attività se per UsingTask è stato definito un attributo con valore "Runtime" o "Architecture" o se per la chiamata all'attività è stato definito un attributo con valore "MSBuildRuntime" o "MSBuildArchitecture" che non corrisponde al runtime corrente o all'architettura di MSBuild. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: rilevato un assembly in conflitto per l'assembly dell'attività "{0}" in "{1}". + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + È previsto un tipo di evento "{0}" serializzabile con il serializzatore .NET. L'evento non era serializzabile ed è stato ignorato. + + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Le modifiche seguenti verranno apportate all'ambiente ricevuto dal nodo padre prima dell'applicazione all'host attività: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Impostazione di '{0}' su '{1}' anziché sul valore dell'ambiente padre, '{2}'. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + Il percorso {0} supera il limite massimo dei percorsi del sistema operativo. Il nome completo del file deve essere composto da meno di {1}. + + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" è un metadato di un elemento riservato e pertanto non può essere modificato o eliminato. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + Non è possibile applicare i metadati dell'elemento "%({0})" al percorso "{1}". {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: l'attività "{0}" è stata contrassegnata con l'attributo LoadInSeparateAppDomain, ma non deriva da MarshalByRefObject. Verificare che l'attività derivi da MarshalByRefObject o AppDomainIsolatedTask. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + Parameter "{0}" cannot be null. + Il parametro "{0}" non può essere Null. + + + + Parameter "{0}" cannot have zero length. + Il parametro "{0}" non può avere lunghezza zero. + + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/SR.ja.xlf b/src/MSBuildTaskHost/Resources/xlf/SR.ja.xlf new file mode 100644 index 00000000000..9f282723a81 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/SR.ja.xlf @@ -0,0 +1,62 @@ + + + + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: MSBuild タスク ホストは、IBuildEngine コールバックを実行するタスクの実行をサポートしていません。これらの操作を実行する場合は、タスクをコア MSBuild プロセスで実行してください。UsingTask の属性として設定されている "Runtime" または "Architecture" の値、あるいはタスク呼び出しの属性として設定されている "MSBuildRuntime" または "MSBuildArchitecture" の値が MSBuild の現在のランタイムまたはアーキテクチャと一致しない場合、タスクは自動的にタスク ホストで実行されます。 + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: タスク アセンブリ "{0}" に対して競合しているアセンブリが "{1}" で見つかりました。 + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + イベントの種類 "{0}" は .NET シリアライザーを使用してシリアル化可能であることが想定されていましたが、シリアル化可能でなかったため無視されました。 + + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + 親ノードから受け取った環境をタスク ホストに適用する前に、次の変更を行っています: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + '{0}' を親環境の値 '{2}' ではなく '{1}' に設定しています。 + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + パス: {0} は OS のパスの上限を越えています。完全修飾のファイル名は {1} 文字以下にする必要があります。 + + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" は予約された項目メタデータです。変更または削除することはできません。 + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + 項目メタデータ "%({0})" をパス "{1}" に適用できません。{2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: "{0}" タスクに属性 LoadInSeparateAppDomain が設定されていますが、MarshalByRefObject から派生していません。そのタスクが MarshalByRefObject または AppDomainIsolatedTask から派生していることを確認してください。 + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + Parameter "{0}" cannot be null. + パラメーター "{0}" を null にすることはできません。 + + + + Parameter "{0}" cannot have zero length. + パラメーター "{0}" の長さを 0 にすることはできません。 + + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/SR.ko.xlf b/src/MSBuildTaskHost/Resources/xlf/SR.ko.xlf new file mode 100644 index 00000000000..8277e15c84b --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/SR.ko.xlf @@ -0,0 +1,62 @@ + + + + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: MSBuild 작업 호스트는 IBuildEngine 콜백을 수행하는 작업의 실행을 지원하지 않습니다. 이러한 작업을 수행하려면 대신 코어 MSBuild 프로세스에서 작업을 실행하세요. UsingTask에 "Runtime" 또는 "Architecture" 값이 사용되었거나 작업 호출에 MSBuild의 현재 런타임 또는 아키텍처와 일치하지 않는 "MSBuildRuntime" 또는 "MSBuildArchitecture" 값이 사용된 경우 작업 호스트에서 작업이 자동으로 실행됩니다. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: 작업 어셈블리 "{0}"과(와) 충돌하는 어셈블리가 "{1}"에 있습니다. + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + 이벤트 유형 "{0}"은(는) .NET serializer를 사용하여 serialize할 수 있어야 합니다. 이 이벤트는 serialize할 수 없으므로 무시되었습니다. + + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + 작업 호스트에 적용하기 전에 부모 노드로부터 받은 환경을 다음과 같이 수정하고 있습니다. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + '{0}'을(를) 부모 환경 값인 '{2}' 대신 '{1}'(으)로 설정하고 있습니다. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + 경로: {0}은(는) OS 최대 경로 제한을 초과합니다. 정규화된 파일 이름은 {1}자 이하여야 합니다. + + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}"은(는) 예약된 항목 메타데이터이므로 수정 또는 삭제할 수 없습니다. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + 항목 메타데이터 "%({0})"을(를) "{1}" 경로에 적용할 수 없습니다. {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: "{0}" 작업이 LoadInSeparateAppDomain 특성으로 표시되었지만 MarshalByRefObject에서 파생되지 않습니다. 해당 작업이 MarshalByRefObject 또는 AppDomainIsolatedTask에서 파생되는지 확인하십시오. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + Parameter "{0}" cannot be null. + "{0}" 매개 변수는 null일 수 없습니다. + + + + Parameter "{0}" cannot have zero length. + "{0}" 매개 변수의 길이는 0일 수 없습니다. + + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/SR.pl.xlf b/src/MSBuildTaskHost/Resources/xlf/SR.pl.xlf new file mode 100644 index 00000000000..e6137d07284 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/SR.pl.xlf @@ -0,0 +1,62 @@ + + + + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: Host zadań programu MSBuild nie obsługuje zadań wykonujących wywołania zwrotne do aparatu IBuildEngine. Jeśli chcesz wykonywać te operacje, uruchom zadanie w podstawowym procesie programu MSBuild. Zadanie będzie automatycznie wykonywane na hoście zadań, jeśli w deklaracji UsingTask ustawiono wartość „Runtime” lub „Architecture” albo w wywołaniu zadania ustawiono wartość „MSBuildRuntime” lub „MSBuildArchitecture”, która nie odpowiada bieżącemu środowisku uruchomieniowemu lub architekturze programu MSBuild. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: Zestaw, który wywołuje konflikt z zestawem zadania „{0}”, został znaleziony w „{1}”. + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + Oczekiwano, że zdarzenie typu „{0}” będzie uszeregowane przy użyciu serializatora platformy .NET. Zdarzenie nie może podlegać szeregowaniu, dlatego zostało zignorowane. + + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Wymienione zmiany otrzymane z węzła nadrzędnego zostaną wprowadzone w środowisku, a po sprawdzeniu działania zastosowane do hosta zadań: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Ustawienie dla elementu „{0}” wartości „{1}” zamiast wartości „{2}” obowiązującej w środowisku nadrzędnym. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + Ścieżka: {0} przekracza limit maksymalnej długości ścieżki w systemie operacyjnym. W pełni kwalifikowana nazwa pliku musi się składać z mniej niż {1} znaków. + + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + „{0}” jest zastrzeżonym elementem metadanych i nie może zostać zmodyfikowany ani usunięty. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + Elementu metadanych „%({0})” nie można zastosować do ścieżki „{1}”. {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: Zadanie „{0}” zostało oznaczone atrybutem LoadInSeparateAppDomain, ale nie pochodzi od obiektu MarshalByRefObject. Sprawdź, czy zadanie pochodzi od obiektu MarshalByRefObject lub zadania AppDomainIsolatedTask. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + Parameter "{0}" cannot be null. + Parametr „{0}” nie może być zerowy. + + + + Parameter "{0}" cannot have zero length. + Parametr „{0}” nie może mieć zerowej długości. + + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/SR.pt-BR.xlf b/src/MSBuildTaskHost/Resources/xlf/SR.pt-BR.xlf new file mode 100644 index 00000000000..727f4447193 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/SR.pt-BR.xlf @@ -0,0 +1,62 @@ + + + + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: O host de tarefas MSBuild não dá suporte a tarefas em execução que executam chamadas de retorno de IBuildEngine. Se desejar executar essas operações, execute sua tarefa no processo MSBuild principal. Uma tarefa será automaticamente executada no host de tarefas se UsingTask tiver como atributo um valor "Runtime" ou "Architecture" ou se a invocação da tarefa tiver como atributo um valor "MSBuildRuntime" ou "MSBuildArchitecture", que não coincida com o tempo de execução ou arquitetura atual do MSBuild. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: Foi encontrado um assembly conflitante no assembly da tarefa "{0}" em "{1}". + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + Era esperado que o tipo de evento "{0}" fosse serializável usando o serializador .NET. O evento não era serializável e foi ignorado. + + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Fazendo as seguintes modificações no ambiente recebido do nó pai antes de aplicá-lo ao host de tarefas: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Definindo "{0}" como "{1}" em vez do valor do ambiente pai, "{2}". + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + Caminho: {0} excede o limite máximo do caminho do SO. O nome do arquivo totalmente qualificado deve ter menos de {1} caracteres. + + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" são metadados de item reservado e não podem ser modificados ou excluídos. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + Os metadados do item "%({0})" não podem ser aplicados ao caminho "{1}". {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: A tarefa "{0}" foi marcada com o atributo LoadInSeparateAppDomain, mas não é derivada de MarshalByRefObject. Verifique se a tarefa é derivada de MarshalByRefObject ou de AppDomainIsolatedTask. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + Parameter "{0}" cannot be null. + O parâmetro "{0}" não pode ser nulo. + + + + Parameter "{0}" cannot have zero length. + O parâmetro "{0}" não pode ter comprimento zero. + + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/SR.ru.xlf b/src/MSBuildTaskHost/Resources/xlf/SR.ru.xlf new file mode 100644 index 00000000000..b405bcbdb3f --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/SR.ru.xlf @@ -0,0 +1,62 @@ + + + + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: узел сборки MSBuild не поддерживает задачи, которые выполняют обратные вызовы IBuildEngine. Если необходимо выполнить эти операции, запустите задачу в основном процессе MSBuild. Задача будет автоматически выполнена в узле задач, если для UsingTask был указан атрибут со значением "Runtime" или "Architecture" или для вызова задачи был указан атрибут со значением "MSBuildRuntime" или "MSBuildArchitecture", которые не соответствуют текущей среде выполнения или архитектуре MSBuild. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: в "{1}" обнаружена сборка, конфликтующая со сборкой задачи "{0}". + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + Необходимо, чтобы тип события "{0}" был сериализуемым с помощью сериализатора .NET. Событие не было сериализуемым и было пропущено. + + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Перед применением окружения, полученного от родительского узла, к серверу задач в нем выполняются следующие изменения: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + Задание для "{0}" значения "{1}", а не значения родительского окружения "{2}". + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + Длина пути {0} превышает максимально допустимую в ОС. Символов в полном имени файла должно быть не больше {1}. + + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" - зарезервированные метаданные элемента; их изменение или удаление невозможно. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + Не удается применить метаданные элемента "%({0})" к пути "{1}". {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: задача "{0}" помечена атрибутом LoadInSeparateAppDomain, но не является производной от MarshalByRefObject. Задача должна быть производной от MarshalByRefObject или AppDomainIsolatedTask. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + Parameter "{0}" cannot be null. + Параметр "{0}" не может иметь неопределенное (null) значение. + + + + Parameter "{0}" cannot have zero length. + Длина параметра "{0}" не может быть равна нулю. + + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/SR.tr.xlf b/src/MSBuildTaskHost/Resources/xlf/SR.tr.xlf new file mode 100644 index 00000000000..5628d348936 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/SR.tr.xlf @@ -0,0 +1,62 @@ + + + + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: MSBuild görev konağı IBuildEngine geri çağırmaları gerçekleştiren görevleri çalıştırmayı desteklemez. Bu işlemleri gerçekleştirmek istiyorsanız lütfen bunun yerine görevinizi çekirdek MSBuild işleminde gerçekleştirin. UsingTask öğesine bir "Runtime" veya "Architecture" değeri atfedilmişse veya görev çağrısına MSBuild’in geçerli çalışma zamanı ya da mimarisi ile eşleşmeyen bir "MSBuildRuntime" veya "MSBuildArchitecture" değeri atfedilmişse görev konağında bir görev otomatik olarak yürütülür. + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: "{0}" görev derlemesi için "{1}" konumunda çakışan derleme bulundu. + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + "{0}" olay türünün .NET serileştiricisi kullanılarak serileştirilebilir olması bekleniyordu. Olay serileştirilebilir değildi ve yoksayıldı. + + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + Üst düğümden alınan ortam görev ana bilgisayarına uygulanmadan önce ortamda aşağıdaki değişiklikler yapılıyor: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + '{0}', üst ortamında değeri olan '{2}' yerine '{1}' değerine ayarlanıyor. + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + Yol: {0}, işletim sisteminin en yüksek yol sınırını aşıyor. Tam dosya adı en fazla {1} karakter olmalıdır. + + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" ayrılmış bir öğe meta verisi olduğu için değiştirilemez veya silinemez. + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + "%({0})" öğe meta verisi "{1}" yoluna uygulanamıyor. {2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: "{0}" görevi LoadInSeparateAppDomain özniteliğiyle işaretlenmiş, ancak MarshalByRefObject öğesinden türetilmiyor. Görevin MarshalByRefObject veya AppDomainIsolatedTask öğesinden türetildiğini denetleyin. + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + Parameter "{0}" cannot be null. + "{0}" parametresi null olamaz. + + + + Parameter "{0}" cannot have zero length. + "{0}" parametresinin uzunluğu sıfır olamaz. + + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.xlf b/src/MSBuildTaskHost/Resources/xlf/SR.xlf similarity index 100% rename from src/MSBuildTaskHost/Resources/xlf/Strings.shared.xlf rename to src/MSBuildTaskHost/Resources/xlf/SR.xlf diff --git a/src/MSBuildTaskHost/Resources/xlf/SR.zh-Hans.xlf b/src/MSBuildTaskHost/Resources/xlf/SR.zh-Hans.xlf new file mode 100644 index 00000000000..2c1ed366f5b --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/SR.zh-Hans.xlf @@ -0,0 +1,62 @@ + + + + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: MSBuild 任务宿主不支持运行执行 IBuildEngine 回调的任务。如果需要执行这些操作,请改为在核心 MSBuild 进程中运行您的任务。如果 UsingTask 已用“Runtime”或“Architecture”值特性化,或者任务调用已用“MSBuildRuntime”或“MSBuildArchitecture”值(与 MSBuild 的当前运行时或体系结构不匹配)特性化,则将自动在任务宿主中执行任务。 + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: 在“{1}”处发现了与任务程序集“{0}”冲突的程序集。 + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + 事件类型“{0}”应可以使用 .NET 序列化程序进行序列化。此事件不可序列化,已忽略它。 + + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + 先对从父节点收到的环境进行以下修改,然后再将其应用于任务宿主: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + 将“{0}”设置为“{1}”,而不是父环境的值“{2}”。 + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + 路径: {0} 超过 OS 最大路径限制。完全限定的文件名必须少于 {1} 个字符。 + + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + “{0}”是保留的项元数据,不能修改或删除。 + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + 无法将项元数据“%({0})”应用于路径“{1}”。{2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: “{0}”任务已标记为 LoadInSeparateAppDomain 特性,但未派生自 MarshalByRefObject。请检查该任务是派生自 MarshalByRefObject 还是 AppDomainIsolatedTask。 + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + Parameter "{0}" cannot be null. + 参数“{0}”不能为 null。 + + + + Parameter "{0}" cannot have zero length. + 参数“{0}”的长度不能为零。 + + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/SR.zh-Hant.xlf b/src/MSBuildTaskHost/Resources/xlf/SR.zh-Hant.xlf new file mode 100644 index 00000000000..43f6f0bb8b0 --- /dev/null +++ b/src/MSBuildTaskHost/Resources/xlf/SR.zh-Hant.xlf @@ -0,0 +1,62 @@ + + + + + + MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. + MSB5022: MSBuild 工作主機不支援會執行 IBuildEngine 回撥的工作。若您想要執行這些作業,請改為在核心 MSBuild 處理序執行您的工作。若 UsingTask 已由 "Runtime" 或 "Architecture" 值賦予屬性,或工作引動過程已由 "MSBuildRuntime" 或 "MSBuildArchitecture" 值賦予屬性 (不符合 MSBuild 的目前執行階段或結構),則工作將自動在工作主機中執行。 + {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. + + + MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". + MSB4008: 已在 "{1}" 中發現工作組件 "{0}" 的衝突組件。 + {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. + + + Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. + 事件類型 "{0}" 應該可以使用 .NET 序列化程式序列化。此事件不可序列化,已略過。 + + + + Making the following modifications to the environment received from the parent node before applying it to the task host: + 在套用到工作主機之前,對從父節點接收的環境進行下列修改: + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. + 將 '{0}' 設定為 '{1}' 而非父環境的值 '{2}。 + Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 + + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + 路徑: {0} 超過 OS 路徑上限。完整檔案名稱必須少於 {1} 個字元。 + + + + "{0}" is a reserved item metadata, and cannot be modified or deleted. + "{0}" 是保留的項目中繼資料,不能修改或刪除。 + UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. + + + The item metadata "%({0})" cannot be applied to the path "{1}". {2} + 無法將項目中繼資料 "%({0})" 套用至路徑 "{1}"。{2} + UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. + + + MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. + MSB4077: "{0}" 工作已經以屬性 LoadInSeparateAppDomain 標記,但不是衍生自 MarshalByRefObject。請檢查工作是否衍生自 MarshalByRefObject 或 AppDomainIsolatedTask。 + {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. + + + Parameter "{0}" cannot be null. + 參數 "{0}" 不能為 null。 + + + + Parameter "{0}" cannot have zero length. + 參數 "{0}" 長度不能為零。 + + + + + \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.cs.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.cs.xlf deleted file mode 100644 index efc33efebe0..00000000000 --- a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.cs.xlf +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - MSB4188: Build was canceled. - MSB4188: Sestavování bylo zrušeno. - {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. - - - MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. - MSB5022: Hostitel úlohy nástroje MSBuild nepodporuje spouštění úloh, které provádějí zpětná volání rozhraní IBuildEngine. Pokud chcete tyto operace provádět, spusťte svou úlohu v hlavním procesu nástroje MSBuild. Úloha bude automaticky provedena v hostiteli úlohy, pokud v deklaraci UsingTask byly použity atributy s hodnotami Runtime nebo Architecture nebo pokud pro volání úlohy byly použity atributy s hodnotami MSBuildRuntime nebo MSBuildArchitecture, které neodpovídají aktuálnímu modulu runtime nebo architektuře nástroje MSBuild. - {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. - - - Build started. - Vytváření sestavení bylo zahájeno. - - - - The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). - Počet elementů v kolekci je větší než dostupné místo v cílovém poli (při spuštění na zadaném indexu). - - - - MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". - MSB4008: Bylo nalezeno konfliktní sestavení pro sestavení úlohy {0} v umístění {1}. - {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. - - - Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs - Vlastní typ události '{0}' se nepodporuje, protože všechny vlastní typy událostí jsou zastaralé. Použijte prosím místo toho Extended*EventArgs. Další informace: https://aka.ms/msbuild/eventargs - - - - Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. - Očekávalo se, že typ události {0} bude možné serializovat pomocí serializátoru .NET. Událost nebylo možné serializovat a byla ignorována. - - - - {0} ({1},{2}) - {0} ({1},{2}) - A file location to be embedded in a string. - - - MSB6005: Task attempted to log before it was initialized. Message was: {0} - MSB6005: Úloha se pokusila přihlásit před tím, než byla inicializována. Zpráva: {0} - {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. - - - MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. - MSB3511: {0} je neplatná hodnota parametru Importance. Platné hodnoty: High, Normal a Low. - {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. - The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown - LOCALIZATION: "Importance" should not be localized. - High should not be localized. - Normal should not be localized. - Low should not be localized. - - - Making the following modifications to the environment received from the parent node before applying it to the task host: - Předtím, než bude pro hostitele úlohy použito prostředí přijaté z nadřazeného uzlu, budou provedeny jeho následující úpravy: - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. - Nastavení proměnné {0} na hodnotu {1} místo hodnoty {2} proměnné prostředí nadřazeného uzlu - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - MSB4025: The project file could not be loaded. {0} - MSB4025: Soubor projektu nelze načíst. {0} - {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is - invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is - provided separately to loggers. - LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. - - - MSB4103: "{0}" is not a valid logger verbosity level. - MSB4103: {0} není platná úroveň podrobností protokolovacího nástroje. - {StrBegin="MSB4103: "} - - - MSBuild is expecting a valid "{0}" object. - Nástroj MSBuild očekává platný objekt {0}. - - - - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. - Cesta: {0} překračuje maximální limit pro cestu k OS. Plně kvalifikovaný název souboru musí být kratší než {1} znaků. - - - - MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". - MSB5028: Soubor filtru řešení v {0} obsahuje projekt {1}, který není v souboru řešení v {2}. - {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. - - - MSB5025: Json in solution filter file "{0}" is incorrectly formatted. - MSB5025: JSON v souboru filtru řešení {0} má nesprávný formát. - {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. - - - MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. - MSB5026: Soubor filtru řešení v {0} určuje, že v {1} se bude nacházet soubor řešení, ale tento soubor neexistuje. - {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. - MSB5009: Při analýze oddílu {0} projektu s identifikátorem GUID: {1} došlo k chybě. Je vnořený pod {2}, ale tento projekt nebyl v řešení nalezen. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. - MSB4132: Verze nástrojů {0} je neznámá. Dostupné verze nástrojů jsou {1}. - {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. - - - MSB5016: The name "{0}" contains an invalid character "{1}". - MSB5016: Název {0} obsahuje neplatný znak {1}. - {StrBegin="MSB5016: "} - - - "{0}" is a reserved item metadata, and cannot be modified or deleted. - "{0}" jsou vyhrazená metadata položky a nemohou být změněna nebo smazána. - UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. - - - The string "{0}" cannot be converted to a boolean (true/false) value. - Řetězec {0} nelze převést na logickou hodnotu (true/false). - - - - MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} - MSB5003: Vytvoření dočasného souboru se nepovedlo. Složka dočasných souborů je plná nebo cesta k této složce není správná. {0} - {StrBegin="MSB5003: "} - - - MSB5018: Failed to delete the temporary file "{0}". {1} {2} - MSB5018: Nepodařilo se odstranit dočasný soubor {0}. {1} {2} - {StrBegin="MSB5018: "} - - - The item metadata "%({0})" cannot be applied to the path "{1}". {2} - Metadata položky %({0}) nelze použít na cestu {1}. {2} - UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. - - - MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. - MSB4077: Úloha {0} byla označena atributem LoadInSeparateAppDomain, není ale odvozena od třídy MarshalByRefObject. Zkontrolujte, jestli je úloha odvozena od třídy MarshalByRefObject nebo AppDomainIsolatedTask. - {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. - - - .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - Verze {0} rozhraní .NET Framework není podporovaná. Zadejte hodnotu z výčtu Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - - - - .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. - Verze {0} rozhraní .NET Framework není podporovaná, pokud je jako cíl explicitně určená sada Windows SDK, která je podporovaná jenom v rozhraní .NET 4.5 nebo novějším. Určete hodnotu Version45 nebo vyšší z výčtu Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - - - - Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. - Verze {0} sady Visual Studio není podporovaná. Zadejte hodnotu z výčtu Microsoft.Build.Utilities.VisualStudioVersion. - - - - When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} - Při pokusu o vygenerování cesty odkazovaného sestavení z cesty {0} a monikeru rozhraní {1} došlo k chybě. {2} - No Error code because this resource will be used in an exception. The error code is discarded if it is included - - - Could not find directory path: {0} - Nebyla nalezena cesta k adresáři: {0} - Directory must exist - - - You do not have access to: {0} - Nemáte přístup k objektu {0}. - Directory must have access - - - Schema validation - Ověřování schématu - - UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: - "MSBUILD : Schema validation error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - - MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. - MSB5002: Spustitelný soubor úlohy {0} se ukončuje, protože nebyl dokončen ve stanoveném limitu {1} milisekund. - {StrBegin="MSB5002: "} - - - Parameter "{0}" cannot be null. - Parametr {0} nesmí mít hodnotu Null. - - - - Parameter "{0}" cannot have zero length. - Parametr {0} nesmí mít nulovou délku. - - - - Parameters "{0}" and "{1}" must have the same number of elements. - Parametry {0} a {1} musí mít stejný počet prvků. - - - - The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. - Řetězec prostředku {0} pro úlohu {1} nebyl nalezen. Zkontrolujte, jestli je název prostředku {0} napsaný správně a jestli tento prostředek existuje v sestavení úlohy. - - - - The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. - Úloha {0} nezaregistrovala své prostředky. Aby bylo možné použít metodu TaskLoggingHelper.FormatResourceString(), musí tato úloha zaregistrovat své prostředky během konstrukce nebo prostřednictvím vlastnosti TaskResources. - LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. - - - MSB5004: The solution file has two projects named "{0}". - MSB5004: Soubor řešení obsahuje dva projekty s názvem {0}. - {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. - - - MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. - MSB5005: Při analýze oddílu projektu pro projekt {0} došlo k chybě. Název souboru projektu {1} obsahuje neplatné znaky. - {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. - - - MSB5006: Error parsing project section for project "{0}". The project file name is empty. - MSB5006: Při analýze oddílu projektu pro projekt {0} došlo k chybě. Název souboru projektu je prázdný. - {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. - - - MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. - MSB5007: Při analýze oddílu konfigurace projektu v souboru řešení došlo k chybě. Položka {0} je neplatná. - {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. - - - MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. - MSB5008: Při analýze oddílu konfigurace řešení v souboru řešení došlo k chybě. Položka {0} je neplatná. - {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the nested project section in solution file. - MSB5009: Při analýze vnořeného oddílu projektu v souboru řešení došlo k chybě. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. - MSB5023: Při analýze vnořeného oddílu projektu v souboru řešení došlo k chybě. Projekt s GUID {0} je uveden jako vnořený pod projektem {1}, ale v daném řešení neexistuje. - {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. - - - MSB5010: No file format header found. - MSB5010: Nebyla nalezena žádná hlavička formátu souboru. - {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. - - - MSB5011: Parent project GUID not found in "{0}" project dependency section. - MSB5011: V oddílu projektových závislostí {0} nebyl nalezen identifikátor GUID nadřazeného projektu. - {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. - - - MSB5012: Unexpected end-of-file reached inside "{0}" project section. - MSB5012: V oddílu projektu {0} bylo dosaženo neočekávaného konce souboru. - {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. - - - MSB5013: Error parsing a project section. - MSB5013: Při analýze oddílu projektu došlo k chybě. - {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. - - - MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. - MSB5014: Nebyla rozpoznána verze formátu souboru. Nástroj MSBuild může číst jenom soubory řešení verzí {0}.0 až {1}.0 včetně. - {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. - - - MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. - MSB5015: Nelze číst vlastnosti z oddílu WebsiteProperties projektu {0}. - {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. - - - Unrecognized solution version "{0}", attempting to continue. - Nerozpoznaná verze řešení {0}, pokus o pokračování. - - - - Solution file - Soubor řešení - UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: - "MSBUILD : Solution file error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - MSB5019: The project file is malformed: "{0}". {1} - MSB5019: Soubor projektu je nesprávně utvořen: {0}. {1} - {StrBegin="MSB5019: "} - - - MSB5020: Could not load the project file: "{0}". {1} - MSB5020: Nepodařilo se načíst soubor projektu: {0}. {1} - {StrBegin="MSB5020: "} - - - MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. - MSB5021: Spustitelný soubor úlohy {0} a její podřízené procesy se ukončují, protože sestavení bylo zrušeno. - {StrBegin="MSB5021: "} - - - This collection is read-only. - Tato kolekce je jen pro čtení. - - - - MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. - MSB5024: Nešlo určit platné umístění pro MSBuild. Zkuste tento proces spustit z nástroje Developer Command Prompt for Visual Studio. - {StrBegin="MSB5024: "} - - - MSB4233: There was an exception while reading the log file: {0} - MSB4233: Došlo k výjimce při čtení souboru protokolu: {0}. - {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. - - - Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. - Parametr {0} s přiřazenou hodnotou {1} nesmí obsahovat neplatnou cestu nebo neplatné znaky souboru. - - - - An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} - Při rozbalování fileSpec s globs: fileSpec došlo k výjimce: {0}, za předpokladu, že se jedná o název souboru. Výjimka: {1} - - - - MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. - MSB5029: Hodnota {0} atributu {1} v elementu <{2}> v souboru {3}je zástupný znak, jehož výsledkem je výčet všech souborů na jednotce, což pravděpodobně nebylo zamýšleno. Zkontrolujte, zda jsou odkazované vlastnosti vždy definovány a zda projekt a aktuální pracovní adresář nejsou v kořenovém adresáři jednotky. - {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its - attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. - - - - \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.de.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.de.xlf deleted file mode 100644 index a63a5c4e29d..00000000000 --- a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.de.xlf +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - MSB4188: Build was canceled. - MSB4188: Der Buildvorgang wurde abgebrochen. - {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. - - - MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. - MSB5022: Der MSBuild-Aufgabenhost unterstützt das Ausführen von Aufgaben nicht, die IBuildEngine-Rückrufe tätigen. Führen Sie Ihre Aufgabe stattdessen im MSBuild-Kernprozess aus, wenn Sie diese Vorgänge durchführen möchten. Wenn UsingTask mit einem Runtime- oder Architecture-Wert oder der Aufgabenaufruf mit einem MSBuildRuntime- oder MSBuildArchitecture-Wert attribuiert wurde, der nicht mit der aktuellen Laufzeit oder Architektur von MSBuild übereinstimmt, wird eine Aufgabe automatisch im Aufgabenhost ausgeführt. - {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. - - - Build started. - Der Buildvorgang wurde gestartet. - - - - The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). - Die Anzahl der Elemente in der Auflistung ist größer als der verfügbare Speicherplatz im Zielarray (wenn am angegebenen Index begonnen wird). - - - - MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". - MSB4008: Eine mit der Aufgabenassembly "{0}" in Konflikt stehende Assembly wurde in "{1}" gefunden. - {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. - - - Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs - Der benutzerdefinierte Ereignistyp '{0}' wird nicht unterstützt, weil alle benutzerdefinierten Ereignistypen veraltet waren. Verwenden Sie stattdessen Extended*EventArgs. Weitere Informationen: https://aka.ms/msbuild/eventargs - - - - Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. - Es wurde erwartet, dass der Ereignistyp "{0}" mithilfe des .NET-Serialisierers serialisierbar ist. Das Ereignis war nicht serialisierbar und wurde ignoriert. - - - - {0} ({1},{2}) - {0} ({1},{2}) - A file location to be embedded in a string. - - - MSB6005: Task attempted to log before it was initialized. Message was: {0} - MSB6005: Die Aufgabe hat versucht, eine Protokollierung durchzuführen, bevor sie initialisiert wurde. Meldung: {0} - {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. - - - MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. - MSB3511: "{0}" ist ein ungültiger Wert für den "Importance"-Parameter. Gültige Werte sind: High, Normal und Low. - {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. - The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown - LOCALIZATION: "Importance" should not be localized. - High should not be localized. - Normal should not be localized. - Low should not be localized. - - - Making the following modifications to the environment received from the parent node before applying it to the task host: - Es werden folgende vom übergeordneten Knoten empfangene Änderungen an der Umgebung vorgenommen, bevor sie auf den Aufgabenhost angewendet wird: - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. - Festlegen von "{0}" auf "{1}" statt auf den Wert "{2}" der übergeordneten Umgebung. - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - MSB4025: The project file could not be loaded. {0} - MSB4025: Die Projektdatei konnte nicht geladen werden. {0} - {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is - invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is - provided separately to loggers. - LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. - - - MSB4103: "{0}" is not a valid logger verbosity level. - MSB4103: "{0}" ist kein gültiger Ausführlichkeitsgrad für die Protokollierung. - {StrBegin="MSB4103: "} - - - MSBuild is expecting a valid "{0}" object. - MSBuild erwartet ein gültiges {0}-Objekt. - - - - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. - Der Pfad "{0}" überschreitet das maximale Pfadlimit des Betriebssystems. Der vollqualifizierte Dateiname muss weniger als {1} Zeichen umfassen. - - - - MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". - MSB5028: Die Projektmappenfilter-Datei unter "{0}" enthält das Projekt "{1}", das in der Projektmappendatei unter "{2}" nicht enthalten ist. - {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. - - - MSB5025: Json in solution filter file "{0}" is incorrectly formatted. - MSB5025: JSON in der Projektmappenfilter-Datei "{0}" ist falsch formatiert. - {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. - - - MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. - MSB5026: Die Projektmappenfilter-Datei unter "{0}" gibt an, dass eine Projektmappendatei unter "{1}" vorhanden ist. Diese Datei ist jedoch nicht vorhanden. - {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. - MSB5009: Fehler beim Analysieren des Projektabschnitts "{0}". GUID: {1}. Er ist unter "{2}" geschachtelt, aber dieses Projekt wurde in der Projektmappe nicht gefunden. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. - MSB4132: Die Toolsversion "{0}" ist unbekannt. Verfügbare Toolversionen sind {1}. - {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. - - - MSB5016: The name "{0}" contains an invalid character "{1}". - MSB5016: Der Name "{0}" enthält das ungültige Zeichen "{1}". - {StrBegin="MSB5016: "} - - - "{0}" is a reserved item metadata, and cannot be modified or deleted. - "{0}" sind reservierte Elementmetadaten, die nicht geändert oder gelöscht werden können. - UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. - - - The string "{0}" cannot be converted to a boolean (true/false) value. - Die Zeichenfolge "{0}" kann nicht in einen booleschen Wert (true/false) konvertiert werden. - - - - MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} - MSB5003: Fehler beim Erstellen einer temporären Datei. Der Ordner für temporäre Dateien ist voll, oder der Pfad ist falsch. {0} - {StrBegin="MSB5003: "} - - - MSB5018: Failed to delete the temporary file "{0}". {1} {2} - MSB5018: Fehler beim Löschen der temporären Datei „{0}“. {1} {2} - {StrBegin="MSB5018: "} - - - The item metadata "%({0})" cannot be applied to the path "{1}". {2} - Die %({0})-Elementmetadaten können nicht auf den Pfad "{1}" angewendet werden. {2} - UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. - - - MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. - MSB4077: Die Aufgabe "{0}" wurde mit dem LoadInSeparateAppDomain-Attribut markiert, ist jedoch nicht von MarshalByRefObject abgeleitet. Stellen Sie sicher, dass die Aufgabe von MarshalByRefObject oder AppDomainIsolatedTask abgeleitet wird. - {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. - - - .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - Die .NET Framework-Version "{0}" wird nicht unterstützt. Geben Sie einen Wert aus der Enumeration "Microsoft.Build.Utilities.TargetDotNetFrameworkVersion" an. - - - - .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. - .NET Framework, Version "{0}" wird nicht unterstützt, wenn das Windows SDK das explizite Ziel ist, da dies nur unter .NET 4.5 und höher unterstützt wird. Geben Sie einen Wert aus der Enumeration "Microsoft.Build.Utilities.TargetDotNetFrameworkVersion", Version45 oder höher an. - - - - Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. - Visual Studio-Version "{0}" wird nicht unterstützt. Geben Sie einen Wert aus der Enumeration "Microsoft.Build.Utilities.VisualStudioVersion" an. - - - - When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} - Fehler beim Versuch, einen Referenzassemblypfad aus dem Pfad "{0}" und dem Frameworkmoniker "{1}" zu generieren. {2} - No Error code because this resource will be used in an exception. The error code is discarded if it is included - - - Could not find directory path: {0} - Der Verzeichnispfad wurde nicht gefunden: {0} - Directory must exist - - - You do not have access to: {0} - Sie haben keinen Zugriff auf: {0} - Directory must have access - - - Schema validation - Schemavalidierung - - UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: - "MSBUILD : Schema validation error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - - MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. - MSB5002: Die ausführbare Datei der Aufgabe "{0}" wird beendet, weil sie nicht in der vorgegebenen Zeit von {1} Millisekunden abgeschlossen wurde. - {StrBegin="MSB5002: "} - - - Parameter "{0}" cannot be null. - Der Parameter "{0}" darf nicht NULL sein. - - - - Parameter "{0}" cannot have zero length. - Parameter "{0}" darf nicht die Länge NULL haben. - - - - Parameters "{0}" and "{1}" must have the same number of elements. - Die Parameter "{0}" und "{1}" müssen die gleiche Anzahl von Elementen enthalten. - - - - The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. - Die Ressourcenzeichenfolge "{0}" für die Aufgabe "{1}" konnte nicht gefunden werden. Vergewissern Sie sich, dass der Ressourcenname "{0}" richtig geschrieben wurde und die Ressource in der Assembly der Aufgabe enthalten ist. - - - - The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. - Die Aufgabe "{0}" hat ihre Ressourcen nicht registriert. Wenn die TaskLoggingHelper.FormatResourceString()-Methode verwendet werden soll, muss diese Aufgabe ihre Ressourcen während der Konstruktion oder über die Eigenschaft "TaskResources" registrieren. - LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. - - - MSB5004: The solution file has two projects named "{0}". - MSB5004: Die Projektmappendatei enthält zwei Projekte mit dem Namen "{0}". - {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. - - - MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. - MSB5005: Fehler beim Analysieren des Projektabschnitts für das Projekt "{0}". Der Projektdateiname "{1}" enthält ungültige Zeichen. - {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. - - - MSB5006: Error parsing project section for project "{0}". The project file name is empty. - MSB5006: Fehler beim Analysieren des Projektabschnitts für das Projekt "{0}". Der Projektdateiname ist leer. - {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. - - - MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. - MSB5007: Fehler beim Analysieren des Projektkonfigurationsabschnitts in der Projektmappendatei. Der Eintrag "{0}" ist ungültig. - {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. - - - MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. - MSB5008: Fehler beim Analysieren des Projektmappen-Konfigurationsabschnitts in der Projektmappendatei. Der Eintrag "{0}" ist ungültig. - {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the nested project section in solution file. - MSB5009: Fehler beim Analysieren des geschachtelten Projektabschnitts in der Projektmappendatei. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. - MSB5023: Fehler beim Analysieren des geschachtelten Projektabschnitts in der Projektmappendatei. Ein Projekt mit GUID "{0}" ist als unter Projekt "{1}" geschachtelt aufgeführt, in der Projektmappe jedoch nicht vorhanden. - {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. - - - MSB5010: No file format header found. - MSB5010: Es wurde kein Dateiformatheader gefunden. - {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. - - - MSB5011: Parent project GUID not found in "{0}" project dependency section. - MSB5011: Die GUID des übergeordneten Projekts wurde im Projektabhängigkeitsabschnitt "{0}" nicht gefunden. - {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. - - - MSB5012: Unexpected end-of-file reached inside "{0}" project section. - MSB5012: Unerwartetes Dateiende innerhalb des Projektabschnitts "{0}" erreicht. - {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. - - - MSB5013: Error parsing a project section. - MSB5013: Fehler beim Analysieren eines Projektabschnitts. - {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. - - - MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. - MSB5014: Die Dateiformatversion wird nicht erkannt. MSBuild kann nur Projektmappendateien der Versionen {0}.0 bis einschließlich {1}.0 lesen. - {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. - - - MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. - MSB5015: Die Eigenschaften konnten nicht aus dem Abschnitt "WebsiteProperties" des Projekts "{0}" gelesen werden. - {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. - - - Unrecognized solution version "{0}", attempting to continue. - Unbekannte Projektmappenversion {0}. Es wird versucht, den Vorgang fortzusetzen. - - - - Solution file - Projektmappendatei - UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: - "MSBUILD : Solution file error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - MSB5019: The project file is malformed: "{0}". {1} - MSB5019: Die Projektdatei ist falsch formatiert: "{0}". {1} - {StrBegin="MSB5019: "} - - - MSB5020: Could not load the project file: "{0}". {1} - MSB5020: Die Projektdatei konnte nicht geladen werden: "{0}". {1} - {StrBegin="MSB5020: "} - - - MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. - MSB5021: Die ausführbare Datei der Aufgabe "{0}" und zugehörige Prozesse werden beendet, weil die Builderstellung abgebrochen wurde. - {StrBegin="MSB5021: "} - - - This collection is read-only. - Diese Sammlung ist schreibgeschützt. - - - - MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. - MSB5024: Es wurde kein gültiger Speicherort für MSBuild ermittelt. Versuchen Sie, den Prozess über die Entwicklereingabeaufforderung für Visual Studio auszuführen. - {StrBegin="MSB5024: "} - - - MSB4233: There was an exception while reading the log file: {0} - MSB4233: Beim Lesen der Protokolldatei ist eine Ausnahme aufgetreten: {0}. - {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. - - - Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. - Der Parameter "{0}" mit dem zugewiesenen Wert "{1}" darf keinen ungültigen Pfad und keine ungültigen Dateizeichen haben. - - - - An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} - Eine Ausnahme ist beim Erweitern einer fileSpec mit Globs aufgetreten: fileSpec: „{0}“, angenommen, es handelt sich um einen Dateinamen. Ausnahme: {1} - - - - MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. - MSB5029: Der Wert „{0}“ des Attributs „{1}“ in Element <{2}> in der Datei „{3}“ ist ein Platzhalter, der dazu führt, dass alle Dateien auf dem Laufwerk aufgelistet werden, was wahrscheinlich nicht beabsichtigt war. Überprüfen Sie, ob die referenzierten Eigenschaften immer definiert sind und dass sich das Projekt und das aktuelle Arbeitsverzeichnis nicht im Laufwerkstamm befinden. - {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its - attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. - - - - \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.es.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.es.xlf deleted file mode 100644 index b7936f94976..00000000000 --- a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.es.xlf +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - MSB4188: Build was canceled. - MSB4188: Se canceló la compilación. - {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. - - - MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. - MSB5022: El host de tareas de MSBuild no admite la ejecución de tareas que realizan devoluciones de llamadas de IBuildEngine. Si desea realizar estas operaciones, ejecute la tarea en el proceso de MSBuild principal en su lugar. Se ejecutará una tarea automáticamente en el host de tareas si a UsingTask se le ha agregado un atributo con un valor "Runtime" o "Arquitectura", o bien a la invocación de tareas se le ha agregado un atributo con un valor "MSBuildRuntime" o "MSBuildArchitecture", que no coincide con el runtime o la arquitectura actual de MSBuild. - {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. - - - Build started. - Compilación iniciada. - - - - The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). - El número de elementos de la colección es mayor que el espacio disponible en la matriz de destino (a partir del índice especificado). - - - - MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". - MSB4008: Se detectó un ensamblado conflictivo para el ensamblado de tarea "{0}" en "{1}". - {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. - - - Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs - No se admite el tipo de evento personalizado '{0}' porque todos los tipos de eventos personalizados estaban en desuso. Use Extended*EventArgs en su lugar. Más información: https://aka.ms/msbuild/eventargs - - - - Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. - Se esperaba que el tipo de evento "{0}" fuera serializable con el serializador .NET. El evento no era serializable y se ha omitido. - - - - {0} ({1},{2}) - {0} ({1},{2}) - A file location to be embedded in a string. - - - MSB6005: Task attempted to log before it was initialized. Message was: {0} - MSB6005: La tarea intentó registrarse antes de inicializarse. El mensaje era: {0} - {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. - - - MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. - MSB3511: "{0}" no es un valor válido para el parámetro "Importance". Los valores válidos son: High, Normal y Low. - {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. - The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown - LOCALIZATION: "Importance" should not be localized. - High should not be localized. - Normal should not be localized. - Low should not be localized. - - - Making the following modifications to the environment received from the parent node before applying it to the task host: - Se están realizando las siguientes modificaciones en el entorno recibido del nodo primario antes de aplicarlo al host de tareas: - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. - Estableciendo '{0}' en '{1}' en lugar del valor del entorno primario, '{2}'. - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - MSB4025: The project file could not be loaded. {0} - MSB4025: No se pudo cargar el archivo del proyecto. {0} - {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is - invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is - provided separately to loggers. - LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. - - - MSB4103: "{0}" is not a valid logger verbosity level. - MSB4103: "{0}" no es un nivel de detalle del registrador. - {StrBegin="MSB4103: "} - - - MSBuild is expecting a valid "{0}" object. - MSBuild espera un objeto "{0}" válido. - - - - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. - La ruta de acceso {0} supera el límite máximo para la ruta de acceso del sistema operativo. El nombre de archivo completo debe ser inferior a {1} caracteres. - - - - MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". - MSB5028: El archivo de filtro de soluciones en "{0}" incluye el proyecto "{1}", que no está en el archivo de solución en "{2}". - {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. - - - MSB5025: Json in solution filter file "{0}" is incorrectly formatted. - MSB5025: El formato JSON del archivo de filtro de soluciones "{0}" no es correcto. - {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. - - - MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. - MSB5026: El archivo de filtro de soluciones en "{0}" especifica que habrá un archivo de solución en "{1}", pero ese archivo no existe. - {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. - MSB5009: Error al analizar la sección "{0}" del proyecto con el GUID "{1}". Está anidado en "{2}", pero ese proyecto no se encuentra en la solución. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. - MSB4132: No se reconoce la versión de herramientas "{0}". Las versiones de herramientas disponibles son {1}. - {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. - - - MSB5016: The name "{0}" contains an invalid character "{1}". - MSB5016: El nombre "{0}" contiene un carácter no válido "{1}". - {StrBegin="MSB5016: "} - - - "{0}" is a reserved item metadata, and cannot be modified or deleted. - "{0}" es un metadato de elemento reservado y no se puede modificar ni eliminar. - UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. - - - The string "{0}" cannot be converted to a boolean (true/false) value. - La cadena "{0}" no puede convertirse en un valor booleano (true/false). - - - - MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} - MSB5003: Error al crear un archivo temporal. La carpeta de archivos temporales está llena o la ruta de acceso no es correcta. {0} - {StrBegin="MSB5003: "} - - - MSB5018: Failed to delete the temporary file "{0}". {1} {2} - MSB5018: No se pudo eliminar el archivo temporal "{0}". {1} {2} - {StrBegin="MSB5018: "} - - - The item metadata "%({0})" cannot be applied to the path "{1}". {2} - Los metadatos "%({0})" del elemento no pueden aplicarse a la ruta de acceso "{1}". {2} - UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. - - - MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. - MSB4077: La tarea "{0}" se marcó con el atributo LoadInSeparateAppDomain, pero esta no deriva de MarshalByRefObject. Asegúrese de que la tarea deriva de MarshalByRefObject o de AppDomainIsolatedTask. - {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. - - - .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - La versión "{0}" no es compatible. Especifique un valor de la enumeración Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - - - - .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. - La versión "{0}" de .NET Framework no es compatible cuando se establece de forma explícita Windows SDK como destino, que solo se admite en .NET 4.5 y posterior. Especifique un valor de la enumeración Microsoft.Build.Utilities.TargetDotNetFrameworkVersion que sea Version45 o posterior. - - - - Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. - La versión "{0}" de Visual Studio no es compatible. Especifique un valor de la enumeración Microsoft.Build.Utilities.VisualStudioVersion. - - - - When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} - Error al intentar generar una ruta de acceso de ensamblado de referencia a partir de la ruta de acceso "{0}" y del moniker "{1}" de la versión de .NET Framework. {2} - No Error code because this resource will be used in an exception. The error code is discarded if it is included - - - Could not find directory path: {0} - No se encuentra la ruta de acceso del directorio: {0} - Directory must exist - - - You do not have access to: {0} - No tiene acceso a: {0} - Directory must have access - - - Schema validation - Validación de esquemas - - UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: - "MSBUILD : Schema validation error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - - MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. - MSB5002: Finalizando el ejecutable de la tarea "{0}" porque no se completó dentro del límite especificado de {1} milisegundos. - {StrBegin="MSB5002: "} - - - Parameter "{0}" cannot be null. - El parámetro "{0}" no puede ser NULL. - - - - Parameter "{0}" cannot have zero length. - La longitud del parámetro "{0}" no puede ser cero. - - - - Parameters "{0}" and "{1}" must have the same number of elements. - Los parámetros "{0}" y "{1}" deben tener el mismo número de elementos. - - - - The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. - No se encuentra la cadena de recursos "{0}" para la tarea "{1}". Asegúrese de que el nombre del recurso "{0}" está escrito correctamente y de que el recurso existe en el ensamblado de la tarea. - - - - The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. - La tarea "{0}" no registró sus recursos. Para usar el método "TaskLoggingHelper.FormatResourceString()", esta tarea tiene que registrar sus recursos durante la construcción o mediante la propiedad "TaskResources". - LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. - - - MSB5004: The solution file has two projects named "{0}". - MSB5004: El archivo de solución tiene dos proyectos denominados "{0}". - {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. - - - MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. - MSB5005: Error al analizar la sección del proyecto "{0}". El nombre del archivo del proyecto "{1}" contiene caracteres no válidos. - {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. - - - MSB5006: Error parsing project section for project "{0}". The project file name is empty. - MSB5006: Error al analizar la sección del proyecto "{0}". El nombre del archivo del proyecto está vacío. - {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. - - - MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. - MSB5007: Error al analizar la sección de configuración del proyecto en el archivo de la solución. La entrada "{0}" no es válida. - {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. - - - MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. - MSB5008: Error al analizar la sección de configuración de la solución en el archivo de la solución. La entrada "{0}" no es válida. - {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the nested project section in solution file. - MSB5009: Error al analizar la sección del proyecto anidado en el archivo de solución. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. - MSB5023: Error al analizar la sección del proyecto anidado en el archivo de solución. El proyecto con el GUID "{0}" figura como anidado en el proyecto "{1}", pero no existe en la solución. - {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. - - - MSB5010: No file format header found. - MSB5010: No se encuentra ningún encabezado de formato de archivo. - {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. - - - MSB5011: Parent project GUID not found in "{0}" project dependency section. - MSB5011: No se encuentra el GUID del proyecto primario en la sección de dependencia del proyecto "{0}". - {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. - - - MSB5012: Unexpected end-of-file reached inside "{0}" project section. - MSB5012: Fin del archivo inesperado dentro de la sección del proyecto "{0}". - {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. - - - MSB5013: Error parsing a project section. - MSB5013: Error al analizar una sección del proyecto. - {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. - - - MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. - MSB5014: No se reconoce la versión del formato de archivo. MSBuild solo puede leer archivos de solución en las versiones de la {0}.0 y {1}.0, ambas incluidas. - {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. - - - MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. - MSB5015: No se pudieron leer las propiedades de la sección WebsiteProperties del proyecto "{0}". - {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. - - - Unrecognized solution version "{0}", attempting to continue. - Versión de solución "{0}" no reconocida. Se intentará continuar. - - - - Solution file - Archivo de la solución - UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: - "MSBUILD : Solution file error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - MSB5019: The project file is malformed: "{0}". {1} - MSB5019: El archivo del proyecto tiene un formato incorrecto: "{0}". {1} - {StrBegin="MSB5019: "} - - - MSB5020: Could not load the project file: "{0}". {1} - MSB5020: No se pudo cargar el archivo del proyecto: "{0}". {1} - {StrBegin="MSB5020: "} - - - MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. - MSB5021: Finalizando el ejecutable de la tarea "{0}" y sus procesos secundarios porque se canceló la compilación. - {StrBegin="MSB5021: "} - - - This collection is read-only. - Esta colección es de solo lectura. - - - - MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. - MSB5024: No se pudo determinar una ubicación válida para MSBuild. Intente ejecutar este proceso desde el Símbolo del sistema para desarrolladores de Visual Studio. - {StrBegin="MSB5024: "} - - - MSB4233: There was an exception while reading the log file: {0} - MSB4233: Excepción al leer el archivo de registro: {0} - {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. - - - Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. - El parámetro "{0}" con el valor asignado "{1}" no puede tener una ruta de acceso no válida o caracteres de archivo no válidos. - - - - An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} - Se produjo una excepción al expandir un fileSpec con globs: fileSpec: "{0}"; suponiendo que es un nombre de archivo. Excepción: {1} - - - - MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. - MSB5029: el valor "{0}" del atributo "{1}" en el elemento <{2}> en el archivo "{3}" es un carácter comodín que da como resultado enumerar todos los archivos de la unidad, lo que probablemente no estaba previsto. Compruebe que las propiedades a las que se hace referencia siempre están definidas y que el proyecto y el directorio de trabajo actual no están en la raíz de la unidad. - {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its - attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. - - - - \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.fr.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.fr.xlf deleted file mode 100644 index abbc14e5158..00000000000 --- a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.fr.xlf +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - MSB4188: Build was canceled. - MSB4188: La génération a été annulée. - {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. - - - MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. - MSB5022: L'hôte de tâche MSBuild ne prend pas en charge l'exécution de tâches qui effectuent des rappels IBuildEngine. Pour effectuer ces opérations, exécutez plutôt votre tâche dans le processus MSBuild de base. Une tâche s'exécute automatiquement dans l'hôte de tâche si UsingTask a été défini sur la valeur "Runtime" ou "Architecture", ou si l'appel de la tâche a été défini sur la valeur "MSBuildRuntime" ou "MSBuildArchitecture", qui ne correspond pas à l'architecture ou au runtime actuel de MSBuild. - {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. - - - Build started. - La génération a démarré. - - - - The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). - Le nombre d’éléments de la collection est supérieur à l’espace disponible dans le tableau de destination (lors du démarrage à l’index spécifié). - - - - MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". - MSB4008: un assembly en conflit avec l'assembly de tâche "{0}" a été trouvé sur "{1}". - {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. - - - Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs - Le type d’événement personnalisé '{0}' n’est pas pris en charge, car tous les types d’événement personnalisés ont été dépréciés. Utilisez Extended*EventArgs à la place. Plus d’informations : https://aka.ms/msbuild/eventargs - - - - Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. - Le type d'événement "{0}" devait être sérialisable à l'aide du sérialiseur .NET. L'événement n'était pas sérialisable et a été ignoré. - - - - {0} ({1},{2}) - {0} ({1},{2}) - A file location to be embedded in a string. - - - MSB6005: Task attempted to log before it was initialized. Message was: {0} - MSB6005: La tâche a tenté d'ouvrir une session avant d'être initialisée. Le message était le suivant : {0} - {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. - - - MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. - MSB3511: "{0}" n'est pas une valeur valide pour le paramètre "Importance". Les valeurs valides sont : High, Normal et Low. - {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. - The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown - LOCALIZATION: "Importance" should not be localized. - High should not be localized. - Normal should not be localized. - Low should not be localized. - - - Making the following modifications to the environment received from the parent node before applying it to the task host: - Modifications suivantes en cours sur l'environnement reçu du nœud parent avant son application à l'hôte de tâche : - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. - Définition de '{0}' sur '{1}' plutôt que sur la valeur de l'environnement parent, '{2}'. - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - MSB4025: The project file could not be loaded. {0} - MSB4025: Impossible de charger le fichier projet. {0} - {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is - invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is - provided separately to loggers. - LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. - - - MSB4103: "{0}" is not a valid logger verbosity level. - MSB4103: "{0}" n'est pas un niveau de verbosité de journaliseur valide. - {StrBegin="MSB4103: "} - - - MSBuild is expecting a valid "{0}" object. - MSBuild attend un objet "{0}" valide. - - - - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. - Le chemin {0} dépasse la limite maximale de chemin du système d'exploitation. Le nom du fichier qualifié complet doit contenir moins de {1} caractères. - - - - MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". - MSB5028: le fichier de filtre de solution sur "{0}" inclut le projet "{1}", qui ne figure pas dans le fichier solution sur "{2}". - {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. - - - MSB5025: Json in solution filter file "{0}" is incorrectly formatted. - MSB5025: les données JSON du fichier de filtre de solution "{0}" sont dans un format incorrect. - {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. - - - MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. - MSB5026: le fichier de filtre de solution sur "{0}" spécifie l'existence d'un fichier solution sur "{1}", mais ce fichier n'existe pas. - {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. - MSB5009: erreur durant l'analyse de la section de projet "{0}" ayant le GUID "{1}". Elle est située sous "{2}" mais ce projet est introuvable dans la solution. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. - MSB4132: La version des outils "{0}" n'est pas reconnue. Les versions disponibles sont {1}. - {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. - - - MSB5016: The name "{0}" contains an invalid character "{1}". - MSB5016: Le nom "{0}" contient un caractère non valide "{1}". - {StrBegin="MSB5016: "} - - - "{0}" is a reserved item metadata, and cannot be modified or deleted. - "{0}" est une métadonnée d'élément réservée qui ne peut être ni modifiée ni supprimée. - UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. - - - The string "{0}" cannot be converted to a boolean (true/false) value. - Impossible de convertir la chaîne "{0}" en valeur booléenne (true/false). - - - - MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} - MSB5003: Échec de la création d'un fichier temporaire. Le dossier de fichiers temporaires est plein ou son chemin est incorrect. {0} - {StrBegin="MSB5003: "} - - - MSB5018: Failed to delete the temporary file "{0}". {1} {2} - MSB5018: Impossible de supprimer le fichier temporaire « {0} ». {1} {2} - {StrBegin="MSB5018: "} - - - The item metadata "%({0})" cannot be applied to the path "{1}". {2} - Impossible d'appliquer la métadonnée d'élément "%({0})" au chemin d'accès "{1}". {2} - UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. - - - MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. - MSB4077: la tâche "{0}" a été marquée avec l'attribut LoadInSeparateAppDomain, mais ne dérive pas de MarshalByRefObject. Vérifiez que la tâche dérive de MarshalByRefObject ou de AppDomainIsolatedTask. - {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. - - - .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - La version "{0}" de .NET Framework n'est pas prise en charge. Spécifiez une valeur de l'énumération Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - - - - .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. - La version "{0}" de .NET Framework n'est pas prise en charge lors du ciblage explicite du SDK Windows, qui est pris en charge uniquement par .NET 4.5 et ultérieur. Spécifiez une valeur de l'énumération Microsoft.Build.Utilities.TargetDotNetFrameworkVersion correspondant à la version 4.5 (Version45) ou ultérieure. - - - - Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. - La version "{0}" de Visual Studio n'est pas prise en charge. Spécifiez une valeur de l'énumération Microsoft.Build.Utilities.VisualStudioVersion. - - - - When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} - Une erreur s'est produite lors de la tentative de génération d'un chemin d'assembly de référence à partir du chemin d'accès "{0}" et du moniker du Framework "{1}". {2} - No Error code because this resource will be used in an exception. The error code is discarded if it is included - - - Could not find directory path: {0} - Chemin d'accès au répertoire introuvable : {0} - Directory must exist - - - You do not have access to: {0} - Vous n'avez pas accès à : {0} - Directory must have access - - - Schema validation - Validation de schéma - - UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: - "MSBUILD : Schema validation error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - - MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. - MSB5002: Arrêt de la tâche exécutable "{0}", car elle ne s'est pas terminée dans le délai spécifié de {1} millisecondes. - {StrBegin="MSB5002: "} - - - Parameter "{0}" cannot be null. - Le paramètre "{0}" ne peut pas être null. - - - - Parameter "{0}" cannot have zero length. - La longueur du paramètre "{0}" ne peut pas être égale à zéro. - - - - Parameters "{0}" and "{1}" must have the same number of elements. - Les paramètres "{0}" et "{1}" doivent avoir le même nombre d'éléments. - - - - The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. - La chaîne de ressource "{0}" pour la tâche "{1}" est introuvable. Vérifiez que le nom de ressource "{0}" est bien orthographié et que la ressource existe dans l'assembly de la tâche. - - - - The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. - La tâche "{0}" n'a pas inscrit ses ressources. Pour pouvoir utiliser la méthode "TaskLoggingHelper.FormatResourceString()", cette tâche doit inscrire ses ressources durant la construction ou par le biais de la propriété "TaskResources". - LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. - - - MSB5004: The solution file has two projects named "{0}". - MSB5004: Le fichier solution contient deux projets nommés "{0}". - {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. - - - MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. - MSB5005: Erreur pendant l'analyse de la section du projet "{0}". Le nom du fichier projet "{1}" contient des caractères non valides. - {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. - - - MSB5006: Error parsing project section for project "{0}". The project file name is empty. - MSB5006: Erreur lors de l'analyse de la section du projet "{0}". Le nom du fichier projet est vide. - {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. - - - MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. - MSB5007: Erreur pendant l'analyse de la section de configuration de projet dans le fichier solution. L'entrée "{0}" n'est pas valide. - {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. - - - MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. - MSB5008: Erreur pendant l'analyse de la section de configuration de solution dans le fichier solution. L'entrée "{0}" n'est pas valide. - {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the nested project section in solution file. - MSB5009: Erreur pendant l'analyse de la section du projet imbriqué dans le fichier solution. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. - MSB5023: Erreur pendant l'analyse de la section du projet imbriqué dans le fichier solution. Un projet avec le GUID "{0}" est répertorié comme étant imbriqué sous le projet "{1}", mais il n'existe pas dans la solution. - {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. - - - MSB5010: No file format header found. - MSB5010: Aucun en-tête de format de fichier trouvé. - {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. - - - MSB5011: Parent project GUID not found in "{0}" project dependency section. - MSB5011: GUID du projet parent introuvable dans la section de dépendance du projet "{0}". - {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. - - - MSB5012: Unexpected end-of-file reached inside "{0}" project section. - MSB5012: Fin du fichier inattendue atteinte à l'intérieur de la section de projet "{0}". - {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. - - - MSB5013: Error parsing a project section. - MSB5013: Erreur lors de l'analyse d'une section de projet. - {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. - - - MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. - MSB5014: La version du format du fichier n'est pas reconnue. MSBuild peut lire uniquement les fichiers solution entre les versions {0}.0 et {1}.0 (inclus). - {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. - - - MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. - MSB5015: Impossible de lire les propriétés dans la section WebsiteProperties du projet "{0}". - {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. - - - Unrecognized solution version "{0}", attempting to continue. - Version de solution non reconnue "{0}", tentative de poursuite. - - - - Solution file - Fichier solution - UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: - "MSBUILD : Solution file error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - MSB5019: The project file is malformed: "{0}". {1} - MSB5019: Le fichier projet est incorrect : "{0}". {1} - {StrBegin="MSB5019: "} - - - MSB5020: Could not load the project file: "{0}". {1} - MSB5020: Impossible de charger le fichier projet : "{0}". {1} - {StrBegin="MSB5020: "} - - - MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. - MSB5021: Arrêt de la tâche exécutable "{0}" et de ses processus enfants, car la génération a été annulée. - {StrBegin="MSB5021: "} - - - This collection is read-only. - Cette collection est en lecture seule. - - - - MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. - MSB5024: Impossible de déterminer un emplacement valide vers MSBuild. Essayez d’exécuter ce processus à partir de l’invite de commandes Developer pour Visual Studio. - {StrBegin="MSB5024: "} - - - MSB4233: There was an exception while reading the log file: {0} - MSB4233: Une exception s'est produite durant la lecture du fichier journal : {0} - {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. - - - Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. - Le paramètre "{0}" avec la valeur assignée "{1}" ne peut pas avoir un chemin non valide ou des caractères de fichier non valides. - - - - An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} - Une exception s’est produite lors du développement d’un fileSpec avec globs: fileSpec: '{0}', en supposant qu’il s’agit d’un nom de fichier. Exception : {1} - - - - MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. - MSB5029: La valeur «{0}» de l’attribut «{1}» dans l’élément <{2}> dans le fichier «{3}» est un caractère générique qui entraîne l’énumération de tous les fichiers sur le lecteur, ce qui n’était probablement pas prévu. Vérifiez que les propriétés référencées sont toujours définies et que le projet et le répertoire de travail actuel ne se trouvent pas à la racine du lecteur. - {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its - attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. - - - - \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.it.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.it.xlf deleted file mode 100644 index 082c3faab51..00000000000 --- a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.it.xlf +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - MSB4188: Build was canceled. - MSB4188: compilazione annullata. - {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. - - - MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. - MSB5022: l'host attività MSBuild non supporta l'esecuzione di attività che eseguono callback IBuildEngine. Per eseguire tali operazioni, eseguire l'attività nel processo MSBuild di base. Un'attività verrà automaticamente eseguita nell'host attività se per UsingTask è stato definito un attributo con valore "Runtime" o "Architecture" o se per la chiamata all'attività è stato definito un attributo con valore "MSBuildRuntime" o "MSBuildArchitecture" che non corrisponde al runtime corrente o all'architettura di MSBuild. - {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. - - - Build started. - Compilazione avviata. - - - - The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). - Il numero di elementi nella raccolta è maggiore dello spazio disponibile nella matrice di destinazione (a partire dall'indice specificato). - - - - MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". - MSB4008: rilevato un assembly in conflitto per l'assembly dell'attività "{0}" in "{1}". - {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. - - - Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs - Il tipo di evento personalizzato '{0}' non è supportato perché tutti i tipi di evento personalizzati sono deprecati. Usare Invece Extended*EventArgs. Altre informazioni: https://aka.ms/msbuild/eventargs - - - - Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. - È previsto un tipo di evento "{0}" serializzabile con il serializzatore .NET. L'evento non era serializzabile ed è stato ignorato. - - - - {0} ({1},{2}) - {0} ({1},{2}) - A file location to be embedded in a string. - - - MSB6005: Task attempted to log before it was initialized. Message was: {0} - MSB6005: tentativo di registrazione prima dell'inizializzazione dell'attività. Messaggio: {0} - {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. - - - MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. - MSB3511: "{0}" non è un valore valido per il parametro "Importance". I valori validi sono: High, Normal e Low. - {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. - The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown - LOCALIZATION: "Importance" should not be localized. - High should not be localized. - Normal should not be localized. - Low should not be localized. - - - Making the following modifications to the environment received from the parent node before applying it to the task host: - Le modifiche seguenti verranno apportate all'ambiente ricevuto dal nodo padre prima dell'applicazione all'host attività: - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. - Impostazione di '{0}' su '{1}' anziché sul valore dell'ambiente padre, '{2}'. - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - MSB4025: The project file could not be loaded. {0} - MSB4025: non è stato possibile caricare il file di progetto. {0} - {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is - invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is - provided separately to loggers. - LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. - - - MSB4103: "{0}" is not a valid logger verbosity level. - MSB4103: "{0}" non è un livello di dettaglio valido del logger. - {StrBegin="MSB4103: "} - - - MSBuild is expecting a valid "{0}" object. - MSBuild prevede un oggetto "{0}" valido. - - - - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. - Il percorso {0} supera il limite massimo dei percorsi del sistema operativo. Il nome completo del file deve essere composto da meno di {1}. - - - - MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". - MSB5028: il file di filtro della soluzione in "{0}" include il progetto "{1}" che non è presente nel file di soluzione in "{2}". - {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. - - - MSB5025: Json in solution filter file "{0}" is incorrectly formatted. - MSB5025: il codice JSON nel file di filtro della soluzione "{0}" non è formattato correttamente. - {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. - - - MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. - MSB5026: nel file di filtro della soluzione in "{0}" è indicata la presenza di un file di soluzione in "{1}", ma tale file non esiste. - {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. - MSB5009: si è verificato un errore durante l'analisi della sezione "{0}" del progetto con GUID: "{1}". È annidata in "{2}", ma tale progetto non è stato trovato nella soluzione. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. - MSB4132: versione degli strumenti "{0}" non riconosciuta. Le versioni disponibili sono {1}. - {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. - - - MSB5016: The name "{0}" contains an invalid character "{1}". - MSB5016: il nome "{0}" contiene il carattere "{1}" non valido. - {StrBegin="MSB5016: "} - - - "{0}" is a reserved item metadata, and cannot be modified or deleted. - "{0}" è un metadato di un elemento riservato e pertanto non può essere modificato o eliminato. - UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. - - - The string "{0}" cannot be converted to a boolean (true/false) value. - Non è possibile convertire la stringa "{0}" in un valore booleano (true/false). - - - - MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} - MSB5003: non è stato possibile creare un file temporaneo. La cartella dei file temporanei è piena oppure il percorso è errato. {0} - {StrBegin="MSB5003: "} - - - MSB5018: Failed to delete the temporary file "{0}". {1} {2} - MSB5018: non è stato possibile eliminare il file temporaneo "{0}". {1} {2} - {StrBegin="MSB5018: "} - - - The item metadata "%({0})" cannot be applied to the path "{1}". {2} - Non è possibile applicare i metadati dell'elemento "%({0})" al percorso "{1}". {2} - UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. - - - MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. - MSB4077: l'attività "{0}" è stata contrassegnata con l'attributo LoadInSeparateAppDomain, ma non deriva da MarshalByRefObject. Verificare che l'attività derivi da MarshalByRefObject o AppDomainIsolatedTask. - {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. - - - .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - La versione "{0}" di .NET Framework non è supportata. Specificare un valore dall'enumerazione Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - - - - .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. - La versione "{0}" di .NET Framework non è supportata se è destinata in modo esplicito a Windows SDK, che è supportato solo da .NET 4.5 e versioni successive. Specificare un valore dell'enumerazione Microsoft.Build.Utilities.TargetDotNetFrameworkVersion che sia Version45 o successivo. - - - - Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. - La versione "{0}" di Visual Studio non è supportata. Specificare un valore dall'enumerazione Microsoft.Build.Utilities.VisualStudioVersion. - - - - When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} - Si è verificato un errore durante il tentativo di generare un percorso dell'assembly di riferimento dal "{0}" e dal moniker del framework "{1}". {2} - No Error code because this resource will be used in an exception. The error code is discarded if it is included - - - Could not find directory path: {0} - Il percorso di directory {0} non è stato trovato - Directory must exist - - - You do not have access to: {0} - Non si dispone dell'accesso a {0} - Directory must have access - - - Schema validation - Convalida schema - - UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: - "MSBUILD : Schema validation error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - - MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. - MSB5002: l'eseguibile "{0}" dell'attività verrà terminato perché non è finito entro il limite specificato di {1} millisecondi. - {StrBegin="MSB5002: "} - - - Parameter "{0}" cannot be null. - Il parametro "{0}" non può essere Null. - - - - Parameter "{0}" cannot have zero length. - Il parametro "{0}" non può avere lunghezza zero. - - - - Parameters "{0}" and "{1}" must have the same number of elements. - I parametri "{0}" e "{1}" devono avere lo stesso numero di elementi. - - - - The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. - La stringa di risorsa "{0}" per l'attività "{1}" non è stata trovata. Verificare che il nome della risorsa "{0}" sia stato digitato correttamente e che la risorsa sia presente nell'assembly dell'attività. - - - - The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. - L'attività "{0}" non ha registrato le proprie risorse. Per usare il metodo "TaskLoggingHelper.FormatResourceString()", è necessario che l'attività registri le risorse durante la costruzione o con la proprietà "TaskResources". - LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. - - - MSB5004: The solution file has two projects named "{0}". - MSB5004: al file di soluzione sono associati due progetti denominati "{0}". - {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. - - - MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. - MSB5005: errore durante l'analisi di una sezione di progetto per il progetto "{0}". Il nome del file di progetto "{1}" contiene caratteri non validi. - {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. - - - MSB5006: Error parsing project section for project "{0}". The project file name is empty. - MSB5006: errore durante l'analisi di una sezione di progetto per il progetto "{0}". Il nome del file di progetto è vuoto. - {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. - - - MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. - MSB5007: errore durante l'analisi della sezione di configurazione del progetto nel file di soluzione. La voce "{0}" non è valida. - {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. - - - MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. - MSB5008: errore durante l'analisi della sezione di configurazione della soluzione nel file di soluzione. La voce "{0}" non è valida. - {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the nested project section in solution file. - MSB5009: errore durante l'analisi della sezione del progetto annidato nel file di soluzione. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. - MSB5023: errore durante l'analisi della sezione del progetto annidato nel file di soluzione. Un progetto con GUID "{0}" è elencato come annidato nel progetto "{1}", ma non esiste nella soluzione. - {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. - - - MSB5010: No file format header found. - MSB5010: nessuna intestazione di formato di file trovata. - {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. - - - MSB5011: Parent project GUID not found in "{0}" project dependency section. - MSB5011: il GUID del progetto padre non è stato trovato nella sezione delle dipendenze del progetto "{0}". - {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. - - - MSB5012: Unexpected end-of-file reached inside "{0}" project section. - MSB5012: fine del file imprevista all'interno della sezione del progetto "{0}". - {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. - - - MSB5013: Error parsing a project section. - MSB5013: errore durante l'analisi di una sezione di progetto. - {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. - - - MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. - MSB5014: la versione del formato di file non è riconosciuta. Con MSBuild è possibile leggere solo file di soluzione dalla versione {0}.0 alla versione {1}.0. - {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. - - - MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. - MSB5015: non è stato possibile leggere le proprietà dalla sezione WebsiteProperties del progetto "{0}". - {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. - - - Unrecognized solution version "{0}", attempting to continue. - Versione di soluzione "{0}" non riconosciuta. Tentativo di continuare in corso. - - - - Solution file - File di soluzione - UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: - "MSBUILD : Solution file error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - MSB5019: The project file is malformed: "{0}". {1} - MSB5019: il formato del file di progetto non valido: "{0}". {1} - {StrBegin="MSB5019: "} - - - MSB5020: Could not load the project file: "{0}". {1} - MSB5020: non è stato possibile caricare il file di progetto: "{0}". {1} - {StrBegin="MSB5020: "} - - - MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. - MSB5021: l'eseguibile "{0}" dell'attività e i relativi processi figlio verranno terminati perché la compilazione è stata annullata. - {StrBegin="MSB5021: "} - - - This collection is read-only. - Raccolta di sola lettura. - - - - MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. - MSB5024: Non è stato possibile determinare un percorso valido di MSBuild. Provare a eseguire il processo dal prompt dei comandi per gli sviluppatori di Visual Studio. - {StrBegin="MSB5024: "} - - - MSB4233: There was an exception while reading the log file: {0} - MSB4233: si è verificata un'eccezione durante la lettura del file di log: {0} - {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. - - - Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. - Il parametro "{0}" con valore assegnato "{1}" non può contenere un percorso non valido o caratteri di file non validi. - - - - An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} - Si è verificata un'eccezione durante l'espansione di un fileSpec con GLOBS: fileSpec: "{0}", presupponendo che si tratti di un nome di file. Eccezione: {1} - - - - MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. - MSB5029: il valore "{0}" dell'attributo "{1}" nell'elemento <{2}> nel file "{3}" è un carattere jolly che determina l'enumerazione di tutti i file nell'unità, che probabilmente non era previsto. Controllare che le proprietà a cui si fa riferimento siano sempre definite e che il progetto e la directory di lavoro corrente non siano nella radice dell'unità. - {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its - attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. - - - - \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ja.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ja.xlf deleted file mode 100644 index 8e33727f2fb..00000000000 --- a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ja.xlf +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - MSB4188: Build was canceled. - MSB4188: ビルドが取り消されました。 - {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. - - - MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. - MSB5022: MSBuild タスク ホストは、IBuildEngine コールバックを実行するタスクの実行をサポートしていません。これらの操作を実行する場合は、タスクをコア MSBuild プロセスで実行してください。UsingTask の属性として設定されている "Runtime" または "Architecture" の値、あるいはタスク呼び出しの属性として設定されている "MSBuildRuntime" または "MSBuildArchitecture" の値が MSBuild の現在のランタイムまたはアーキテクチャと一致しない場合、タスクは自動的にタスク ホストで実行されます。 - {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. - - - Build started. - ビルドを開始しました。 - - - - The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). - コレクション内の要素の数が、同期先配列内の使用可能な領域を超えています (指定されたインデックスで開始する場合)。 - - - - MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". - MSB4008: タスク アセンブリ "{0}" に対して競合しているアセンブリが "{1}" で見つかりました。 - {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. - - - Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs - カスタム イベントの種類 '{0}' は、すべてのカスタム イベントの種類が非推奨になったため、サポートされていません。代わりに Extended*EventArgs を使用してください。詳細情報: https://aka.ms/msbuild/eventargs - - - - Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. - イベントの種類 "{0}" は .NET シリアライザーを使用してシリアル化可能であることが想定されていましたが、シリアル化可能でなかったため無視されました。 - - - - {0} ({1},{2}) - {0} ({1},{2}) - A file location to be embedded in a string. - - - MSB6005: Task attempted to log before it was initialized. Message was: {0} - MSB6005: タスクは、初期化される前にログを記録しようとしました。メッセージ: {0} - {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. - - - MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. - MSB3511: "{0}" は、"Importance" パラメーターに対して無効な値です。有効な値は High、Normal および Low です。 - {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. - The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown - LOCALIZATION: "Importance" should not be localized. - High should not be localized. - Normal should not be localized. - Low should not be localized. - - - Making the following modifications to the environment received from the parent node before applying it to the task host: - 親ノードから受け取った環境をタスク ホストに適用する前に、次の変更を行っています: - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. - '{0}' を親環境の値 '{2}' ではなく '{1}' に設定しています。 - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - MSB4025: The project file could not be loaded. {0} - MSB4025: プロジェクト ファイルを読み込めませんでした。{0} - {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is - invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is - provided separately to loggers. - LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. - - - MSB4103: "{0}" is not a valid logger verbosity level. - MSB4103: "{0}" は有効なロガー詳細レベルではありません。 - {StrBegin="MSB4103: "} - - - MSBuild is expecting a valid "{0}" object. - MSBuild は有効な "{0}" オブジェクトを必要としています。 - - - - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. - パス: {0} は OS のパスの上限を越えています。完全修飾のファイル名は {1} 文字以下にする必要があります。 - - - - MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". - MSB5028: "{0}" のソリューション フィルター ファイルには、"{2}" のソリューション ファイルにないプロジェクト "{1}" が含まれています。 - {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. - - - MSB5025: Json in solution filter file "{0}" is incorrectly formatted. - MSB5025: ソリューション フィルター ファイル "{0}" の JSON の形式が正しくありません。 - {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. - - - MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. - MSB5026: "{0}" のソリューション フィルター ファイルでは、"{1}" にソリューション ファイルを配置するように指定されていますが、そのファイルは存在しません。 - {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. - MSB5009: GUID "{1}" のプロジェクト "{0}" セクションの解析でエラーが発生しました。これは "{2}" の下に入れ子にされていますが、このプロジェクトはソリューション内で見つかりません。 - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. - MSB4132: ツール バージョン "{0}" が認識されません。使用可能なツール バージョンは {1} です。 - {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. - - - MSB5016: The name "{0}" contains an invalid character "{1}". - MSB5016: 名前 "{0}" は無効な文字 "{1}" を含んでいます。 - {StrBegin="MSB5016: "} - - - "{0}" is a reserved item metadata, and cannot be modified or deleted. - "{0}" は予約された項目メタデータです。変更または削除することはできません。 - UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. - - - The string "{0}" cannot be converted to a boolean (true/false) value. - 文字列 "{0}" をブール値 (true/false) に変換することはできません。 - - - - MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} - MSB5003: 一時ファイルを作成できませんでした。一時ファイル フォルダーがいっぱいであるか、またはそのパスが正しくありません。{0} - {StrBegin="MSB5003: "} - - - MSB5018: Failed to delete the temporary file "{0}". {1} {2} - MSB5018: 一時ファイル "{0}" を削除できませんでした。{1} {2} - {StrBegin="MSB5018: "} - - - The item metadata "%({0})" cannot be applied to the path "{1}". {2} - 項目メタデータ "%({0})" をパス "{1}" に適用できません。{2} - UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. - - - MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. - MSB4077: "{0}" タスクに属性 LoadInSeparateAppDomain が設定されていますが、MarshalByRefObject から派生していません。そのタスクが MarshalByRefObject または AppDomainIsolatedTask から派生していることを確認してください。 - {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. - - - .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - .NET Framework のバージョン "{0}" はサポートされていません。列挙 Microsoft.Build.Utilities.TargetDotNetFrameworkVersion から値を指定してください。 - - - - .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. - Windows SDK を明示的にターゲットとする場合、.NET Framework のバージョン "{0}" はサポートされません。Windows SDK は、.NET 4.5 以降でのみサポートされています。列挙 Microsoft.Build.Utilities.TargetDotNetFrameworkVersion から Version45 以上の値を指定してください。 - - - - Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. - Visual Studio のバージョン "{0}" はサポートされていません。列挙 Microsoft.Build.Utilities.VisualStudioVersion から値を指定してください。 - - - - When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} - 参照アセンブリ パスをパス "{0}" とフレームワーク モニカー "{1}" から生成しようとしたときに、エラーが発生しました。{2} - No Error code because this resource will be used in an exception. The error code is discarded if it is included - - - Could not find directory path: {0} - ディレクトリ パスが見つかりませんでした: {0} - Directory must exist - - - You do not have access to: {0} - {0} へのアクセス権がありません - Directory must have access - - - Schema validation - スキーマの検証 - - UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: - "MSBUILD : Schema validation error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - - MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. - MSB5002: 実行可能なタスク "{0}" は、指定された制限 ({1} ミリ秒) 内で完了しなかったため、終了しています。 - {StrBegin="MSB5002: "} - - - Parameter "{0}" cannot be null. - パラメーター "{0}" を null にすることはできません。 - - - - Parameter "{0}" cannot have zero length. - パラメーター "{0}" の長さを 0 にすることはできません。 - - - - Parameters "{0}" and "{1}" must have the same number of elements. - パラメーター "{0}" と "{1}" の要素数は同じである必要があります。 - - - - The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. - "{1}" タスクのリソース文字列 "{0}" が見つかりません。リソース名 "{0}" のスペルが正しいこと、およびリソースがタスクのアセンブリ内に存在することを確認してください。 - - - - The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. - "{0}" タスクのリソースが登録されていません。"TaskLoggingHelper.FormatResourceString()" メソッドを使用するためには、構築時に、または "TaskResources" プロパティを通じて、このタスクのリソースを登録する必要があります。 - LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. - - - MSB5004: The solution file has two projects named "{0}". - MSB5004: ソリューション ファイルには "{0}" という名前のプロジェクトが 2 つあります。 - {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. - - - MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. - MSB5005: プロジェクト "{0}" のプロジェクト セクションを解析中にエラーが発生しました。プロジェクトのファイル名 "{1}" に無効な文字が使用されています。 - {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. - - - MSB5006: Error parsing project section for project "{0}". The project file name is empty. - MSB5006: プロジェクト "{0}" のプロジェクト セクションを解析中にエラーが発生しました。プロジェクトのファイル名が空です。 - {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. - - - MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. - MSB5007: ソリューション ファイル内のプロジェクト構成セクションを解析中にエラーが発生しました。エントリ "{0}" は無効です。 - {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. - - - MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. - MSB5008: ソリューション ファイル内のソリューション構成セクションを解析中にエラーが発生しました。エントリ "{0}" は無効です。 - {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the nested project section in solution file. - MSB5009: ソリューション ファイル内の入れ子にされたプロジェクト セクションを解析中にエラーが発生しました。 - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. - MSB5023: ソリューション ファイルの入れ子になったプロジェクト セクションを解析中にエラーが発生しました。GUID "{0}" のプロジェクトは、プロジェクト "{1}" 下に入れ子として表示されていますが、ソリューション内に存在しません。 - {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. - - - MSB5010: No file format header found. - MSB5010: ファイル形式のヘッダーが見つかりませんでした。 - {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. - - - MSB5011: Parent project GUID not found in "{0}" project dependency section. - MSB5011: 親プロジェクト GUID が "{0}" プロジェクト依存セクションで見つかりませんでした。 - {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. - - - MSB5012: Unexpected end-of-file reached inside "{0}" project section. - MSB5012: "{0}" プロジェクト セクション内で、予期しない EOF に到達しました。 - {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. - - - MSB5013: Error parsing a project section. - MSB5013: プロジェクト セクションを解析中にエラーが発生しました。 - {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. - - - MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. - MSB5014: ファイル形式のバージョンを認識できません。MSBuild で読み取ることができるのは、バージョン {0}.0 ~ {1}.0 のソリューション ファイルだけです。 - {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. - - - MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. - MSB5015: "{0}" プロジェクトの WebsiteProperties セクションからプロパティを読み取れませんでした。 - {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. - - - Unrecognized solution version "{0}", attempting to continue. - "{0}" は認識できないソリューション バージョンです。続行を試みます。 - - - - Solution file - ソリューション ファイル - UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: - "MSBUILD : Solution file error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - MSB5019: The project file is malformed: "{0}". {1} - MSB5019: プロジェクト ファイルの形式が正しくありません: "{0}"。{1} - {StrBegin="MSB5019: "} - - - MSB5020: Could not load the project file: "{0}". {1} - MSB5020: プロジェクト ファイル "{0}" を読み込めませんでした。{1} - {StrBegin="MSB5020: "} - - - MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. - MSB5021: ビルドが取り消されたため、実行可能なタスク "{0}" とその子プロセスを終了しています。 - {StrBegin="MSB5021: "} - - - This collection is read-only. - このコレクションは読み取り専用です。 - - - - MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. - MSB5024: MSBuild への有効な場所が決定できませんでした。Visual Studio の開発者コマンド プロンプトからこのプロセスを実行してください。 - {StrBegin="MSB5024: "} - - - MSB4233: There was an exception while reading the log file: {0} - MSB4233: ログ ファイルの読み取り中に例外が発生しました: {0} - {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. - - - Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. - 値 "{1}" が割り当てられたパラメーター "{0}" には、無効なパスまたは無効なファイル内の文字を指定することはできません。 - - - - An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} - glob を使用して fileSpec を展開中に例外が発生しました: fileSpec: "{0}"、これはファイル名であると仮定します。例外: {1} - - - - MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. - MSB5029: ファイル "{3}" の要素 <{2}> の "{1}" 属性の値 "{0}" は、ドライブ上のすべてのファイルを列挙するワイルドカードであり、意図されていない可能性があります。参照されるプロパティが常に定義されていること、およびプロジェクトと現在の作業ディレクトリがドライブ ルートにないことを確認します。 - {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its - attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. - - - - \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ko.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ko.xlf deleted file mode 100644 index 102c6ecfb97..00000000000 --- a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ko.xlf +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - MSB4188: Build was canceled. - MSB4188: 빌드가 취소되었습니다. - {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. - - - MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. - MSB5022: MSBuild 작업 호스트는 IBuildEngine 콜백을 수행하는 작업의 실행을 지원하지 않습니다. 이러한 작업을 수행하려면 대신 코어 MSBuild 프로세스에서 작업을 실행하세요. UsingTask에 "Runtime" 또는 "Architecture" 값이 사용되었거나 작업 호출에 MSBuild의 현재 런타임 또는 아키텍처와 일치하지 않는 "MSBuildRuntime" 또는 "MSBuildArchitecture" 값이 사용된 경우 작업 호스트에서 작업이 자동으로 실행됩니다. - {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. - - - Build started. - 빌드를 시작했습니다. - - - - The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). - 컬렉션의 요소 수가 대상 배열에서 사용 가능한 공간보다 큽니다(지정된 인덱스에서 시작할 때). - - - - MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". - MSB4008: 작업 어셈블리 "{0}"과(와) 충돌하는 어셈블리가 "{1}"에 있습니다. - {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. - - - Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs - 모든 사용자 지정 이벤트 유형이 사용되지 않으므로 사용자 지정 이벤트 유형 '{0}' 지원되지 않습니다. 대신 Extended*EventArgs를 사용하세요. 추가 정보: https://aka.ms/msbuild/eventargs - - - - Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. - 이벤트 유형 "{0}"은(는) .NET serializer를 사용하여 serialize할 수 있어야 합니다. 이 이벤트는 serialize할 수 없으므로 무시되었습니다. - - - - {0} ({1},{2}) - {0}({1},{2}) - A file location to be embedded in a string. - - - MSB6005: Task attempted to log before it was initialized. Message was: {0} - MSB6005: 작업을 초기화하기 전에 로깅하려고 했습니다. 메시지: {0} - {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. - - - MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. - MSB3511: "{0}"은(는) "Importance" 매개 변수에 사용할 수 없는 값입니다. 유효한 값은 High, Normal 및 Low입니다. - {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. - The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown - LOCALIZATION: "Importance" should not be localized. - High should not be localized. - Normal should not be localized. - Low should not be localized. - - - Making the following modifications to the environment received from the parent node before applying it to the task host: - 작업 호스트에 적용하기 전에 부모 노드로부터 받은 환경을 다음과 같이 수정하고 있습니다. - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. - '{0}'을(를) 부모 환경 값인 '{2}' 대신 '{1}'(으)로 설정하고 있습니다. - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - MSB4025: The project file could not be loaded. {0} - MSB4025: 프로젝트 파일을 로드할 수 없습니다. {0} - {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is - invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is - provided separately to loggers. - LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. - - - MSB4103: "{0}" is not a valid logger verbosity level. - MSB4103: "{0}"은(는) 로거의 세부 정보 표시로 알맞지 않습니다. - {StrBegin="MSB4103: "} - - - MSBuild is expecting a valid "{0}" object. - MSBuild에 올바른 "{0}" 개체가 필요합니다. - - - - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. - 경로: {0}은(는) OS 최대 경로 제한을 초과합니다. 정규화된 파일 이름은 {1}자 이하여야 합니다. - - - - MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". - MSB5028: "{0}"의 솔루션 필터 파일에 "{2}"의 솔루션 파일에 없는 "{1}" 프로젝트가 포함되어 있습니다. - {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. - - - MSB5025: Json in solution filter file "{0}" is incorrectly formatted. - MSB5025: 솔루션 필터 파일 "{0}"의 Json 형식이 잘못되었습니다. - {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. - - - MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. - MSB5026: "{0}"의 솔루션 필터 파일이 "{1}"에 솔루션 파일이 있도록 지정하지만, 해당 파일이 없습니다. - {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. - MSB5009: GUID가 "{1}"인 프로젝트 "{0}" 섹션을 구문 분석하는 동안 오류가 발생했습니다. "{2}" 아래에 중첩되지만 해당 프로젝트를 솔루션에서 찾을 수 없습니다. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. - MSB4132: 도구 버전 "{0}"을(를) 인식할 수 없습니다. 사용할 수 있는 도구 버전은 {1}입니다. - {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. - - - MSB5016: The name "{0}" contains an invalid character "{1}". - MSB5016: 이름 "{0}"에 잘못된 문자 "{1}"이(가) 사용되었습니다. - {StrBegin="MSB5016: "} - - - "{0}" is a reserved item metadata, and cannot be modified or deleted. - "{0}"은(는) 예약된 항목 메타데이터이므로 수정 또는 삭제할 수 없습니다. - UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. - - - The string "{0}" cannot be converted to a boolean (true/false) value. - "{0}" 문자열을 부울 값(true/false)으로 변환할 수 없습니다. - - - - MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} - MSB5003: 임시 파일을 만들지 못했습니다. 임시 파일 폴더가 꽉 찼거나 경로가 올바르지 않습니다. {0} - {StrBegin="MSB5003: "} - - - MSB5018: Failed to delete the temporary file "{0}". {1} {2} - MSB5018: 임시 파일 "{0}"을(를) 삭제하지 못했습니다. {1} {2} - {StrBegin="MSB5018: "} - - - The item metadata "%({0})" cannot be applied to the path "{1}". {2} - 항목 메타데이터 "%({0})"을(를) "{1}" 경로에 적용할 수 없습니다. {2} - UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. - - - MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. - MSB4077: "{0}" 작업이 LoadInSeparateAppDomain 특성으로 표시되었지만 MarshalByRefObject에서 파생되지 않습니다. 해당 작업이 MarshalByRefObject 또는 AppDomainIsolatedTask에서 파생되는지 확인하십시오. - {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. - - - .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - .NET Framework 버전 "{0}"이(가) 지원되지 않습니다. Microsoft.Build.Utilities.TargetDotNetFrameworkVersion 열거형에서 값을 지정하세요. - - - - .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. - .NET 4.5 이상에서만 지원되는 Windows SDK를 명시적으로 대상으로 지정하는 경우 .NET Framework 버전 "{0}"이(가) 지원되지 않습니다. Microsoft.Build.Utilities.TargetDotNetFrameworkVersion 열거형에서 Version45 이상인 값을 지정하세요. - - - - Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. - Visual Studio 버전 "{0}"이(가) 지원되지 않습니다. Microsoft.Build.Utilities.VisualStudioVersion 열거형에서 값을 지정하세요. - - - - When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} - "{0}" 경로 및 프레임워크 모니커 "{1}"에서 참조 어셈블리 경로를 생성하는 동안 오류가 발생했습니다. {2} - No Error code because this resource will be used in an exception. The error code is discarded if it is included - - - Could not find directory path: {0} - 디렉터리 경로를 찾을 수 없습니다. {0} - Directory must exist - - - You do not have access to: {0} - {0}에 액세스할 수 있는 권한이 없습니다. - Directory must have access - - - Schema validation - 스키마 유효성 검사 - - UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: - "MSBUILD : Schema validation error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - - MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. - MSB5002: 작업 실행 파일 "{0}"이(가) 지정한 제한 시간인 {1}밀리초 안에 완료되지 않았으므로 종료합니다. - {StrBegin="MSB5002: "} - - - Parameter "{0}" cannot be null. - "{0}" 매개 변수는 null일 수 없습니다. - - - - Parameter "{0}" cannot have zero length. - "{0}" 매개 변수의 길이는 0일 수 없습니다. - - - - Parameters "{0}" and "{1}" must have the same number of elements. - "{0}" 및 "{1}" 매개 변수에는 동일한 수의 요소를 사용해야 합니다. - - - - The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. - "{1}" 작업의 리소스 문자열 "{0}"을(를) 찾을 수 없습니다. 리소스 이름 "{0}"의 맞춤법이 올바른지 그리고 리소스가 작업 어셈블리에 있는지 확인하세요. - - - - The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. - {0} 작업의 리소스가 등록되지 않았습니다. "TaskLoggingHelper.FormatResourceString()" 메서드를 사용하려면 생성 시 또는 "TaskResources" 속성을 통해 이 작업의 리소스를 등록해야 합니다. - LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. - - - MSB5004: The solution file has two projects named "{0}". - MSB5004: 솔루션 파일에 이름이 "{0}"인 프로젝트가 두 개 있습니다. - {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. - - - MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. - MSB5005: "{0}" 프로젝트의 프로젝트 섹션을 구문 분석하는 동안 오류가 발생했습니다. 프로젝트 파일 이름 "{1}"에 잘못된 문자가 있습니다. - {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. - - - MSB5006: Error parsing project section for project "{0}". The project file name is empty. - MSB5006: "{0}" 프로젝트의 프로젝트 섹션을 구문 분석하는 동안 오류가 발생했습니다. 프로젝트 파일 이름이 비어 있습니다. - {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. - - - MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. - MSB5007: 솔루션 파일의 프로젝트 구성 섹션을 구문 분석하는 동안 오류가 발생했습니다. "{0}" 항목이 잘못되었습니다. - {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. - - - MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. - MSB5008: 솔루션 파일의 솔루션 구성 섹션을 구문 분석하는 동안 오류가 발생했습니다. "{0}" 항목이 잘못되었습니다. - {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the nested project section in solution file. - MSB5009: 솔루션 파일의 중첩된 프로젝트 섹션을 구문 분석하는 동안 오류가 발생했습니다. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. - MSB5023: 솔루션 파일의 중첩된 프로젝트 섹션을 구문 분석하는 동안 오류가 발생했습니다. GUID가 "{0}"인 프로젝트는 "{1}" 프로젝트 아래에 중첩되고 있는 것으로 나열되지만 솔루션에 없습니다. - {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. - - - MSB5010: No file format header found. - MSB5010: 파일 형식 헤더가 없습니다. - {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. - - - MSB5011: Parent project GUID not found in "{0}" project dependency section. - MSB5011: "{0}" 프로젝트 종속성 섹션에 부모 프로젝트 GUID가 없습니다. - {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. - - - MSB5012: Unexpected end-of-file reached inside "{0}" project section. - MSB5012: "{0}" 프로젝트 섹션에서 예기치 않은 파일 끝에 도달했습니다. - {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. - - - MSB5013: Error parsing a project section. - MSB5013: 프로젝트 섹션을 구문 분석하는 동안 오류가 발생했습니다. - {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. - - - MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. - MSB5014: 파일 형식 버전을 인식할 수 없습니다. MSBuild는 {0}.0 버전에서 {1}.0 버전까지의 솔루션 파일만 읽을 수 있습니다. - {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. - - - MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. - MSB5015: "{0}" 프로젝트의 WebsiteProperties 섹션에서 속성을 읽을 수 없습니다. - {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. - - - Unrecognized solution version "{0}", attempting to continue. - 인식할 수 없는 솔루션 버전 "{0}"입니다. 작업을 계속하려고 합니다. - - - - Solution file - 솔루션 파일 - UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: - "MSBUILD : Solution file error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - MSB5019: The project file is malformed: "{0}". {1} - MSB5019: 프로젝트 파일의 형식이 잘못되었습니다. "{0}". {1} - {StrBegin="MSB5019: "} - - - MSB5020: Could not load the project file: "{0}". {1} - MSB5020: 프로젝트 파일을 로드할 수 없습니다. "{0}". {1} - {StrBegin="MSB5020: "} - - - MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. - MSB5021: 빌드가 취소되었으므로 작업 실행 파일 "{0}" 및 자식 프로세스를 종료합니다. - {StrBegin="MSB5021: "} - - - This collection is read-only. - 이 컬렉션은 읽기 전용입니다. - - - - MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. - MSB5024: 유효한 MSBuild 위치를 확인할 수 없습니다. Visual Studio용 개발자 명령 프롬프트에서 프로세스를 실행해 보세요. - {StrBegin="MSB5024: "} - - - MSB4233: There was an exception while reading the log file: {0} - MSB4233: 로그 파일을 읽는 동안 예외가 발생했습니다. {0} - {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. - - - Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. - "{1}" 값이 할당된 "{0}" 매개 변수는 유효하지 않은 경로 또는 파일 문자를 포함할 수 없습니다. - - - - An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} - glob를 사용하여 fileSpec을 확장하는 동안 예외가 발생했습니다. fileSpec: "{0}", 파일 이름이라고 가정합니다. 예외: {1} - - - - MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. - MSB5029: 파일 "{0}"에 있는 요소 <{1}> 요소의 "{2}" 특성의 값 "{3}"은(는) 의도하지 않은 드라이브의 모든 파일을 열거하는 와일드카드입니다. 참조된 속성이 항상 정의되어 있고 프로젝트 및 현재 작업 디렉터리가 드라이브 루트에 없는지 확인합니다. - {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its - attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. - - - - \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.pl.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.pl.xlf deleted file mode 100644 index f5c531296d1..00000000000 --- a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.pl.xlf +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - MSB4188: Build was canceled. - MSB4188: Kompilacja została anulowana. - {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. - - - MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. - MSB5022: Host zadań programu MSBuild nie obsługuje zadań wykonujących wywołania zwrotne do aparatu IBuildEngine. Jeśli chcesz wykonywać te operacje, uruchom zadanie w podstawowym procesie programu MSBuild. Zadanie będzie automatycznie wykonywane na hoście zadań, jeśli w deklaracji UsingTask ustawiono wartość „Runtime” lub „Architecture” albo w wywołaniu zadania ustawiono wartość „MSBuildRuntime” lub „MSBuildArchitecture”, która nie odpowiada bieżącemu środowisku uruchomieniowemu lub architekturze programu MSBuild. - {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. - - - Build started. - Kompilacja rozpoczęła się. - - - - The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). - Liczba elementów w kolekcji jest większa niż dostępne miejsce w tablicy docelowej (zaczynając od określonego indeksu). - - - - MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". - MSB4008: Zestaw, który wywołuje konflikt z zestawem zadania „{0}”, został znaleziony w „{1}”. - {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. - - - Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs - Niestandardowy typ zdarzenia '{0}' nie jest obsługiwany, ponieważ wszystkie typy zdarzeń niestandardowych były przestarzałe. Zamiast tego użyj elementu Extended*EventArgs. Więcej informacji: https://aka.ms/msbuild/eventargs - - - - Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. - Oczekiwano, że zdarzenie typu „{0}” będzie uszeregowane przy użyciu serializatora platformy .NET. Zdarzenie nie może podlegać szeregowaniu, dlatego zostało zignorowane. - - - - {0} ({1},{2}) - {0} ({1},{2}) - A file location to be embedded in a string. - - - MSB6005: Task attempted to log before it was initialized. Message was: {0} - MSB6005: Zadanie podjęło próbę zarejestrowania przed zainicjowaniem. Pojawił się komunikat: {0} - {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. - - - MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. - MSB3511: „{0}” jest nieprawidłową wartością parametru „Importance”. Prawidłowe wartości: High, Normal i Low. - {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. - The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown - LOCALIZATION: "Importance" should not be localized. - High should not be localized. - Normal should not be localized. - Low should not be localized. - - - Making the following modifications to the environment received from the parent node before applying it to the task host: - Wymienione zmiany otrzymane z węzła nadrzędnego zostaną wprowadzone w środowisku, a po sprawdzeniu działania zastosowane do hosta zadań: - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. - Ustawienie dla elementu „{0}” wartości „{1}” zamiast wartości „{2}” obowiązującej w środowisku nadrzędnym. - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - MSB4025: The project file could not be loaded. {0} - MSB4025: Nie można załadować pliku projektu. {0} - {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is - invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is - provided separately to loggers. - LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. - - - MSB4103: "{0}" is not a valid logger verbosity level. - MSB4103: „{0}” nie jest prawidłowym poziomem szczegółowości rejestratora. - {StrBegin="MSB4103: "} - - - MSBuild is expecting a valid "{0}" object. - Program MSBuild oczekuje prawidłowego obiektu „{0}”. - - - - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. - Ścieżka: {0} przekracza limit maksymalnej długości ścieżki w systemie operacyjnym. W pełni kwalifikowana nazwa pliku musi się składać z mniej niż {1} znaków. - - - - MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". - MSB5028: Plik filtru rozwiązania w lokalizacji „{0}” obejmuje projekt „{1}”, który nie znajduje się w pliku rozwiązania w lokalizacji „{2}”. - {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. - - - MSB5025: Json in solution filter file "{0}" is incorrectly formatted. - MSB5025: Kod JSON w pliku filtru rozwiązania „{0}” jest niepoprawnie sformatowany. - {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. - - - MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. - MSB5026: Plik filtru rozwiązania w lokalizacji „{0}” określa, że plik rozwiązania będzie się znajdował w lokalizacji „{1}”, ale ten plik nie istnieje. - {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. - MSB5009: Błąd analizowania sekcji projektu „{0}” o identyfikatorze GUID: „{1}”. Jest ona zagnieżdżona w obszarze „{2}”, ale nie znaleziono tego projektu w rozwiązaniu. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. - MSB4132: Wersja narzędzi „{0}” nie została rozpoznana. Dostępne wersje narzędzi to {1}. - {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. - - - MSB5016: The name "{0}" contains an invalid character "{1}". - MSB5016: Nazwa „{0}” zawiera nieprawidłowy znak „{1}”. - {StrBegin="MSB5016: "} - - - "{0}" is a reserved item metadata, and cannot be modified or deleted. - „{0}” jest zastrzeżonym elementem metadanych i nie może zostać zmodyfikowany ani usunięty. - UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. - - - The string "{0}" cannot be converted to a boolean (true/false) value. - Ciągu „{0}” nie można przekonwertować na wartość logiczną (prawda/fałsz). - - - - MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} - MSB5003: Nie można utworzyć pliku tymczasowego. Folder plików tymczasowych jest zapełniony lub jego ścieżka jest niepoprawna. {0} - {StrBegin="MSB5003: "} - - - MSB5018: Failed to delete the temporary file "{0}". {1} {2} - MSB5018: nie można usunąć pliku tymczasowego „{0}”. {1} {2} - {StrBegin="MSB5018: "} - - - The item metadata "%({0})" cannot be applied to the path "{1}". {2} - Elementu metadanych „%({0})” nie można zastosować do ścieżki „{1}”. {2} - UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. - - - MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. - MSB4077: Zadanie „{0}” zostało oznaczone atrybutem LoadInSeparateAppDomain, ale nie pochodzi od obiektu MarshalByRefObject. Sprawdź, czy zadanie pochodzi od obiektu MarshalByRefObject lub zadania AppDomainIsolatedTask. - {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. - - - .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - Program .NET Framework w wersji „{0}” nie jest obsługiwany. Podaj wartość z wyliczenia Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - - - - .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. - Program .NET Framework w wersji „{0}” nie jest obsługiwany, jeśli jawnym obiektem docelowym jest zestaw SDK systemu Windows, ponieważ taki zestaw jest obsługiwany tylko na platformie .NET w wersji 4.5 lub nowszej. Podaj wartość z wyliczenia Microsoft.Build.Utilities.TargetDotNetFrameworkVersion, która wynosi co najmniej Version45. - - - - Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. - Program Visual Studio w wersji „{0}” nie jest obsługiwany. Podaj wartość z wyliczenia Microsoft.Build.Utilities.VisualStudioVersion. - - - - When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} - Wystąpił błąd podczas próby wygenerowania ścieżki zestawu odwołania ze ścieżki „{0}” i monikera struktury „{1}”. {2} - No Error code because this resource will be used in an exception. The error code is discarded if it is included - - - Could not find directory path: {0} - Nie można odnaleźć ścieżki katalogu: {0} - Directory must exist - - - You do not have access to: {0} - Nie masz dostępu do następującego elementu: {0} - Directory must have access - - - Schema validation - Walidacja schematu - - UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: - "MSBUILD : Schema validation error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - - MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. - MSB5002: plik wykonywalny zadania „{0}” zostanie zakończony, ponieważ nie ukończył działania przed określonym limitem czasu ({1} ms). - {StrBegin="MSB5002: "} - - - Parameter "{0}" cannot be null. - Parametr „{0}” nie może być zerowy. - - - - Parameter "{0}" cannot have zero length. - Parametr „{0}” nie może mieć zerowej długości. - - - - Parameters "{0}" and "{1}" must have the same number of elements. - Parametry „{0}” i „{1}” muszą mieć tę samą liczbę elementów. - - - - The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. - Nie można odnaleźć ciągu zasobu „{0}” dla zadania „{1}”. Upewnij się, że nazwa zasobu „{0}” ma poprawną pisownię i że zasób istnieje w zestawie zadania. - - - - The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. - Zadanie „{0}” nie zarejestrowało swoich zasobów. Aby było możliwe użycie metody „TaskLoggingHelper.FormatResourceString()”, to zadanie musi zarejestrować swoje zasoby podczas konstruowania lub przy użyciu właściwości „TaskResources”. - LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. - - - MSB5004: The solution file has two projects named "{0}". - MSB5004: Plik rozwiązania zawiera dwa projekty o nazwie „{0}”. - {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. - - - MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. - MSB5005: Błąd podczas analizowania sekcji projektu „{0}”. Nazwa pliku projektu „{1}” zawiera nieprawidłowe znaki. - {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. - - - MSB5006: Error parsing project section for project "{0}". The project file name is empty. - MSB5006: Błąd podczas analizowania sekcji projektu „{0}”. Nazwa pliku projektu jest pusta. - {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. - - - MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. - MSB5007: Błąd podczas analizowania sekcji konfiguracji projektu w pliku rozwiązania. Wpis „{0}” jest nieprawidłowy. - {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. - - - MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. - MSB5008: Błąd podczas analizowania sekcji konfiguracji rozwiązania w pliku rozwiązania. Wpis „{0}” jest nieprawidłowy. - {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the nested project section in solution file. - MSB5009: Błąd podczas analizowania zagnieżdżonej sekcji projektu w pliku rozwiązania. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. - MSB5023: Błąd podczas analizowania zagnieżdżonej sekcji projektu w pliku rozwiązania. Projekt z identyfikatorem GUID „{0}” figuruje jako zagnieżdżony w projekcie „{1}”, ale nie istnieje w rozwiązaniu. - {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. - - - MSB5010: No file format header found. - MSB5010: Nie odnaleziono nagłówka formatu pliku. - {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. - - - MSB5011: Parent project GUID not found in "{0}" project dependency section. - MSB5011: Nie odnaleziono identyfikatora GUID projektu nadrzędnego w sekcji zależności projektu „{0}”. - {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. - - - MSB5012: Unexpected end-of-file reached inside "{0}" project section. - MSB5012: Osiągnięto nieoczekiwany koniec pliku wewnątrz sekcji projektu „{0}”. - {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. - - - MSB5013: Error parsing a project section. - MSB5013: Błąd podczas analizowania sekcji projektu. - {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. - - - MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. - MSB5014: Nie rozpoznano wersji formatu pliku. Program MSBuild może odczytywać tylko pliki rozwiązań w wersjach od {0}.0 do {1}.0 włącznie. - {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. - - - MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. - MSB5015: Nie można odczytać właściwości z sekcji WebsiteProperties projektu „{0}”. - {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. - - - Unrecognized solution version "{0}", attempting to continue. - Nierozpoznana wersja rozwiązania „{0}”, nastąpi próba kontynuacji. - - - - Solution file - Plik rozwiązania - UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: - "MSBUILD : Solution file error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - MSB5019: The project file is malformed: "{0}". {1} - MSB5019: Plik projektu ma nieprawidłową postać: „{0}”. {1} - {StrBegin="MSB5019: "} - - - MSB5020: Could not load the project file: "{0}". {1} - MSB5020: Nie można załadować pliku projektu: „{0}”. {1} - {StrBegin="MSB5020: "} - - - MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. - MSB5021: plik wykonywalny zadania „{0}” i jego procesy podrzędne zostaną zakończone, ponieważ anulowano kompilację. - {StrBegin="MSB5021: "} - - - This collection is read-only. - Ta kolekcja jest tylko do odczytu. - - - - MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. - MSB5024: Nie można określić prawidłowej lokalizacji programu MSBuild. Spróbuj uruchomić ten proces z wiersza polecenia dewelopera dla programu Visual Studio. - {StrBegin="MSB5024: "} - - - MSB4233: There was an exception while reading the log file: {0} - MSB4233: Wystąpił wyjątek podczas odczytywania pliku dziennika: {0} - {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. - - - Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. - Parametr „{0}” z przypisaną wartością „{1}” nie może mieć nieprawidłowej ścieżki ani zawierać nieprawidłowych znaków w pliku. - - - - An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} - Wystąpił wyjątek podczas rozwijania elementu fileSpec za pomocą globów: fileSpec: „{0}”, przy założeniu, że jest to nazwa pliku. Wyjątek: {1} - - - - MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. - MSB5029: Wartość „{0}” atrybutu „{1}” w elemencie <{2}> w pliku „{3}” jest symbolem wieloznacznym, który powoduje wyliczenie wszystkich plików na dysku, co prawdopodobnie nie było zamierzone. Sprawdź, czy przywoływane właściwości są zawsze zdefiniowane oraz czy projekt i bieżący katalog roboczy nie znajdują się w katalogu głównym dysku. - {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its - attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. - - - - \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.pt-BR.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.pt-BR.xlf deleted file mode 100644 index cf78ee34200..00000000000 --- a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.pt-BR.xlf +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - MSB4188: Build was canceled. - MSB4188: Compilação cancelada. - {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. - - - MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. - MSB5022: O host de tarefas MSBuild não dá suporte a tarefas em execução que executam chamadas de retorno de IBuildEngine. Se desejar executar essas operações, execute sua tarefa no processo MSBuild principal. Uma tarefa será automaticamente executada no host de tarefas se UsingTask tiver como atributo um valor "Runtime" ou "Architecture" ou se a invocação da tarefa tiver como atributo um valor "MSBuildRuntime" ou "MSBuildArchitecture", que não coincida com o tempo de execução ou arquitetura atual do MSBuild. - {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. - - - Build started. - Compilação iniciada. - - - - The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). - O número de elementos na coleção é maior que o espaço disponível na matriz de destino (ao iniciar no índice especificado). - - - - MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". - MSB4008: Foi encontrado um assembly conflitante no assembly da tarefa "{0}" em "{1}". - {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. - - - Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs - Não há suporte '{0}' tipo de evento personalizado porque todos os tipos de eventos personalizados foram preteridos. Use Extended*EventArgs em vez disso. Mais informações: https://aka.ms/msbuild/eventargs - - - - Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. - Era esperado que o tipo de evento "{0}" fosse serializável usando o serializador .NET. O evento não era serializável e foi ignorado. - - - - {0} ({1},{2}) - {0} ({1},{2}) - A file location to be embedded in a string. - - - MSB6005: Task attempted to log before it was initialized. Message was: {0} - MSB6005: A tarefa tentou fazer o registro antes de ser inicializada. A mensagem era: {0} - {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. - - - MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. - MSB3511: "{0}" é um valor inválido para o parâmetro "Importance". Os valores válidos são: High, Normal e Low. - {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. - The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown - LOCALIZATION: "Importance" should not be localized. - High should not be localized. - Normal should not be localized. - Low should not be localized. - - - Making the following modifications to the environment received from the parent node before applying it to the task host: - Fazendo as seguintes modificações no ambiente recebido do nó pai antes de aplicá-lo ao host de tarefas: - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. - Definindo "{0}" como "{1}" em vez do valor do ambiente pai, "{2}". - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - MSB4025: The project file could not be loaded. {0} - MSB4025: Não foi possível carregar o arquivo de projeto. {0} - {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is - invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is - provided separately to loggers. - LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. - - - MSB4103: "{0}" is not a valid logger verbosity level. - MSB4103: "{0}" não é um nível de detalhamento de agente de log válido. - {StrBegin="MSB4103: "} - - - MSBuild is expecting a valid "{0}" object. - O MSBuild está esperando um objeto "{0}" válido. - - - - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. - Caminho: {0} excede o limite máximo do caminho do SO. O nome do arquivo totalmente qualificado deve ter menos de {1} caracteres. - - - - MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". - MSB5028: o arquivo de filtro da solução em "{0}" inclui o projeto "{1}" que não está no arquivo da solução em "{2}". - {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. - - - MSB5025: Json in solution filter file "{0}" is incorrectly formatted. - MSB5025: o JSON no arquivo de filtro da solução "{0}" está formatado incorretamente. - {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. - - - MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. - MSB5026: o arquivo de filtro da solução em "{0}" especifica que haverá um arquivo de solução em "{1}", mas esse arquivo não existe. - {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. - MSB5009: erro ao analisar a seção do projeto "{0}" com o GUID: "{1}". Ela está aninhada em "{2}", mas o projeto não foi encontrado na solução. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. - MSB4132: A versão das ferramentas "{0}" não é reconhecida. As versões das ferramentas disponíveis são {1}. - {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. - - - MSB5016: The name "{0}" contains an invalid character "{1}". - MSB5016: O nome "{0}" contém um caractere inválido "{1}". - {StrBegin="MSB5016: "} - - - "{0}" is a reserved item metadata, and cannot be modified or deleted. - "{0}" são metadados de item reservado e não podem ser modificados ou excluídos. - UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. - - - The string "{0}" cannot be converted to a boolean (true/false) value. - A cadeia de caracteres "{0}" não pode ser convertida em um valor booliano (true/false). - - - - MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} - MSB5003: Falha ao criar arquivo temporário. A pasta de arquivos temporários está cheia ou o caminho está incorreto. {0} - {StrBegin="MSB5003: "} - - - MSB5018: Failed to delete the temporary file "{0}". {1} {2} - MSB5018: falha ao excluir o arquivo temporário "{0}". {1} {2} - {StrBegin="MSB5018: "} - - - The item metadata "%({0})" cannot be applied to the path "{1}". {2} - Os metadados do item "%({0})" não podem ser aplicados ao caminho "{1}". {2} - UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. - - - MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. - MSB4077: A tarefa "{0}" foi marcada com o atributo LoadInSeparateAppDomain, mas não é derivada de MarshalByRefObject. Verifique se a tarefa é derivada de MarshalByRefObject ou de AppDomainIsolatedTask. - {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. - - - .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - Não há suporte para a versão "{0}" do .NET Framework. Especifique um valor da enumeração Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - - - - .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. - Não há suporte para a versão "{0}" do .NET Framework quando o destino for explicitamente o SDK do Windows, que só tem suporte no .NET 4.5 e posterior. Especifique um valor da enumeração Microsoft.Build.Utilities.TargetDotNetFrameworkVersion que seja Version45 ou acima. - - - - Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. - O Visual Studio versão "{0}" não tem suporte. Especifique um valor da enumeração Microsoft.Build.Utilities.VisualStudioVersion. - - - - When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} - Erro ao tentar gerar um caminho de assembly de referência no caminho "{0}" e o moniker de estrutura "{1}". {2} - No Error code because this resource will be used in an exception. The error code is discarded if it is included - - - Could not find directory path: {0} - Não foi possível localizar o caminho do diretório: {0} - Directory must exist - - - You do not have access to: {0} - Você não tem acesso a: {0} - Directory must have access - - - Schema validation - Validação de esquema - - UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: - "MSBUILD : Schema validation error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - - MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. - MSB5002: encerrando a tarefa executável "{0}" porque ela não foi concluída dentro do limite especificado de {1} milissegundos. - {StrBegin="MSB5002: "} - - - Parameter "{0}" cannot be null. - O parâmetro "{0}" não pode ser nulo. - - - - Parameter "{0}" cannot have zero length. - O parâmetro "{0}" não pode ter comprimento zero. - - - - Parameters "{0}" and "{1}" must have the same number of elements. - Os parâmetros "{0}" e "{1}" devem ter o mesmo número de elementos. - - - - The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. - A cadeia de caracteres de recursos "{0}" da tarefa "{1}" não foi encontrada. Confirme se o nome do recurso "{0}" foi digitado corretamente e se o recurso existe no assembly da tarefa. - - - - The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. - A tarefa "{0}" não registrou seus recursos. Para usar o método "TaskLoggingHelper.FormatResourceString()", essa tarefa deve registrar seus recursos durante a construção ou usando a propriedade "TaskResources". - LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. - - - MSB5004: The solution file has two projects named "{0}". - MSB5004: O arquivo de solução tem dois projetos denominados "{0}". - {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. - - - MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. - MSB5005: Erro ao analisar a seção do projeto para o projeto "{0}". O nome do arquivo de projeto "{1}" contém caracteres inválidos. - {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. - - - MSB5006: Error parsing project section for project "{0}". The project file name is empty. - MSB5006: Erro ao analisar a seção do projeto para o projeto "{0}". O nome do arquivo de projeto está vazio. - {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. - - - MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. - MSB5007: Erro ao analisar a seção de configuração do projeto no arquivo de solução. A entrada "{0}" não é válida. - {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. - - - MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. - MSB5008: Erro ao analisar a seção de configuração da solução no arquivo de solução. A entrada "{0}" não é válida. - {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the nested project section in solution file. - MSB5009: Erro ao analisar a seção do projeto aninhado no arquivo de solução. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. - MSB5023: Erro ao analisar a seção do projeto aninhado no arquivo de solução. Um projeto com o GUID "{0}" é listado como aninhado no projeto "{1}", mas não existe na solução. - {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. - - - MSB5010: No file format header found. - MSB5010: Nenhum cabeçalho de formato de arquivo encontrado. - {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. - - - MSB5011: Parent project GUID not found in "{0}" project dependency section. - MSB5011: O projeto pai GUID não foi encontrado na seção de dependência do projeto "{0}". - {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. - - - MSB5012: Unexpected end-of-file reached inside "{0}" project section. - MSB5012: Fim de arquivo inesperado na seção do projeto "{0}". - {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. - - - MSB5013: Error parsing a project section. - MSB5013: Erro ao analisar uma seção do projeto. - {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. - - - MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. - MSB5014: A versão de formato do arquivo não é reconhecida. O MSBuild pode ler apenas os arquivos de solução entre as versões {0}.0 e {1}.0, inclusive. - {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. - - - MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. - MSB5015: As propriedades não podem ser lidas a partir da seção WebsiteProperties do projeto "{0}". - {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. - - - Unrecognized solution version "{0}", attempting to continue. - Versão de solução não reconhecida "{0}". Tentando continuar. - - - - Solution file - Arquivo de solução - UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: - "MSBUILD : Solution file error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - MSB5019: The project file is malformed: "{0}". {1} - MSB5019: Arquivo de projeto malformado: "{0}". {1} - {StrBegin="MSB5019: "} - - - MSB5020: Could not load the project file: "{0}". {1} - MSB5020: Não foi possível carregar o arquivo de projeto: "{0}". {1} - {StrBegin="MSB5020: "} - - - MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. - MSB5021: Encerrando a tarefa executável "{0}" e seus processos filho porque o build foi cancelado. - {StrBegin="MSB5021: "} - - - This collection is read-only. - Esta coleção é somente leitura. - - - - MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. - MSB5024: não foi possível determinar um local válido para o MSBuild. Tente executar esse processo no Prompt de Comando do Desenvolvedor para o Visual Studio. - {StrBegin="MSB5024: "} - - - MSB4233: There was an exception while reading the log file: {0} - MSB4233: houve uma exceção durante a leitura do arquivo de log: {0} - {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. - - - Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. - O parâmetro "{0}" com o valor "{1}" atribuído não pode ter um caminho inválido ou caracteres de arquivo inválidos. - - - - An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} - Ocorreu uma exceção ao expandir um fileSpec com globs: fileSpec: "{0}", assumindo que se trata de um nome de arquivo. Exceção: {1} - - - - MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. - MSB5029: O valor "{0}" do atributo "{1}" no elemento <{2}> no arquivo "{3}" é um curinga que resulta na enumeração de todos os arquivos na unidade, o que provavelmente não foi planejado. Verifique se as propriedades referenciadas estão sempre definidas e se o projeto e o diretório de trabalho atual não estão na raiz da unidade. - {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its - attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. - - - - \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ru.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ru.xlf deleted file mode 100644 index a40cb28ad14..00000000000 --- a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.ru.xlf +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - MSB4188: Build was canceled. - MSB4188: сборка отменена. - {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. - - - MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. - MSB5022: узел сборки MSBuild не поддерживает задачи, которые выполняют обратные вызовы IBuildEngine. Если необходимо выполнить эти операции, запустите задачу в основном процессе MSBuild. Задача будет автоматически выполнена в узле задач, если для UsingTask был указан атрибут со значением "Runtime" или "Architecture" или для вызова задачи был указан атрибут со значением "MSBuildRuntime" или "MSBuildArchitecture", которые не соответствуют текущей среде выполнения или архитектуре MSBuild. - {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. - - - Build started. - Сборка начата. - - - - The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). - Количество элементов в коллекции превышает доступное пространство в целевом массиве (при запуске с указанного индекса). - - - - MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". - MSB4008: в "{1}" обнаружена сборка, конфликтующая со сборкой задачи "{0}". - {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. - - - Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs - Настраиваемый тип '{0}' не поддерживается, так как все пользовательские типы событий устарели. Используйте extended*EventArgs. Дополнительные сведения: https://aka.ms/msbuild/eventargs - - - - Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. - Необходимо, чтобы тип события "{0}" был сериализуемым с помощью сериализатора .NET. Событие не было сериализуемым и было пропущено. - - - - {0} ({1},{2}) - {0} ({1},{2}) - A file location to be embedded in a string. - - - MSB6005: Task attempted to log before it was initialized. Message was: {0} - MSB6005: задачей предпринята попытка вести журнал до своей инициализации. Сообщение: {0} - {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. - - - MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. - MSB3511: "{0}" — недопустимое значение для параметра Importance (Важность). Допустимые значения: High (высокая), Normal (средняя) и Low (низкая). - {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. - The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown - LOCALIZATION: "Importance" should not be localized. - High should not be localized. - Normal should not be localized. - Low should not be localized. - - - Making the following modifications to the environment received from the parent node before applying it to the task host: - Перед применением окружения, полученного от родительского узла, к серверу задач в нем выполняются следующие изменения: - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. - Задание для "{0}" значения "{1}", а не значения родительского окружения "{2}". - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - MSB4025: The project file could not be loaded. {0} - MSB4025: не удалось загрузить файл проекта. {0} - {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is - invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is - provided separately to loggers. - LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. - - - MSB4103: "{0}" is not a valid logger verbosity level. - MSB4103: "{0}" не является допустимым уровнем детализации средства ведения журнала. - {StrBegin="MSB4103: "} - - - MSBuild is expecting a valid "{0}" object. - Для MSBuild требуется допустимый объект "{0}". - - - - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. - Длина пути {0} превышает максимально допустимую в ОС. Символов в полном имени файла должно быть не больше {1}. - - - - MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". - MSB5028: файл фильтра решения в "{0}" включает проект "{1}", который отсутствует в файле решения в "{2}". - {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. - - - MSB5025: Json in solution filter file "{0}" is incorrectly formatted. - MSB5025: код JSON в файле фильтра решения "{0}" имеет неправильный формат. - {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. - - - MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. - MSB5026: файл фильтра решения в "{0}" указывает на то, что в "{1}" будет находиться файл решения, однако этот файл отсутствует. - {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. - MSB5009: ошибка при синтаксическом анализе раздела проекта "{0}" с GUID: "{1}". Он вложен в "{2}", но этот проект не найден в решении. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. - MSB4132: версия инструментов "{0}" не распознана. Доступные версии инструментов: {1}. - {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. - - - MSB5016: The name "{0}" contains an invalid character "{1}". - MSB5016: имя "{0}" содержит недопустимый знак "{1}". - {StrBegin="MSB5016: "} - - - "{0}" is a reserved item metadata, and cannot be modified or deleted. - "{0}" - зарезервированные метаданные элемента; их изменение или удаление невозможно. - UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. - - - The string "{0}" cannot be converted to a boolean (true/false) value. - Строка "{0}" не может быть преобразована в логическое значение (истина/ложь). - - - - MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} - MSB5003: не удалось создать временный файл. Папка временных файлов переполнена, или указан неверный путь. {0} - {StrBegin="MSB5003: "} - - - MSB5018: Failed to delete the temporary file "{0}". {1} {2} - MSB5018: не удалось удалить временный файл "{0}". {1} {2} - {StrBegin="MSB5018: "} - - - The item metadata "%({0})" cannot be applied to the path "{1}". {2} - Не удается применить метаданные элемента "%({0})" к пути "{1}". {2} - UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. - - - MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. - MSB4077: задача "{0}" помечена атрибутом LoadInSeparateAppDomain, но не является производной от MarshalByRefObject. Задача должна быть производной от MarshalByRefObject или AppDomainIsolatedTask. - {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. - - - .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - .NET Framework версии "{0}" не поддерживается. Укажите значение из перечисления Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - - - - .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. - При явном использовании пакета Windows SDK платформа .NET Framework версии "{0}" не поддерживается; она поддерживается только платформой .NET 4.5 и более поздними версиями. Укажите значение из перечисления Microsoft.Build.Utilities.TargetDotNetFrameworkVersion версии Version45 или более поздней. - - - - Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. - Visual Studio версии "{0}" не поддерживается. Укажите значение из перечисления Microsoft.Build.Utilities.VisualStudioVersion. - - - - When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} - Ошибка при попытке создать ссылочный путь к сборке из пути "{0}" и моникер инфраструктуры "{1}". {2} - No Error code because this resource will be used in an exception. The error code is discarded if it is included - - - Could not find directory path: {0} - Не удалось найти путь к каталогу: {0} - Directory must exist - - - You do not have access to: {0} - Отсутствуют права доступа к: {0} - Directory must have access - - - Schema validation - Проверка схемы - - UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: - "MSBUILD : Schema validation error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - - MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. - MSB5002: завершается исполняемый файл задачи "{0}", так как он не закончил работу в указанный период {1} мс. - {StrBegin="MSB5002: "} - - - Parameter "{0}" cannot be null. - Параметр "{0}" не может иметь неопределенное (null) значение. - - - - Parameter "{0}" cannot have zero length. - Длина параметра "{0}" не может быть равна нулю. - - - - Parameters "{0}" and "{1}" must have the same number of elements. - Параметры "{0}" и "{1}" должны содержать одинаковое количество элементов. - - - - The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. - Не удалось найти строку ресурса "{0}" для задачи "{1}". Убедитесь, что имя ресурса "{0}" написано правильно и ресурс существует в сборке задачи. - - - - The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. - Задача "{0}" не зарегистрировала свои ресурсы. Чтобы использовать метод "TaskLoggingHelper.FormatResourceString()", эта задача должна зарегистрировать свои ресурсы во время построения или через свойство "TaskResources". - LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. - - - MSB5004: The solution file has two projects named "{0}". - MSB5004: файл решения содержит два проекта с именем "{0}". - {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. - - - MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. - MSB5005: ошибка синтаксического анализа раздела проекта "{0}". Имя файла проекта "{1}" содержит недопустимые символы. - {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. - - - MSB5006: Error parsing project section for project "{0}". The project file name is empty. - MSB5006: ошибка синтаксического анализа раздела проекта "{0}". Имя файла проекта пусто. - {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. - - - MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. - MSB5007: ошибка синтаксического анализа раздела конфигурации проекта в файле решения. Недопустимая запись "{0}". - {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. - - - MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. - MSB5008: ошибка синтаксического анализа раздела конфигурации проекта в файле решения. Недопустимая запись "{0}". - {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the nested project section in solution file. - MSB5009: ошибка синтаксического анализа вложенного раздела проекта в файле решения. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. - MSB5023: ошибка синтаксического анализа вложенного раздела проекта в файле решения. Указано, что проект с GUID "{0}" вложен в проект "{1}", но сам проект отсутствует в решении. - {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. - - - MSB5010: No file format header found. - MSB5010: заголовок формата файла не найден. - {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. - - - MSB5011: Parent project GUID not found in "{0}" project dependency section. - MSB5011: родительский проект GUID не найден в разделе зависимости проекта "{0}". - {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. - - - MSB5012: Unexpected end-of-file reached inside "{0}" project section. - MSB5012: достигнуто непредвиденное окончание файла в разделе проекта "{0}". - {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. - - - MSB5013: Error parsing a project section. - MSB5013: ошибка при синтаксическом анализе раздела проекта. - {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. - - - MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. - MSB5014: не распознана версия формата файла. MSBuild может считывать файлы решения только версий с {0}.0 по {1}.0 включительно. - {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. - - - MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. - MSB5015: не удалось прочитать свойства из раздела WebsiteProperties проекта "{0}". - {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. - - - Unrecognized solution version "{0}", attempting to continue. - Нераспознанная версия решения "{0}"; попытка продолжить. - - - - Solution file - Файл решения - UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: - "MSBUILD : Solution file error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - MSB5019: The project file is malformed: "{0}". {1} - MSB5019: файл проекта имеет неправильный формат: "{0}". {1} - {StrBegin="MSB5019: "} - - - MSB5020: Could not load the project file: "{0}". {1} - MSB5020: не удалось загрузить файл проекта: "{0}". {1} - {StrBegin="MSB5020: "} - - - MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. - MSB5021: завершается исполняемый файл задачи "{0}" и его дочерние процессы, так как сборка отменена. - {StrBegin="MSB5021: "} - - - This collection is read-only. - Эта коллекция доступна только для чтения. - - - - MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. - MSB5024: Не удалось определить допустимое расположение для MSBuild. Попробуйте запустить этот процесс из командной строки разработчика для Visual Studio. - {StrBegin="MSB5024: "} - - - MSB4233: There was an exception while reading the log file: {0} - MSB4233: возникло исключение при чтении файла журнала: {0} - {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. - - - Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. - Параметр "{0}" с назначенным значением "{1}" не может иметь недопустимый путь или недопустимые символы файлов. - - - - An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} - Возникло исключение при расширении fileSpec со стандартными масками: fileSpec: "{0}", предполагая, что это имя файла. Исключение: {1} - - - - MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. - MSB5029: значение "{0}" атрибута "{1}" в элементе <{2}> в файле "{3}" является подстановочным знаком, который приводит к перечислению всех файлов на диске, что, вероятно, не предполагалось. Убедитесь, что указанные свойства всегда определены и что проект и текущий рабочий каталог не находятся в корне диска. - {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its - attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. - - - - \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.tr.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.tr.xlf deleted file mode 100644 index e5e76c65345..00000000000 --- a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.tr.xlf +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - MSB4188: Build was canceled. - MSB4188: Derleme iptal edildi. - {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. - - - MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. - MSB5022: MSBuild görev konağı IBuildEngine geri çağırmaları gerçekleştiren görevleri çalıştırmayı desteklemez. Bu işlemleri gerçekleştirmek istiyorsanız lütfen bunun yerine görevinizi çekirdek MSBuild işleminde gerçekleştirin. UsingTask öğesine bir "Runtime" veya "Architecture" değeri atfedilmişse veya görev çağrısına MSBuild’in geçerli çalışma zamanı ya da mimarisi ile eşleşmeyen bir "MSBuildRuntime" veya "MSBuildArchitecture" değeri atfedilmişse görev konağında bir görev otomatik olarak yürütülür. - {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. - - - Build started. - Oluşturma başlatıldı. - - - - The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). - Koleksiyondaki öğe sayısı hedef dizideki kullanılabilir boşluğun boyutundan (belirtilen dizinden başlayarak) daha büyük. - - - - MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". - MSB4008: "{0}" görev derlemesi için "{1}" konumunda çakışan derleme bulundu. - {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. - - - Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs - Tüm özel '{0}' türleri kullanım dışı bırakıldığinden özel olay türü türü desteklenmiyor. Lütfen bunun yerine Extended*EventArgs kullanın. Daha fazla bilgi: https://aka.ms/msbuild/eventargs - - - - Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. - "{0}" olay türünün .NET serileştiricisi kullanılarak serileştirilebilir olması bekleniyordu. Olay serileştirilebilir değildi ve yoksayıldı. - - - - {0} ({1},{2}) - {0} ({1},{2}) - A file location to be embedded in a string. - - - MSB6005: Task attempted to log before it was initialized. Message was: {0} - MSB6005: Görev başlatılmadan önce günlüğe yazmaya çalıştı. İleti: {0} - {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. - - - MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. - MSB3511: "{0}", "Importance" parametresi için geçersiz bir değer. Geçerli değerler şunlardır: High, Normal ve Low. - {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. - The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown - LOCALIZATION: "Importance" should not be localized. - High should not be localized. - Normal should not be localized. - Low should not be localized. - - - Making the following modifications to the environment received from the parent node before applying it to the task host: - Üst düğümden alınan ortam görev ana bilgisayarına uygulanmadan önce ortamda aşağıdaki değişiklikler yapılıyor: - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. - '{0}', üst ortamında değeri olan '{2}' yerine '{1}' değerine ayarlanıyor. - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - MSB4025: The project file could not be loaded. {0} - MSB4025: Proje dosyası yüklenemedi. {0} - {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is - invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is - provided separately to loggers. - LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. - - - MSB4103: "{0}" is not a valid logger verbosity level. - MSB4103: "{0}", geçerli bir günlükçü ayrıntı düzeyi değil. - {StrBegin="MSB4103: "} - - - MSBuild is expecting a valid "{0}" object. - MSBuild, geçerli bir "{0}" nesnesi bekliyor. - - - - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. - Yol: {0}, işletim sisteminin en yüksek yol sınırını aşıyor. Tam dosya adı en fazla {1} karakter olmalıdır. - - - - MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". - MSB5028: "{0}" konumundaki çözüm filtresi dosyası, "{2}" konumundaki çözüm dosyasında bulunmayan "{1}" projesini içeriyor. - {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. - - - MSB5025: Json in solution filter file "{0}" is incorrectly formatted. - MSB5025: "{0}" çözüm filtresi dosyasındaki JSON hatalı biçimlendirilmiş. - {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. - - - MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. - MSB5026: "{0}" konumundaki çözüm filtresi dosyası, "{1}" konumunda bir çözüm dosyası olacağını belirtiyor, ancak bu dosya mevcut değil. - {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. - MSB5009: "{1}" GUID'si ile "{0}" proje bölümü ayrıştırılırken hata oluştu. Proje "{2}" altında iç içe yerleştirilmiş ancak bu proje çözümde bulunamadı. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. - MSB4132: Araçlar sürümü "{0}" tanınmıyor. Kullanılabilir araç sürümleri şunlardır: {1}. - {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. - - - MSB5016: The name "{0}" contains an invalid character "{1}". - MSB5016: "{0}" adı geçersiz "{1}" karakterini içeriyor. - {StrBegin="MSB5016: "} - - - "{0}" is a reserved item metadata, and cannot be modified or deleted. - "{0}" ayrılmış bir öğe meta verisi olduğu için değiştirilemez veya silinemez. - UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. - - - The string "{0}" cannot be converted to a boolean (true/false) value. - "{0}" dizesi bir boole (doğru/yanlış) değerine dönüştürülemiyor. - - - - MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} - MSB5003: Geçici bir dosya oluşturulamadı. Geçici dosyalar klasörü dolu veya yolu hatalı. {0} - {StrBegin="MSB5003: "} - - - MSB5018: Failed to delete the temporary file "{0}". {1} {2} - MSB5018: "{0}" geçici dosyası silinemedi. {1} {2} - {StrBegin="MSB5018: "} - - - The item metadata "%({0})" cannot be applied to the path "{1}". {2} - "%({0})" öğe meta verisi "{1}" yoluna uygulanamıyor. {2} - UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. - - - MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. - MSB4077: "{0}" görevi LoadInSeparateAppDomain özniteliğiyle işaretlenmiş, ancak MarshalByRefObject öğesinden türetilmiyor. Görevin MarshalByRefObject veya AppDomainIsolatedTask öğesinden türetildiğini denetleyin. - {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. - - - .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - .NET Framework "{0}" sürümü desteklenmiyor. Lütfen Microsoft.Build.Utilities.TargetDotNetFrameworkVersion sabit listesinden bir değer belirtin. - - - - .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. - Yalnızca .NET 4.5 ve sonraki sürümlerde desteklenen Windows SDK açıkça hedeflenirken .NET Framework "{0}" sürümü desteklenmez. Lütfen Microsoft.Build.Utilities.TargetDotNetFrameworkVersion sabit listesinden Sürüm 45 veya üzerinde bir değer belirtin. - - - - Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. - Visual Studio "{0}" sürümü desteklenmiyor. Lütfen Microsoft.Build.Utilities.VisualStudioVersion sabit listesinden bir değer belirtin. - - - - When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} - "{0}" yolundan ve "{1}" framework adından bir başvuru bütünleştirilmiş kod yolu oluşturmaya çalışılırken bir hata oluştu. {2} - No Error code because this resource will be used in an exception. The error code is discarded if it is included - - - Could not find directory path: {0} - Dizin yolu bulunamadı: {0} - Directory must exist - - - You do not have access to: {0} - Şu öğeye erişiminiz yok: {0} - Directory must have access - - - Schema validation - Şema doğrulama - - UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: - "MSBUILD : Schema validation error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - - MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. - MSB5002: "{0}" görev yürütülebilir dosyası belirtilen {1} milisaniye sınırı içinde tamamlanmadığından sonlandırılıyor. - {StrBegin="MSB5002: "} - - - Parameter "{0}" cannot be null. - "{0}" parametresi null olamaz. - - - - Parameter "{0}" cannot have zero length. - "{0}" parametresinin uzunluğu sıfır olamaz. - - - - Parameters "{0}" and "{1}" must have the same number of elements. - "{0}" ve "{1}" parametrelerinin öğe sayıları aynı olmalıdır. - - - - The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. - "{1}" görevi için "{0}" kaynak dizesi bulunamıyor. "{0}" kaynak adının doğru yazıldığını ve kaynağın görevin bütünleştirilmiş kodunda bulunduğunu doğrulayın. - - - - The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. - "{0}" görevi, kaynaklarını kaydetmedi. "TaskLoggingHelper.FormatResourceString()" metodunu kullanmak için, bu görevin oluşturma sırasında veya "TaskResources" özelliği aracılığıyla kaynaklarını kaydetmesi gerekir. - LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. - - - MSB5004: The solution file has two projects named "{0}". - MSB5004: Çözüm dosyasında "{0}" adlı iki proje var. - {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. - - - MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. - MSB5005: "{0}" projesi için proje bölümü ayrıştırma hatası. "{1}" proje dosyası adı geçersiz karakterler içeriyor. - {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. - - - MSB5006: Error parsing project section for project "{0}". The project file name is empty. - MSB5006: "{0}" projesi için proje bölümü ayrıştırma hatası. Proje dosyası adı boş. - {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. - - - MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. - MSB5007: Çözüm dosyasında proje yapılandırma bölümünü ayrıştırma hatası. "{0}" girdisi geçersiz. - {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. - - - MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. - MSB5008: Çözüm dosyasında çözüm yapılandırma bölümünü ayrıştırma hatası. "{0}" girdisi geçersiz. - {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the nested project section in solution file. - MSB5009: Çözüm dosyasındaki iç içe proje bölümünü ayrıştırma hatası oluştu. - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. - MSB5023: Çözüm dosyasındaki iç içe proje bölümünü ayrıştırma hatası. GUID’si "{0}" olan bir proje, "{1}" projesi altında iç içe olarak listelendi, ancak çözümde yok. - {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. - - - MSB5010: No file format header found. - MSB5010: Dosya biçimi üstbilgisi bulunamadı. - {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. - - - MSB5011: Parent project GUID not found in "{0}" project dependency section. - MSB5011: Üst proje GUID'i, "{0}" proje bağımlılığı bölümünde bulunamadı. - {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. - - - MSB5012: Unexpected end-of-file reached inside "{0}" project section. - MSB5012: "{0}" proje bölümünde beklenmeyen şekilde dosya sonuna ulaşıldı. - {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. - - - MSB5013: Error parsing a project section. - MSB5013: Proje bölümünü ayrıştırma hatası oluştu. - {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. - - - MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. - MSB5014: Dosya biçimi sürümü tanınmıyor. MSBuild yalnızca {0}.0 ile {1}.0 (dahil) sürümleri arasındaki çözüm dosyalarını okuyabilir. - {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. - - - MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. - MSB5015: Özellikler, "{0}" projesinin WebsiteProperties bölümünden okunamadı. - {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. - - - Unrecognized solution version "{0}", attempting to continue. - Tanınmayan çözüm sürümü "{0}"; devam edilmeye çalışılıyor. - - - - Solution file - Çözüm dosyası - UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: - "MSBUILD : Solution file error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - MSB5019: The project file is malformed: "{0}". {1} - MSB5019: Proje dosyası yanlış biçimlendirilmiş: "{0}". {1} - {StrBegin="MSB5019: "} - - - MSB5020: Could not load the project file: "{0}". {1} - MSB5020: Proje dosyası yüklenemedi: "{0}". {1} - {StrBegin="MSB5020: "} - - - MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. - MSB5021: Derleme iptal edildiğinden "{0}" görev yürütülebilir dosyası ve bunun alt öğeleri sonlandırılıyor. - {StrBegin="MSB5021: "} - - - This collection is read-only. - Bu koleksiyon salt okunur. - - - - MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. - MSB5024: MSBuild için geçerli bir konum belirlenemedi. Bu işlemi Visual Studio için Geliştirici Komut İstemi’nde çalıştırmayı deneyin. - {StrBegin="MSB5024: "} - - - MSB4233: There was an exception while reading the log file: {0} - MSB4233: Günlük dosyası okunurken bir özel durum oluştu: {0} - {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. - - - Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. - "{1}" değeri atanan "{0}" parametresinde geçersiz yol veya geçersiz dosya karakterleri bulunamaz. - - - - An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} - globs: fileSpec: "{0}" ile bir fileSpec genişletilirken özel bir durum oluştu, bunun bir dosya adı olduğu varsayıldı. Özel Durum: {1} - - - - MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. - MSB5029: "{0}" dosyasındaki <{1}> öğesinde "{2}" özniteliğinin "{3}" değeri, sürücüdeki tüm dosyaların numaralandırılmasıyla sonuçlanan (büyük olasılıkla bunun olması amaçlanmıyordu) bir joker karakterdir. Başvurulan özelliklerin her zaman tanımlandığını ve projenin ve geçerli çalışma dizininin sürücü kökünde bulunmadığını kontrol edin. - {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its - attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. - - - - \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.zh-Hans.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.zh-Hans.xlf deleted file mode 100644 index f0540b9f5e0..00000000000 --- a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.zh-Hans.xlf +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - MSB4188: Build was canceled. - MSB4188: 已取消生成。 - {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. - - - MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. - MSB5022: MSBuild 任务宿主不支持运行执行 IBuildEngine 回调的任务。如果需要执行这些操作,请改为在核心 MSBuild 进程中运行您的任务。如果 UsingTask 已用“Runtime”或“Architecture”值特性化,或者任务调用已用“MSBuildRuntime”或“MSBuildArchitecture”值(与 MSBuild 的当前运行时或体系结构不匹配)特性化,则将自动在任务宿主中执行任务。 - {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. - - - Build started. - 已启动生成。 - - - - The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). - 集合中的元素数大于目标阵列中的可用空间 (从指定索引开始时)。 - - - - MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". - MSB4008: 在“{1}”处发现了与任务程序集“{0}”冲突的程序集。 - {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. - - - Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs - 不支持自定义事件类型 '{0}',因为已弃用所有自定义事件类型。请改用 Extended*EventArgs。详细信息: https://aka.ms/msbuild/eventargs - - - - Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. - 事件类型“{0}”应可以使用 .NET 序列化程序进行序列化。此事件不可序列化,已忽略它。 - - - - {0} ({1},{2}) - {0} ({1},{2}) - A file location to be embedded in a string. - - - MSB6005: Task attempted to log before it was initialized. Message was: {0} - MSB6005: 任务尚未初始化就尝试进行日志记录。消息为: {0} - {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. - - - MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. - MSB3511: “{0}”是无效的“Importance”参数值。有效值包括: High、Normal 和 Low。 - {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. - The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown - LOCALIZATION: "Importance" should not be localized. - High should not be localized. - Normal should not be localized. - Low should not be localized. - - - Making the following modifications to the environment received from the parent node before applying it to the task host: - 先对从父节点收到的环境进行以下修改,然后再将其应用于任务宿主: - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. - 将“{0}”设置为“{1}”,而不是父环境的值“{2}”。 - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - MSB4025: The project file could not be loaded. {0} - MSB4025: 未能加载项目文件。{0} - {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is - invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is - provided separately to loggers. - LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. - - - MSB4103: "{0}" is not a valid logger verbosity level. - MSB4103: "{0}" 不是有效的记录器详细程度。 - {StrBegin="MSB4103: "} - - - MSBuild is expecting a valid "{0}" object. - MSBuild 需要有效的“{0}”对象。 - - - - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. - 路径: {0} 超过 OS 最大路径限制。完全限定的文件名必须少于 {1} 个字符。 - - - - MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". - MSB5028: 位于“{0}”的解决方案筛选器文件包含“{2}”处的解决方案文件中没有的项目“{1}”。 - {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. - - - MSB5025: Json in solution filter file "{0}" is incorrectly formatted. - MSB5025: 解决方案筛选器文件“{0}”中的 JSON 的格式不正确。 - {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. - - - MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. - MSB5026: 位于“{0}”的解决方案筛选器文件指定“{1}”处将存在一个解决方案文件,但该文件不存在。 - {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. - MSB5009: 分析 GUID 为 "{1}" 的项目 "{0}" 部分时出错。它嵌套在 "{2}" 下,但在解决方案中未找到该项目。 - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. - MSB4132: 无法识别工具版本“{0}”。可用的工具版本为 {1}。 - {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. - - - MSB5016: The name "{0}" contains an invalid character "{1}". - MSB5016: 名称“{0}”包含无效字符“{1}”。 - {StrBegin="MSB5016: "} - - - "{0}" is a reserved item metadata, and cannot be modified or deleted. - “{0}”是保留的项元数据,不能修改或删除。 - UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. - - - The string "{0}" cannot be converted to a boolean (true/false) value. - 无法将字符串“{0}”转换为布尔值(true/false)。 - - - - MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} - MSB5003: 未能创建临时文件。临时文件文件夹已满或其路径不正确。{0} - {StrBegin="MSB5003: "} - - - MSB5018: Failed to delete the temporary file "{0}". {1} {2} - MSB5018: 未能删除临时文件“{0}”。{1} {2} - {StrBegin="MSB5018: "} - - - The item metadata "%({0})" cannot be applied to the path "{1}". {2} - 无法将项元数据“%({0})”应用于路径“{1}”。{2} - UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. - - - MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. - MSB4077: “{0}”任务已标记为 LoadInSeparateAppDomain 特性,但未派生自 MarshalByRefObject。请检查该任务是派生自 MarshalByRefObject 还是 AppDomainIsolatedTask。 - {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. - - - .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - 不支持 .NET Framework 版本“{0}”。请指定枚举 Microsoft.Build.Utilities.TargetDotNetFrameworkVersion 中的某个值。 - - - - .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. - 当明确以 Windows SDK 为目标时,不支持 .NET Framework 版本“{0}”,Windows SDK 只在 .NET 4.5 及更高版本上受支持。请指定枚举 Microsoft.Build.Utilities.TargetDotNetFrameworkVersion 中 Version45 或以上的值。 - - - - Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. - 不支持 Visual Studio 版本“{0}”。请指定枚举 Microsoft.Build.Utilities.VisualStudioVersion 中的某个值。 - - - - When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} - 尝试从路径“{0}”和框架名字对象“{1}”生成引用程序集路径时出错。{2} - No Error code because this resource will be used in an exception. The error code is discarded if it is included - - - Could not find directory path: {0} - 未能找到目录路径: {0} - Directory must exist - - - You do not have access to: {0} - 您无权访问 {0} - Directory must have access - - - Schema validation - 架构验证 - - UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: - "MSBUILD : Schema validation error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - - MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. - MSB5002: 终止任务可执行文件“{0}”,因为它未在指定的 {1} 毫秒限制内完成。 - {StrBegin="MSB5002: "} - - - Parameter "{0}" cannot be null. - 参数“{0}”不能为 null。 - - - - Parameter "{0}" cannot have zero length. - 参数“{0}”的长度不能为零。 - - - - Parameters "{0}" and "{1}" must have the same number of elements. - 参数“{0}”和“{1}”的元素数目必须相同。 - - - - The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. - 找不到“{1}”任务的资源字符串“{0}”。请确认资源名称“{0}”的拼写正确,并且任务的程序集中存在该资源。 - - - - The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. - 任务“{0}”尚未注册其资源。此任务需要在构造期间或通过“TaskResources”属性注册其资源,然后才能使用“TaskLoggingHelper.FormatResourceString()”方法。 - LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. - - - MSB5004: The solution file has two projects named "{0}". - MSB5004: 解决方案文件有两个名为“{0}”的项目。 - {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. - - - MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. - MSB5005: 分析项目“{0}”的项目节时出错。项目文件名“{1}”包含无效字符。 - {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. - - - MSB5006: Error parsing project section for project "{0}". The project file name is empty. - MSB5006: 分析项目“{0}”的项目节时出错。项目文件名为空。 - {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. - - - MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. - MSB5007: 分析解决方案文件中的项目配置节时出错。“{0}”项无效。 - {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. - - - MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. - MSB5008: 分析解决方案文件中的解决方案配置节时出错。“{0}”项无效。 - {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the nested project section in solution file. - MSB5009: 分析解决方案文件中的嵌套项目节时出错。 - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. - MSB5023: 分析解决方案文件中的嵌套项目节时出错。GUID 为“{0}”的项目列为嵌套在项目“{1}”下,但不存在于解决方案中。 - {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. - - - MSB5010: No file format header found. - MSB5010: 未找到文件格式头。 - {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. - - - MSB5011: Parent project GUID not found in "{0}" project dependency section. - MSB5011: 在“{0}”项目依赖项节中未找到父项目 GUID。 - {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. - - - MSB5012: Unexpected end-of-file reached inside "{0}" project section. - MSB5012: 在“{0}”项目节内部意外到达文件尾。 - {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. - - - MSB5013: Error parsing a project section. - MSB5013: 分析项目节时出错。 - {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. - - - MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. - MSB5014: 无法识别文件格式版本。MSBuild 只能读取版本 {0}.0 到 {1}.0 之间(包括这两个版本)的解决方案文件。 - {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. - - - MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. - MSB5015: 未能从“{0}”项目的 WebsiteProperties 节中读取属性。 - {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. - - - Unrecognized solution version "{0}", attempting to continue. - 无法识别解决方案版本“{0}”,正在尝试继续。 - - - - Solution file - 解决方案文件 - UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: - "MSBUILD : Solution file error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - MSB5019: The project file is malformed: "{0}". {1} - MSB5019: 项目文件格式不正确: “{0}”。{1} - {StrBegin="MSB5019: "} - - - MSB5020: Could not load the project file: "{0}". {1} - MSB5020: 未能加载项目文件: “{0}”。{1} - {StrBegin="MSB5020: "} - - - MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. - MSB5021: 终止任务可执行文件“{0}”及其子进程,因为已取消生成。 - {StrBegin="MSB5021: "} - - - This collection is read-only. - 此集合是只读的。 - - - - MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. - MSB5024: 无法确定到 MSBuild 的有效位置。请尝试从 Visual Studio 开发人员命令提示处运行此进程。 - {StrBegin="MSB5024: "} - - - MSB4233: There was an exception while reading the log file: {0} - MSB4233: 读取日志文件时出现异常: {0} - {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. - - - Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. - 分配有值“{1}”的参数“{0}”不可具有无效路径或无效的文件字符。 - - - - An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} - 使用 glob 展开 fileSpec 时发生异常: fileSpec:“{0}”,系统假定它是文件名。异常: {1} - - - - MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. - MSB5029: 文件“{3}”中元素 <{2}> 中“{1}”属性的值“{0}”是通配符,可导致枚举驱动器上的所有文件,这可能不是预期的行为。确认始终定义了引用的属性,并且项目和当前工作目录不在驱动器根目录下。 - {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its - attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. - - - - \ No newline at end of file diff --git a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.zh-Hant.xlf b/src/MSBuildTaskHost/Resources/xlf/Strings.shared.zh-Hant.xlf deleted file mode 100644 index a7e8187bddb..00000000000 --- a/src/MSBuildTaskHost/Resources/xlf/Strings.shared.zh-Hant.xlf +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - MSB4188: Build was canceled. - MSB4188: 建置已取消。 - {StrBegin="MSB4188: "} Error when the build stops suddenly for some reason. For example, because a child node died. - - - MSB5022: The MSBuild task host does not support running tasks that perform IBuildEngine callbacks. If you wish to perform these operations, please run your task in the core MSBuild process instead. A task will automatically execute in the task host if the UsingTask has been attributed with a "Runtime" or "Architecture" value, or the task invocation has been attributed with an "MSBuildRuntime" or "MSBuildArchitecture" value, that does not match the current runtime or architecture of MSBuild. - MSB5022: MSBuild 工作主機不支援會執行 IBuildEngine 回撥的工作。若您想要執行這些作業,請改為在核心 MSBuild 處理序執行您的工作。若 UsingTask 已由 "Runtime" 或 "Architecture" 值賦予屬性,或工作引動過程已由 "MSBuildRuntime" 或 "MSBuildArchitecture" 值賦予屬性 (不符合 MSBuild 的目前執行階段或結構),則工作將自動在工作主機中執行。 - {StrBegin="MSB5022: "} "Runtime", "Architecture", "MSBuildRuntime", and "MSBuildArchitecture" are all attributes in the project file, and thus should not be localized. - - - Build started. - 已經開始建置。 - - - - The number of elements in the collection is greater than the available space in the destination array (when starting at the specified index). - 集合中的元素數大於目標陣列中的可用空間 (從指定索引開始時)。 - - - - MSB4008: A conflicting assembly for the task assembly "{0}" has been found at "{1}". - MSB4008: 已在 "{1}" 中發現工作組件 "{0}" 的衝突組件。 - {StrBegin="MSB4008: "}UE: This message is shown when the type/class of a task cannot be resolved uniquely from a single assembly. - - - Custom event type '{0}' is not supported as all custom event types were deprecated. Please use Extended*EventArgs instead. More info: https://aka.ms/msbuild/eventargs - 不支援自定義事件類型 '{0}',因為所有自定義事件類型都已過時。請改用 Extended*EventArgs。更多資訊: https://aka.ms/msbuild/eventargs - - - - Event type "{0}" was expected to be serializable using the .NET serializer. The event was not serializable and has been ignored. - 事件類型 "{0}" 應該可以使用 .NET 序列化程式序列化。此事件不可序列化,已略過。 - - - - {0} ({1},{2}) - {0} ({1},{2}) - A file location to be embedded in a string. - - - MSB6005: Task attempted to log before it was initialized. Message was: {0} - MSB6005: 工作在初始化之前就嘗試記錄。訊息為: {0} - {StrBegin="MSB6005: "}UE: This occurs if the task attempts to log something in its own constructor. - - - MSB3511: "{0}" is an invalid value for the "Importance" parameter. Valid values are: High, Normal and Low. - MSB3511: "{0}" 對 "Importance" 參數而言為無效值。有效值為: High、Normal 和 Low。 - {StrBegin="MSB3511: "}UE: This message is shown when a user specifies a value for the importance attribute of Message which is not valid. - The importance enumeration is: High, Normal and Low. Specifying any other importance will result in this message being shown - LOCALIZATION: "Importance" should not be localized. - High should not be localized. - Normal should not be localized. - Low should not be localized. - - - Making the following modifications to the environment received from the parent node before applying it to the task host: - 在套用到工作主機之前,對從父節點接收的環境進行下列修改: - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - Setting '{0}' to '{1}' rather than the parent environment's value, '{2}'. - 將 '{0}' 設定為 '{1}' 而非父環境的值 '{2}。 - Only ever used when MSBuild is run under a "secret" environment variable switch, MSBuildTaskHostUpdateEnvironmentAndLog=1 - - - MSB4025: The project file could not be loaded. {0} - MSB4025: 無法載入專案檔。{0} - {StrBegin="MSB4025: "}UE: This message is shown when the project file given to the engine cannot be loaded because the filename/path is - invalid, or due to lack of permissions, or incorrect XML. The project filename is not part of the message because it is - provided separately to loggers. - LOCALIZATION: {0} is a localized message from the CLR/FX explaining why the project is invalid. - - - MSB4103: "{0}" is not a valid logger verbosity level. - MSB4103: "{0}" 不是有效的記錄器詳細程度等級。 - {StrBegin="MSB4103: "} - - - MSBuild is expecting a valid "{0}" object. - MSBuild 需要有效的 "{0}" 物件。 - - - - Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. - 路徑: {0} 超過 OS 路徑上限。完整檔案名稱必須少於 {1} 個字元。 - - - - MSB5028: Solution filter file at "{0}" includes project "{1}" that is not in the solution file at "{2}". - MSB5028: 位於 "{0}" 的解決方案篩選檔案包含專案 "{1}",該專案不在位於 "{2}" 的解決方案檔案中。 - {StrBegin="MSB5028: "}UE: The project filename is provided separately to loggers. - - - MSB5025: Json in solution filter file "{0}" is incorrectly formatted. - MSB5025: 解決方案篩選檔案 "{0}" 中的 Json 格式不正確。 - {StrBegin="MSB5025: "}UE: The solution filename is provided separately to loggers. - - - MSB5026: The solution filter file at "{0}" specifies there will be a solution file at "{1}", but that file does not exist. - MSB5026: 位於 "{0}" 的解決方案篩選檔案指定將會有位於 "{1}" 的解決方案檔案,但該檔案並不存在。 - {StrBegin="MSB5026: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the project "{0}" section with GUID: "{1}". It is nested under "{2}" but that project is not found in the solution. - MSB5009: 剖析 GUID 為 "{1}" 的專案 "{0}" 區段時發生錯誤。其為 "{2}" 下方的巢狀專案,但在解決方案中找不到該專案。 - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB4132: The tools version "{0}" is unrecognized. Available tools versions are {1}. - MSB4132: 工具版本 "{0}" 無法辨認。可用的工具版本為 {1}。 - {StrBegin="MSB4132: "}LOCALIZATION: {1} contains a comma separated list. - - - MSB5016: The name "{0}" contains an invalid character "{1}". - MSB5016: 名稱 "{0}" 包含無效字元 "{1}"。 - {StrBegin="MSB5016: "} - - - "{0}" is a reserved item metadata, and cannot be modified or deleted. - "{0}" 是保留的項目中繼資料,不能修改或刪除。 - UE: Tasks and OM users are not allowed to remove or change the value of the built-in metadata on items e.g. the meta-data "FullPath", "RelativeDir", etc. are reserved. - - - The string "{0}" cannot be converted to a boolean (true/false) value. - 無法將字串 "{0}" 轉換成布林值 (true/false)。 - - - - MSB5003: Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} - MSB5003: 無法建立暫存檔案。暫存檔案資料夾已滿或其路徑錯誤。{0} - {StrBegin="MSB5003: "} - - - MSB5018: Failed to delete the temporary file "{0}". {1} {2} - MSB5018: 無法刪除暫存檔 "{0}"。{1}{2} - {StrBegin="MSB5018: "} - - - The item metadata "%({0})" cannot be applied to the path "{1}". {2} - 無法將項目中繼資料 "%({0})" 套用至路徑 "{1}"。{2} - UE: This message is shown when the user tries to perform path manipulations using one of the built-in item metadata e.g. %(RootDir), on an item-spec that's not a valid path. LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. - - - MSB4077: The "{0}" task has been marked with the attribute LoadInSeparateAppDomain, but does not derive from MarshalByRefObject. Check that the task derives from MarshalByRefObject or AppDomainIsolatedTask. - MSB4077: "{0}" 工作已經以屬性 LoadInSeparateAppDomain 標記,但不是衍生自 MarshalByRefObject。請檢查工作是否衍生自 MarshalByRefObject 或 AppDomainIsolatedTask。 - {StrBegin="MSB4077: "}LOCALIZATION: <LoadInSeparateAppDomain>, <MarshalByRefObject>, <AppDomainIsolatedTask> should not be localized. - - - .NET Framework version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion. - 不支援 .NET Framework 版本 "{0}"。請從列舉 Microsoft.Build.Utilities.TargetDotNetFrameworkVersion 中指定值。 - - - - .NET Framework version "{0}" is not supported when explicitly targeting the Windows SDK, which is only supported on .NET 4.5 and later. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion that is Version45 or above. - 明確地以 Windows SDK 為目標時,不支援 .NET Framework 版本 "{0}",只有 .NET 4.5 及更新版本才予以支援。請從 Version45 (含) 以上版本的列舉 Microsoft.Build.Utilities.TargetDotNetFrameworkVersion 中指定值。 - - - - Visual Studio version "{0}" is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.VisualStudioVersion. - 不支援 Visual Studio 版本 "{0}"。請從列舉 Microsoft.Build.Utilities.VisualStudioVersion 中指定值。 - - - - When attempting to generate a reference assembly path from the path "{0}" and the framework moniker "{1}" there was an error. {2} - 嘗試從路徑 "{0}" 和架構 Moniker "{1}" 產生參考組件路徑時,發生錯誤。{2} - No Error code because this resource will be used in an exception. The error code is discarded if it is included - - - Could not find directory path: {0} - 找不到目錄路徑: {0} - Directory must exist - - - You do not have access to: {0} - 您沒有存取權限: {0} - Directory must have access - - - Schema validation - 結構描述驗證 - - UE: this fragment is used to describe errors that are caused by schema validation. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from schema validation would look like this: - "MSBUILD : Schema validation error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - - MSB5002: Terminating the task executable "{0}" because it did not finish within the specified limit of {1} milliseconds. - MSB5002: 因為工作可執行檔 "{0}" 未於指定的 {1} 毫秒限制內完成,所以即將予以終止。 - {StrBegin="MSB5002: "} - - - Parameter "{0}" cannot be null. - 參數 "{0}" 不能為 null。 - - - - Parameter "{0}" cannot have zero length. - 參數 "{0}" 長度不能為零。 - - - - Parameters "{0}" and "{1}" must have the same number of elements. - 參數 "{0}" 和 "{1}" 的項目數目必須相同。 - - - - The resource string "{0}" for the "{1}" task cannot be found. Confirm that the resource name "{0}" is correctly spelled, and the resource exists in the task's assembly. - 找不到工作 "{1}" 的資源字串 "{0}"。請確認資源名稱 "{0}" 拼寫正確,且資源位於這項工作的組件中。 - - - - The "{0}" task has not registered its resources. In order to use the "TaskLoggingHelper.FormatResourceString()" method this task needs to register its resources either during construction, or via the "TaskResources" property. - "{0}" 工作尚未註冊其資源。若要使用 "TaskLoggingHelper.FormatResourceString()" 方法,這項工作必須於建構期間或透過 "TaskResources" 屬性註冊其資源。 - LOCALIZATION: "TaskLoggingHelper.FormatResourceString()" and "TaskResources" should not be localized. - - - MSB5004: The solution file has two projects named "{0}". - MSB5004: 方案檔有兩個名為 "{0}" 的專案。 - {StrBegin="MSB5004: "}UE: The solution filename is provided separately to loggers. - - - MSB5005: Error parsing project section for project "{0}". The project file name "{1}" contains invalid characters. - MSB5005: 剖析專案 "{0}" 的專案區段時發生錯誤。專案檔名稱 "{1}" 包含無效的字元。 - {StrBegin="MSB5005: "}UE: The solution filename is provided separately to loggers. - - - MSB5006: Error parsing project section for project "{0}". The project file name is empty. - MSB5006: 剖析專案 "{0}" 的專案區段時發生錯誤。專案檔名稱為空白。 - {StrBegin="MSB5006: "}UE: The solution filename is provided separately to loggers. - - - MSB5007: Error parsing the project configuration section in solution file. The entry "{0}" is invalid. - MSB5007: 剖析方案檔中的專案組態區段時發生錯誤。項目 "{0}" 無效。 - {StrBegin="MSB5007: "}UE: The solution filename is provided separately to loggers. - - - MSB5008: Error parsing the solution configuration section in solution file. The entry "{0}" is invalid. - MSB5008: 剖析方案檔中的方案組態區段時發生錯誤。項目 "{0}" 無效。 - {StrBegin="MSB5008: "}UE: The solution filename is provided separately to loggers. - - - MSB5009: Error parsing the nested project section in solution file. - MSB5009: 剖析方案檔中的巢狀專案區段時發生錯誤。 - {StrBegin="MSB5009: "}UE: The solution filename is provided separately to loggers. - - - MSB5023: Error parsing the nested project section in solution file. A project with the GUID "{0}" is listed as being nested under project "{1}", but does not exist in the solution. - MSB5023: 剖析方案檔中的巢狀專案區段時發生錯誤。GUID 為 "{0}" 的專案列為專案 "{1}" 下的巢狀專案,但不存在於解決方案中。 - {StrBegin="MSB5023: "}UE: The solution filename is provided separately to loggers. - - - MSB5010: No file format header found. - MSB5010: 找不到檔案格式標頭。 - {StrBegin="MSB5010: "}UE: The solution filename is provided separately to loggers. - - - MSB5011: Parent project GUID not found in "{0}" project dependency section. - MSB5011: 在 "{0}" 專案相依性區段中找不到父專案 GUID。 - {StrBegin="MSB5011: "}UE: The solution filename is provided separately to loggers. - - - MSB5012: Unexpected end-of-file reached inside "{0}" project section. - MSB5012: 在 "{0}" 專案區段內部發現未預期的檔案結尾。 - {StrBegin="MSB5012: "}UE: The solution filename is provided separately to loggers. - - - MSB5013: Error parsing a project section. - MSB5013: 剖析專案區段時發生錯誤。 - {StrBegin="MSB5013: "}UE: The solution filename is provided separately to loggers. - - - MSB5014: File format version is not recognized. MSBuild can only read solution files between versions {0}.0 and {1}.0, inclusive. - MSB5014: 無法識別檔案格式版本。MSBuild 只能讀取版本 {0}.0 到 {1}.0 (含) 的方案檔。 - {StrBegin="MSB5014: "}UE: The solution filename is provided separately to loggers. - - - MSB5015: The properties could not be read from the WebsiteProperties section of the "{0}" project. - MSB5015: 無法從 "{0}" 專案的 WebsiteProperties 區段讀取屬性。 - {StrBegin="MSB5015: "}UE: The solution filename is provided separately to loggers. - - - Unrecognized solution version "{0}", attempting to continue. - 無法辨認的方案版本 "{0}",正在嘗試繼續處理。 - - - - Solution file - 方案檔 - UE: this fragment is used to describe errors found while parsing solution files. For example, if a normal error is - displayed like this: "MSBUILD : error MSB0000: This is an error.", then an error from solution parsing would look like this: - "MSBUILD : Solution file error MSB0000: This is an error." - LOCALIZATION: This fragment needs to be localized. - - - MSB5019: The project file is malformed: "{0}". {1} - MSB5019: 專案檔格式不正確: "{0}"。{1} - {StrBegin="MSB5019: "} - - - MSB5020: Could not load the project file: "{0}". {1} - MSB5020: 無法載入專案檔: "{0}"。{1} - {StrBegin="MSB5020: "} - - - MSB5021: Terminating the task executable "{0}" and its child processes because the build was canceled. - MSB5021: 因為建置已取消,所以即將終止工作可執行檔 "{0}" 及其子處理序。 - {StrBegin="MSB5021: "} - - - This collection is read-only. - 這是一個唯讀集合。 - - - - MSB5024: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. - MSB5024: 無法判斷 MSBuild 的有效位置。請嘗試從 Visual Studio 的開發人員命令提示字元執行此處理序。 - {StrBegin="MSB5024: "} - - - MSB4233: There was an exception while reading the log file: {0} - MSB4233: 讀取記錄檔時發生錯誤: {0} - {StrBegin="MSB4233: "}This is shown when the Binary Logger can't read the log file. - - - Parameter "{0}" with assigned value "{1}" cannot have invalid path or invalid file characters. - 指派值為 "{1}" 的參數 "{0}" 不得具有無效的路徑或檔案字元。 - - - - An exception occurred while expanding a fileSpec with globs: fileSpec: "{0}", assuming it is a file name. Exception: {1} - 展開具有 glob 的 fileSpec 時發生例外狀況: fileSpec: "{0}" (假設它是檔案名稱)。例外狀況: {1} - - - - MSB5029: The value "{0}" of the "{1}" attribute in element <{2}> in file "{3}" is a wildcard that results in enumerating all files on the drive, which was likely not intended. Check that referenced properties are always defined and that the project and current working directory are not at the drive root. - MSB5029: 檔案「{3}」元素 <{2}> 中屬性「{1}」的值「{0}」是萬用字元,導致列舉磁碟機上的所有檔案,這很可能不是預期的結果。檢查參考的屬性是否一直定義,以及專案與目前的工作目錄是否不在磁碟機根目錄中。 - {StrBegin="MSB5029: "}UE: This is a generic message that is displayed when we find a project element that has a drive enumerating wildcard value for one of its - attributes e.g. <Compile Include="$(NotAlwaysDefined)\**\*.cs"> -- if the property is undefined, the value of Include should not result in enumerating all files on drive. - - - - \ No newline at end of file diff --git a/src/MSBuildTaskHost/TaskLoader.cs b/src/MSBuildTaskHost/TaskLoader.cs index 8b388df408b..8accf1ff911 100644 --- a/src/MSBuildTaskHost/TaskLoader.cs +++ b/src/MSBuildTaskHost/TaskLoader.cs @@ -4,6 +4,7 @@ using System; using System.Reflection; using Microsoft.Build.Framework; +using Microsoft.Build.TaskHost.Resources; using Microsoft.Build.TaskHost.Utilities; namespace Microsoft.Build.TaskHost @@ -22,7 +23,7 @@ internal static class TaskLoader /// /// Delegate for logging task loading errors. /// - internal delegate void LogError(string taskLocation, int taskLine, int taskColumn, string message, params object[] messageArgs); + internal delegate void LogError(string taskLocation, int taskLine, int taskColumn, string message); /// /// Checks if the given type is a task factory. @@ -61,13 +62,7 @@ internal static bool IsTaskClass(Type type, object unused) { if (!loadedType.IsMarshalByRef) { - logError( - taskLocation, - taskLine, - taskColumn, - "TaskNotMarshalByRef", - taskName); - + logError(taskLocation, taskLine, taskColumn, string.Format(SR.TaskNotMarshalByRef, taskName)); return null; } else @@ -119,14 +114,7 @@ internal static bool IsTaskClass(Type type, object unused) // to fail here. if (taskType != loadedType.Type) { - logError( - taskLocation, - taskLine, - taskColumn, - "ConflictingTaskAssembly", - loadedType.AssemblyFilePath, - loadedType.Type.Assembly.Location); - + logError(taskLocation, taskLine, taskColumn, string.Format(SR.ConflictingTaskAssembly, loadedType.AssemblyFilePath, loadedType.Type.Assembly.Location)); taskInstanceInOtherAppDomain = null; } } diff --git a/src/MSBuildTaskHost/TypeLoader.cs b/src/MSBuildTaskHost/TypeLoader.cs index 3911607260b..71ab01054cc 100644 --- a/src/MSBuildTaskHost/TypeLoader.cs +++ b/src/MSBuildTaskHost/TypeLoader.cs @@ -215,7 +215,7 @@ private class AssemblyInfoToLoadedTypes /// internal AssemblyInfoToLoadedTypes(TypeFilter typeFilter, string assemblyFilePath) { - ErrorUtilities.VerifyThrowArgumentNull(typeFilter, "typefilter"); + ErrorUtilities.VerifyThrowArgumentNull(typeFilter); ErrorUtilities.VerifyThrowArgumentNull(assemblyFilePath); _isDesiredType = typeFilter; diff --git a/src/MSBuildTaskHost/Utilities/ErrorUtilities.cs b/src/MSBuildTaskHost/Utilities/ErrorUtilities.cs index 22545e2be7c..563b9169678 100644 --- a/src/MSBuildTaskHost/Utilities/ErrorUtilities.cs +++ b/src/MSBuildTaskHost/Utilities/ErrorUtilities.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Build.TaskHost.Exceptions; +using Microsoft.Build.TaskHost.Resources; namespace Microsoft.Build.TaskHost.Utilities { @@ -13,25 +14,37 @@ namespace Microsoft.Build.TaskHost.Utilities /// internal static class ErrorUtilities { + [DoesNotReturn] + internal static void ThrowInternalError(string message) + => throw new InternalErrorException(message); + + [DoesNotReturn] + internal static void ThrowInternalError(string format, object? arg0) + => throw new InternalErrorException(string.Format(format, arg0)); + + [DoesNotReturn] + internal static void ThrowInternalError(string format, object? arg0, object? arg1) + => throw new InternalErrorException(string.Format(format, arg0, arg1)); + + [DoesNotReturn] + internal static void ThrowInternalError(string format, object? arg0, object? arg1, object? arg2) + => throw new InternalErrorException(string.Format(format, arg0, arg1, arg2)); + /// /// Throws InternalErrorException. /// This is only for situations that would mean that there is a bug in MSBuild itself. /// [DoesNotReturn] - internal static void ThrowInternalError(string message, params object?[]? args) - { - throw new InternalErrorException(ResourceUtilities.FormatString(message, args)); - } + internal static void ThrowInternalError(string format, params object?[] args) + => throw new InternalErrorException(string.Format(format, args)); /// /// Throws InternalErrorException. /// This is only for situations that would mean that there is a bug in MSBuild itself. /// [DoesNotReturn] - internal static void ThrowInternalError(string message, Exception? innerException, params object?[]? args) - { - throw new InternalErrorException(ResourceUtilities.FormatString(message, args), innerException); - } + internal static void ThrowInternalError(string message, Exception? innerException) + => throw new InternalErrorException(message, innerException); /// /// Throws InternalErrorException. @@ -40,9 +53,7 @@ internal static void ThrowInternalError(string message, Exception? innerExceptio /// [DoesNotReturn] internal static void ThrowInternalErrorUnreachable() - { - throw new InternalErrorException("Unreachable?"); - } + => throw new InternalErrorException("Unreachable?"); /// /// Helper to throw an InternalErrorException when the specified parameter is null. @@ -51,11 +62,13 @@ internal static void ThrowInternalErrorUnreachable() /// /// The value of the argument. /// Parameter that should not be null - internal static void VerifyThrowInternalNull([NotNull] object? parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) + internal static void VerifyThrowInternalNull( + [NotNull] object? parameter, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) { if (parameter is null) { - ThrowInternalError("{0} unexpectedly null", parameterName); + ThrowInternalError($"{parameterName} unexpectedly null"); } } @@ -66,13 +79,15 @@ internal static void VerifyThrowInternalNull([NotNull] object? parameter, [Calle /// /// The value of the argument. /// Parameter that should not be null or zero length - internal static void VerifyThrowInternalLength([NotNull] string? parameterValue, [CallerArgumentExpression(nameof(parameterValue))] string? parameterName = null) + internal static void VerifyThrowInternalLength( + [NotNull] string? parameterValue, + [CallerArgumentExpression(nameof(parameterValue))] string? parameterName = null) { VerifyThrowInternalNull(parameterValue, parameterName); if (parameterValue.Length == 0) { - ThrowInternalError("{0} unexpectedly empty", parameterName); + ThrowInternalError($"{parameterName} unexpectedly empty"); } } @@ -83,35 +98,33 @@ internal static void VerifyThrowInternalLength([NotNull] string? parameterValue, /// code somewhere. This should not be used to throw errors based on bad /// user input or anything that the user did wrong. /// - internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string unformattedMessage) + internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string message) { if (!condition) { - ThrowInternalError(unformattedMessage, null, null); + ThrowInternalError(message); } } /// /// Overload for one string format argument. /// - internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string unformattedMessage, object arg0) + internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string format, object? arg0) { if (!condition) { - ThrowInternalError(unformattedMessage, arg0); + ThrowInternalError(format, arg0); } } /// /// Throws an InvalidOperationException with the specified resource string /// - /// Resource to use in the exception + /// Resource to use in the exception /// Formatting args. [DoesNotReturn] - internal static void ThrowInvalidOperation(string resourceName, params object?[]? args) - { - throw new InvalidOperationException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword(resourceName, args)); - } + internal static void ThrowInvalidOperation(string format, object? arg0, object? arg1, object? arg2) + => throw new InvalidOperationException(string.Format(format, arg0, arg1, arg2)); /// /// Throws an ArgumentException that can include an inner exception. @@ -124,32 +137,27 @@ internal static void ThrowInvalidOperation(string resourceName, params object?[] /// This method is thread-safe. /// /// Can be null. - /// + /// /// [DoesNotReturn] - internal static void ThrowArgument(Exception? innerException, string resourceName, params object?[]? args) - { - throw new ArgumentException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword(resourceName, args), innerException); - } + private static void ThrowArgument(Exception? innerException, string format, object? arg0) + => throw new ArgumentException(string.Format(format, arg0), innerException); /// /// Overload for one string format argument. /// - internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, string resourceName, object arg0) - { - VerifyThrowArgument(condition, null, resourceName, arg0); - } + internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, string format, object? arg0) + => VerifyThrowArgument(condition, innerException: null, format, arg0); /// /// Overload for one string format argument. /// - internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, Exception? innerException, string resourceName, object arg0) + internal static void VerifyThrowArgument( + [DoesNotReturnIf(false)] bool condition, Exception? innerException, string format, object? arg0) { - ResourceUtilities.VerifyResourceStringExists(resourceName); - if (!condition) { - ThrowArgument(innerException, resourceName, arg0); + ThrowArgument(innerException, format, arg0); } } @@ -157,7 +165,9 @@ internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition /// Throws an ArgumentNullException if the given string parameter is null /// and ArgumentException if it has zero length. /// - internal static void VerifyThrowArgumentLength([NotNull] string? parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) + internal static void VerifyThrowArgumentLength( + [NotNull] string? parameter, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) { VerifyThrowArgumentNull(parameter, parameterName); @@ -169,35 +179,23 @@ internal static void VerifyThrowArgumentLength([NotNull] string? parameter, [Cal [DoesNotReturn] private static void ThrowArgumentLength(string? parameterName) - { - throw new ArgumentException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("Shared.ParameterCannotHaveZeroLength", parameterName)); - } + => throw new ArgumentException(string.Format(SR.Shared_ParameterCannotHaveZeroLength, parameterName), parameterName); /// /// Throws an ArgumentNullException if the given parameter is null. /// - internal static void VerifyThrowArgumentNull([NotNull] object? parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) + internal static void VerifyThrowArgumentNull( + [NotNull] object? parameter, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) { - VerifyThrowArgumentNull(parameter, parameterName, "Shared.ParameterCannotBeNull"); - } - - /// - /// Throws an ArgumentNullException if the given parameter is null. - /// - internal static void VerifyThrowArgumentNull([NotNull] object? parameter, string? parameterName, string resourceName) - { - ResourceUtilities.VerifyResourceStringExists(resourceName); if (parameter is null) { - ThrowArgumentNull(parameterName, resourceName); + ThrowArgumentNull(parameterName, SR.Shared_ParameterCannotBeNull); } } [DoesNotReturn] - internal static void ThrowArgumentNull(string? parameterName, string resourceName) - { - // Most ArgumentNullException overloads append its own rather clunky multi-line message. So use the one overload that doesn't. - throw new ArgumentNullException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword(resourceName, parameterName), (Exception?)null); - } + private static void ThrowArgumentNull(string? parameterName, string message) + => throw new ArgumentNullException(parameterName, string.Format(message, parameterName)); } } diff --git a/src/MSBuildTaskHost/Utilities/FileUtilities.cs b/src/MSBuildTaskHost/Utilities/FileUtilities.cs index 57de6288749..fb6ecf083dc 100644 --- a/src/MSBuildTaskHost/Utilities/FileUtilities.cs +++ b/src/MSBuildTaskHost/Utilities/FileUtilities.cs @@ -66,7 +66,7 @@ private static string GetFullPath(string path) if (IsPathTooLong(uncheckedFullPath)) { - string message = ResourceUtilities.FormatString(AssemblyResources.GetString("Shared.PathTooLong"), path, NativeMethods.MaxPath); + string message = string.Format(SR.Shared_PathTooLong, path, NativeMethods.MaxPath); throw new PathTooLongException(message); } diff --git a/src/MSBuildTaskHost/Utilities/Modifiers.cs b/src/MSBuildTaskHost/Utilities/Modifiers.cs index e8788106ff0..f0c1b090fa4 100644 --- a/src/MSBuildTaskHost/Utilities/Modifiers.cs +++ b/src/MSBuildTaskHost/Utilities/Modifiers.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using Microsoft.Build.TaskHost.Resources; #nullable disable @@ -354,7 +355,7 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS } else { - ErrorUtilities.ThrowInternalError("\"{0}\" is not a valid item-spec modifier.", modifier); + ErrorUtilities.ThrowInternalError($"\"{modifier}\" is not a valid item-spec modifier."); } modifiedItemSpec = GetItemSpecModifier(currentDirectory, definingProjectEscaped, null, additionalModifier); @@ -363,12 +364,12 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS } else { - ErrorUtilities.ThrowInternalError("\"{0}\" is not a valid item-spec modifier.", modifier); + ErrorUtilities.ThrowInternalError($"\"{modifier}\" is not a valid item-spec modifier."); } } catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) { - ErrorUtilities.ThrowInvalidOperation("Shared.InvalidFilespecForTransform", modifier, itemSpec, e.Message); + ErrorUtilities.ThrowInvalidOperation(SR.Shared_InvalidFilespecForTransform, modifier, itemSpec, e.Message); } return modifiedItemSpec; diff --git a/src/MSBuildTaskHost/Utilities/ResourceUtilities.cs b/src/MSBuildTaskHost/Utilities/ResourceUtilities.cs deleted file mode 100644 index d4ff30eb58e..00000000000 --- a/src/MSBuildTaskHost/Utilities/ResourceUtilities.cs +++ /dev/null @@ -1,314 +0,0 @@ -// 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.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Resources; -using Microsoft.Build.TaskHost.Resources; - -namespace Microsoft.Build.TaskHost.Utilities -{ - /// - /// This class contains utility methods for dealing with resources. - /// - internal static class ResourceUtilities - { - /// - /// Extracts the message code (if any) prefixed to the given string. - /// MSB\d\d\d\d):\s*(?.*)$" - /// Arbitrary codes match "^\s*(?[A-Za-z]+\d+):\s*(?.*)$" - /// ]]> - /// Thread safe. - /// - /// Whether to match only MSBuild error codes, or any error code. - /// The string to parse. - /// [out] The message code, or null if there was no code. - /// The string without its message code prefix, if any. - [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "Microsoft.Build.Shared.ResourceUtilities.#ExtractMessageCode(System.Boolean,System.String,System.String&)", Justification = "Unavoidable complexity")] - internal static string ExtractMessageCode(bool msbuildCodeOnly, string message, out string? code) - { - code = null; - int i = 0; - - while (i < message.Length && Char.IsWhiteSpace(message[i])) - { - i++; - } - - if (msbuildCodeOnly) - { - if ( - message.Length < i + 8 || - message[i] != 'M' || - message[i + 1] != 'S' || - message[i + 2] != 'B' || - message[i + 3] < '0' || message[i + 3] > '9' || - message[i + 4] < '0' || message[i + 4] > '9' || - message[i + 5] < '0' || message[i + 5] > '9' || - message[i + 6] < '0' || message[i + 6] > '9' || - message[i + 7] != ':') - { - return message; - } - - code = message.Substring(i, 7); - - i += 8; - } - else - { - int j = i; - for (; j < message.Length; j++) - { - char c = message[j]; - if (((c < 'a') || (c > 'z')) && ((c < 'A') || (c > 'Z'))) - { - break; - } - } - - if (j == i) - { - return message; // Should have been at least one letter - } - - int k = j; - - for (; k < message.Length; k++) - { - char c = message[k]; - if (c < '0' || c > '9') - { - break; - } - } - - if (k == j) - { - return message; // Should have been at least one digit - } - - if (k == message.Length || message[k] != ':') - { - return message; - } - - code = message.Substring(i, k - i); - - i = k + 1; - } - - while (i < message.Length && Char.IsWhiteSpace(message[i])) - { - i++; - } - - if (i < message.Length) - { - message = message.Substring(i); - } - - return message; - } - - /// - /// Retrieves the MSBuild F1-help keyword for the given resource string. Help keywords are used to index help topics in - /// host IDEs. - /// - /// Resource string to get the MSBuild F1-keyword for. - /// The MSBuild F1-help keyword string. - private static string GetHelpKeyword(string resourceName) - => "MSBuild." + resourceName; - - /// - /// Retrieves the contents of the named resource string. - /// - /// Resource string name. - /// Resource string contents. - internal static string GetResourceString(string resourceName) - => AssemblyResources.GetString(resourceName); - - /// - /// Loads the specified string resource and formats it with the arguments passed in. If the string resource has an MSBuild - /// message code and help keyword associated with it, they too are returned. - /// - /// PERF WARNING: calling a method that takes a variable number of arguments is expensive, because memory is allocated for - /// the array of arguments -- do not call this method repeatedly in performance-critical scenarios. - /// - /// This method is thread-safe. - /// [out] The MSBuild message code, or null. - /// [out] The MSBuild F1-help keyword for the host IDE, or null. - /// Resource string to load. - /// Optional arguments for formatting the resource string. - /// The formatted resource string. - private static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName, params object?[]? args) - { - helpKeyword = GetHelpKeyword(resourceName); - - // NOTE: the AssemblyResources.GetString() method is thread-safe - return ExtractMessageCode(true /* msbuildCodeOnly */, FormatString(GetResourceString(resourceName), args), out code); - } - - /// - /// Loads the specified string resource and formats it with the arguments passed in. If the string resource has an MSBuild - /// message code and help keyword associated with it, they too are returned. - /// - /// [out] The MSBuild message code, or null. - /// [out] The MSBuild F1-help keyword for the host IDE, or null. - /// Resource string to load. - /// Argument for formatting the resource string. - private static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName, object? arg1) - { - helpKeyword = GetHelpKeyword(resourceName); - return ExtractMessageCode(true, FormatString(GetResourceString(resourceName), arg1), out code); - } - - /// - /// Looks up a string in the resources, and formats it with the arguments passed in. If the string resource has an MSBuild - /// message code and help keyword associated with it, they are discarded. - /// - /// PERF WARNING: calling a method that takes a variable number of arguments is expensive, because memory is allocated for - /// the array of arguments -- do not call this method repeatedly in performance-critical scenarios. - /// - /// This method is thread-safe. - /// Resource string to load. - /// Optional arguments for formatting the resource string. - /// The formatted resource string. - internal static string FormatResourceStringStripCodeAndKeyword(string resourceName, params object?[]? args) - => FormatResourceStringStripCodeAndKeyword(out _, out _, resourceName, args); - - /// - /// Looks up a string in the resources, and formats it with the argument passed in. If the string resource has an MSBuild - /// message code and help keyword associated with it, they are discarded. - /// - /// This method is thread-safe. - /// Resource string to load. - /// Argument for formatting the resource string. - /// The formatted resource string. - internal static string FormatResourceStringStripCodeAndKeyword(string resourceName, object? arg1) - => FormatResourceStringStripCodeAndKeyword(out _, out _, resourceName, arg1); - - /// - /// Formats the given string using the variable arguments passed in. - /// - /// PERF WARNING: calling a method that takes a variable number of arguments is expensive, because memory is allocated for - /// the array of arguments -- do not call this method repeatedly in performance-critical scenarios - /// - /// Thread safe. - /// - /// The string to format. - /// Optional arguments for formatting the given string. - /// The formatted string. - internal static string FormatString(string unformatted, params object?[]? args) - { - string formatted = unformatted; - - // NOTE: String.Format() does not allow a null arguments array - if (args?.Length > 0) - { - ValidateArgsIfDebug(args); - - // Format the string, using the variable arguments passed in. - // NOTE: all String methods are thread-safe - formatted = string.Format(CultureInfo.CurrentCulture, unformatted, args); - } - - return formatted; - } - - // Overloads with 1-3 arguments to avoid array allocations. - - /// - /// Formats the given string using the variable arguments passed in. - /// - /// The string to format. - /// Argument for formatting the given string. - /// The formatted string. - internal static string FormatString(string unformatted, object? arg1) - { - ValidateArgsIfDebug([arg1]); - return string.Format(CultureInfo.CurrentCulture, unformatted, arg1); - } - - /// - /// Formats the given string using the variable arguments passed in. - /// - /// The string to format. - /// First argument for formatting the given string. - /// Second argument for formatting the given string. - /// The formatted string. - internal static string FormatString(string unformatted, object? arg1, object? arg2) - { - ValidateArgsIfDebug([arg1, arg2]); - return string.Format(CultureInfo.CurrentCulture, unformatted, arg1, arg2); - } - - [Conditional("DEBUG")] - private static void ValidateArgsIfDebug(object?[] args) - { - // If you accidentally pass some random type in that can't be converted to a string, - // FormatResourceString calls ToString() which returns the full name of the type! - foreach (object? param in args) - { - // Check it has a real implementation of ToString() and the type is not actually System.String - if (param != null) - { - if (string.Equals(param.GetType().ToString(), param.ToString(), StringComparison.Ordinal) && - param.GetType() != typeof(string)) - { - ErrorUtilities.ThrowInternalError( - "Invalid resource parameter type, was {0}", - param.GetType().FullName); - } - } - } - } - - /// - /// Verifies that a particular resource string actually exists in the string table. This will only be called in debug - /// builds. It helps catch situations where a dev calls VerifyThrowXXX with a new resource string, but forgets to add the - /// resource string to the string table, or misspells it! - /// - /// This method is thread-safe. - /// Resource string to check. - [Conditional("DEBUG")] - internal static void VerifyResourceStringExists(string resourceName) - { - try - { - // Look up the resource string in the engine's string table. - // NOTE: the AssemblyResources.GetString() method is thread-safe - string unformattedMessage = AssemblyResources.GetString(resourceName); - - if (unformattedMessage == null) - { - ErrorUtilities.ThrowInternalError("The resource string \"" + resourceName + "\" was not found."); - } - } - catch (ArgumentException e) - { -#if FEATURE_DEBUG_LAUNCH - Debug.Fail("The resource string \"" + resourceName + "\" was not found."); -#endif - ErrorUtilities.ThrowInternalError(e.Message); - } - catch (InvalidOperationException e) - { -#if FEATURE_DEBUG_LAUNCH - Debug.Fail("The resource string \"" + resourceName + "\" was not found."); -#endif - ErrorUtilities.ThrowInternalError(e.Message); - } - catch (MissingManifestResourceException e) - { -#if FEATURE_DEBUG_LAUNCH - Debug.Fail("The resource string \"" + resourceName + "\" was not found."); -#endif - ErrorUtilities.ThrowInternalError(e.Message); - } - } - } -} From c4aa56d3848faa547e0eb49f9731cd245da829ab Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 5 Feb 2026 09:47:26 -0800 Subject: [PATCH 095/136] MSBuildTaskHost: Clean up BinaryReader/WriterExtensions - File-scoped namespaces - Expression-bodied members - Remove uncalled members --- .../BackEnd/BinaryReaderExtensions.cs | 73 ++--------------- .../BackEnd/BinaryWriterExtensions.cs | 80 ++++--------------- 2 files changed, 24 insertions(+), 129 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/BinaryReaderExtensions.cs b/src/MSBuildTaskHost/BackEnd/BinaryReaderExtensions.cs index 9156f3cca31..a89ba126eca 100644 --- a/src/MSBuildTaskHost/BackEnd/BinaryReaderExtensions.cs +++ b/src/MSBuildTaskHost/BackEnd/BinaryReaderExtensions.cs @@ -1,74 +1,15 @@ // 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; using System.IO; -namespace Microsoft.Build.TaskHost.BackEnd -{ - internal static class BinaryReaderExtensions - { - public static string? ReadOptionalString(this BinaryReader reader) - { - return reader.ReadByte() == 0 ? null : reader.ReadString(); - } - - public static int? ReadOptionalInt32(this BinaryReader reader) - { - return reader.ReadByte() == 0 ? null : reader.ReadInt32(); - } - - public static int Read7BitEncodedInt(this BinaryReader reader) - { - // Read out an Int32 7 bits at a time. The high bit - // of the byte when on means to continue reading more bytes. - int count = 0; - int shift = 0; - byte b; - do - { - // Check for a corrupted stream. Read a max of 5 bytes. - // In a future version, add a DataFormatException. - if (shift == 5 * 7) // 5 bytes max per Int32, shift += 7 - { - throw new FormatException(); - } - - // ReadByte handles end of stream cases for us. - b = reader.ReadByte(); - count |= (b & 0x7F) << shift; - shift += 7; - } while ((b & 0x80) != 0); - return count; - } +namespace Microsoft.Build.TaskHost.BackEnd; - public static DateTime ReadTimestamp(this BinaryReader reader) - { - long timestampTicks = reader.ReadInt64(); - DateTimeKind kind = (DateTimeKind)reader.ReadInt32(); - var timestamp = new DateTime(timestampTicks, kind); - return timestamp; - } - - public static unsafe Guid ReadGuid(this BinaryReader reader) - { - return new Guid(reader.ReadBytes(sizeof(Guid))); - } - - public static Dictionary ReadDurationDictionary(this BinaryReader reader) - { - int count = reader.Read7BitEncodedInt(); - var durations = new Dictionary(count); - for (int i = 0; i < count; i++) - { - string key = reader.ReadString(); - TimeSpan value = TimeSpan.FromTicks(reader.ReadInt64()); - - durations.Add(key, value); - } +internal static class BinaryReaderExtensions +{ + public static string? ReadOptionalString(this BinaryReader reader) + => reader.ReadByte() == 0 ? null : reader.ReadString(); - return durations; - } - } + public static int? ReadOptionalInt32(this BinaryReader reader) + => reader.ReadByte() == 0 ? null : reader.ReadInt32(); } diff --git a/src/MSBuildTaskHost/BackEnd/BinaryWriterExtensions.cs b/src/MSBuildTaskHost/BackEnd/BinaryWriterExtensions.cs index bd416d42dd9..0b1619bc1d2 100644 --- a/src/MSBuildTaskHost/BackEnd/BinaryWriterExtensions.cs +++ b/src/MSBuildTaskHost/BackEnd/BinaryWriterExtensions.cs @@ -1,81 +1,35 @@ // 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; using System.IO; -namespace Microsoft.Build.TaskHost.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd; + +internal static class BinaryWriterExtensions { - internal static class BinaryWriterExtensions + public static void WriteOptionalString(this BinaryWriter writer, string? value) { - public static void WriteOptionalString(this BinaryWriter writer, string? value) - { - if (value == null) - { - writer.Write((byte)0); - } - else - { - writer.Write((byte)1); - writer.Write(value); - } - } - - public static void WriteOptionalInt32(this BinaryWriter writer, int? value) - { - if (value == null) - { - writer.Write((byte)0); - } - else - { - writer.Write((byte)1); - writer.Write(value.Value); - } - } - - public static void WriteTimestamp(this BinaryWriter writer, DateTime timestamp) + if (value == null) { - writer.Write(timestamp.Ticks); - writer.Write((Int32)timestamp.Kind); + writer.Write((byte)0); } - - public static void Write7BitEncodedInt(this BinaryWriter writer, int value) + else { - // Write out an int 7 bits at a time. The high bit of the byte, - // when on, tells reader to continue reading more bytes. - uint v = (uint)value; // support negative numbers - while (v >= 0x80) - { - writer.Write((byte)(v | 0x80)); - v >>= 7; - } - - writer.Write((byte)v); + writer.Write((byte)1); + writer.Write(value); } + } - public static void WriteGuid(this BinaryWriter writer, Guid value) + public static void WriteOptionalInt32(this BinaryWriter writer, int? value) + { + if (value == null) { - Guid val = value; - unsafe - { - byte* ptr = (byte*)&val; - for (int i = 0; i < sizeof(Guid); i++, ptr++) - { - writer.Write(*ptr); - } - } + writer.Write((byte)0); } - - public static void WriteDurationsDictionary(this BinaryWriter writer, Dictionary durations) + else { - writer.Write7BitEncodedInt(durations.Count); - foreach (KeyValuePair kvp in durations) - { - writer.Write(kvp.Key); - writer.Write(kvp.Value.Ticks); - } + writer.Write((byte)1); + writer.Write(value.Value); } } } From 2a97a76ceb9f898fdff69de79cbffc30bc1c5ff0 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 5 Feb 2026 10:17:19 -0800 Subject: [PATCH 096/136] MSBuildTaskHost: Clean up BinaryTranslater, ITranslater, and *Helpers - File-scoped namespaces - Expression-bodied members - Enable nullable and add annotations - Primary constructors --- .../BackEnd/BinaryTranslator.cs | 1147 ++++++++--------- src/MSBuildTaskHost/BackEnd/ITranslator.cs | 453 ++++--- .../BackEnd/TranslatorHelpers.cs | 79 +- 3 files changed, 790 insertions(+), 889 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/BinaryTranslator.cs b/src/MSBuildTaskHost/BackEnd/BinaryTranslator.cs index c2c404a7d43..7e561ebf069 100644 --- a/src/MSBuildTaskHost/BackEnd/BinaryTranslator.cs +++ b/src/MSBuildTaskHost/BackEnd/BinaryTranslator.cs @@ -3,772 +3,683 @@ using System; using System.Collections.Generic; -using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using Microsoft.Build.TaskHost.Exceptions; using Microsoft.Build.TaskHost.Utilities; -#nullable disable +namespace Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.TaskHost.BackEnd +/// +/// This class is responsible for serializing and deserializing simple types to and +/// from the byte streams used to communicate INodePacket-implementing classes. +/// Each class implements a Translate method on INodePacket which takes this class +/// as a parameter, and uses it to store and retrieve fields to the stream. +/// +internal static class BinaryTranslator { + private static byte[] EmptyByteArray => field ??= []; + + /// + /// Returns a read-only serializer. + /// + /// The serializer. + internal static ITranslator GetReadTranslator(Stream stream, BinaryReaderFactory buffer) + => new BinaryReadTranslator(stream, buffer); + + /// + /// Returns a write-only serializer. + /// + /// The stream containing data to serialize. + /// The serializer. + internal static ITranslator GetWriteTranslator(Stream stream) + => new BinaryWriteTranslator(stream); + /// - /// This class is responsible for serializing and deserializing simple types to and - /// from the byte streams used to communicate INodePacket-implementing classes. - /// Each class implements a Translate method on INodePacket which takes this class - /// as a parameter, and uses it to store and retrieve fields to the stream. + /// Implementation of ITranslator for reading from a stream. /// - internal static class BinaryTranslator + private class BinaryReadTranslator(Stream packetStream, BinaryReaderFactory buffer) : ITranslator { -#nullable enable /// - /// Returns a read-only serializer. + /// Gets the reader, if any. /// - /// The serializer. - internal static ITranslator GetReadTranslator(Stream stream, BinaryReaderFactory buffer) - { - return new BinaryReadTranslator(stream, buffer); - } -#nullable disable + public BinaryReader Reader { get; } = buffer.Create(packetStream); /// - /// Returns a write-only serializer. + /// Gets the writer, if any. /// - /// The stream containing data to serialize. - /// The serializer. - internal static ITranslator GetWriteTranslator(Stream stream) + public BinaryWriter Writer { - return new BinaryWriteTranslator(stream); + get + { + ErrorUtilities.ThrowInternalError("Cannot get writer from reader."); + return null; + } } /// - /// Implementation of ITranslator for reading from a stream. + /// Gets the current serialization mode. /// - private class BinaryReadTranslator : ITranslator - { - /// - /// The binary reader used in read mode. - /// - private BinaryReader _reader; + public TranslationDirection Mode => TranslationDirection.ReadFromStream; -#nullable enable - /// - /// Constructs a serializer from the specified stream, operating in the designated mode. - /// - public BinaryReadTranslator(Stream packetStream, BinaryReaderFactory buffer) - { - _reader = buffer.Create(packetStream); - } -#nullable disable + /// + public byte NegotiatedPacketVersion { get; set; } - /// - /// Delegates the Dispose call the to the underlying BinaryReader. - /// - public void Dispose() - { - _reader.Close(); - } - - /// - /// Gets the reader, if any. - /// - public BinaryReader Reader - { - get { return _reader; } - } + /// + /// Delegates the Dispose call the to the underlying BinaryReader. + /// + public void Dispose() + => Reader.Close(); - /// - /// Gets the writer, if any. - /// - public BinaryWriter Writer - { - get - { - ErrorUtilities.ThrowInternalError("Cannot get writer from reader."); - return null; - } - } + /// + /// Translates a boolean. + /// + /// The value to be translated. + public void Translate(ref bool value) + => value = Reader.ReadBoolean(); - /// - /// Returns the current serialization mode. - /// - public TranslationDirection Mode + /// + /// Translates an array. + /// + /// The array to be translated. + public void Translate(ref bool[]? array) + { + if (!TranslateNullable(array)) { - [DebuggerStepThrough] - get - { return TranslationDirection.ReadFromStream; } + return; } - /// - public byte NegotiatedPacketVersion { get; set; } + int count = Reader.ReadInt32(); + array = new bool[count]; - /// - /// Translates a boolean. - /// - /// The value to be translated. - public void Translate(ref bool value) + for (int i = 0; i < count; i++) { - value = _reader.ReadBoolean(); + array[i] = Reader.ReadBoolean(); } + } - /// - /// Translates an array. - /// - /// The array to be translated. - public void Translate(ref bool[] array) - { - if (!TranslateNullable(array)) - { - return; - } + /// + /// Translates a byte. + /// + /// The value to be translated. + public void Translate(ref byte value) + => value = Reader.ReadByte(); + + /// + /// Translates a short. + /// + /// The value to be translated. + public void Translate(ref short value) + => value = Reader.ReadInt16(); - int count = _reader.ReadInt32(); - array = new bool[count]; + /// + /// Translates an unsigned short. + /// + /// The value to be translated. + public void Translate(ref ushort value) + => value = Reader.ReadUInt16(); - for (int i = 0; i < count; i++) - { - array[i] = _reader.ReadBoolean(); - } - } + /// + /// Translates an integer. + /// + /// The value to be translated. + public void Translate(ref int value) + => value = Reader.ReadInt32(); - /// - /// Translates a byte. - /// - /// The value to be translated. - public void Translate(ref byte value) + /// + /// Translates an array. + /// + /// The array to be translated. + public void Translate(ref int[]? array) + { + if (!TranslateNullable(array)) { - value = _reader.ReadByte(); + return; } - /// - /// Translates a short. - /// - /// The value to be translated. - public void Translate(ref short value) + int count = Reader.ReadInt32(); + array = new int[count]; + + for (int i = 0; i < count; i++) { - value = _reader.ReadInt16(); + array[i] = Reader.ReadInt32(); } + } + + /// + /// Translates a long. + /// + /// The value to be translated. + public void Translate(ref long value) + => value = Reader.ReadInt64(); - /// - /// Translates an unsigned short. - /// - /// The value to be translated. - public void Translate(ref ushort value) + /// + /// Translates a double. + /// + /// The value to be translated. + public void Translate(ref double value) + => value = Reader.ReadDouble(); + + /// + /// Translates a string. + /// + /// The value to be translated. + public void Translate(ref string? value) + { + if (!TranslateNullable(value)) { - value = _reader.ReadUInt16(); + return; } - /// - /// Translates an integer. - /// - /// The value to be translated. - public void Translate(ref int value) + value = Reader.ReadString(); + } + + /// + /// Translates a byte array. + /// + /// The array to be translated. + public void Translate(ref byte[]? byteArray) + { + if (!TranslateNullable(byteArray)) { - value = _reader.ReadInt32(); + return; } - /// - /// Translates an array. - /// - /// The array to be translated. - public void Translate(ref int[] array) + int count = Reader.ReadInt32(); + byteArray = count > 0 + ? Reader.ReadBytes(count) + : EmptyByteArray; + } + + /// + /// Translates a string array. + /// + /// The array to be translated. + public void Translate(ref string[]? array) + { + if (!TranslateNullable(array)) { - if (!TranslateNullable(array)) - { - return; - } + return; + } - int count = _reader.ReadInt32(); - array = new int[count]; + int count = Reader.ReadInt32(); + array = new string[count]; - for (int i = 0; i < count; i++) - { - array[i] = _reader.ReadInt32(); - } + for (int i = 0; i < count; i++) + { + array[i] = Reader.ReadString(); } + } - /// - /// Translates a long. - /// - /// The value to be translated. - public void Translate(ref long value) + /// + /// Translates a collection of T into the specified type using an and . + /// + /// The collection to be translated. + /// The translator to use for the values in the collection. + /// The factory to create the ICollection. + /// The type contained in the collection. + /// The type of collection to be created. + public void Translate(ref ICollection? collection, ObjectTranslator objectTranslator, NodePacketCollectionCreator collectionFactory) + where L : ICollection + { + if (!TranslateNullable(collection)) { - value = _reader.ReadInt64(); + return; } - /// - /// Translates a double. - /// - /// The value to be translated. - public void Translate(ref double value) + int count = Reader.ReadInt32(); + collection = collectionFactory(count); + + for (int i = 0; i < count; i++) { - value = _reader.ReadDouble(); + T value = default!; + objectTranslator(this, ref value); + collection.Add(value); } + } - /// - /// Translates a string. - /// - /// The value to be translated. - public void Translate(ref string value) - { - if (!TranslateNullable(value)) - { - return; - } + /// + /// Translates a DateTime. + /// + /// The value to be translated. + public void Translate(ref DateTime value) + { + DateTimeKind kind = DateTimeKind.Unspecified; + TranslateEnum(ref kind, 0); + value = new DateTime(Reader.ReadInt64(), kind); + } - value = _reader.ReadString(); - } + /// + /// Translates a CultureInfo. + /// + /// The CultureInfo to translate. + public void TranslateCulture(ref CultureInfo? value) + { + string cultureName = Reader.ReadString(); - /// - /// Translates a byte array - /// - /// The array to be translated - public void Translate(ref byte[] byteArray) + // It may be that some culture codes are accepted on later .net framework versions + // but not on the older 3.5 or 2.0. Fallbacks are required in this case to prevent + // exceptions + try { - if (!TranslateNullable(byteArray)) - { - return; - } - - int count = _reader.ReadInt32(); - if (count > 0) - { - byteArray = _reader.ReadBytes(count); - } - else - { -#pragma warning disable CA1825 // Avoid zero-length array allocations - byteArray = new byte[0]; -#pragma warning restore CA1825 // Avoid zero-length array allocations - } + value = new CultureInfo(cultureName); } - - /// - /// Translates a string array. - /// - /// The array to be translated. - public void Translate(ref string[] array) + catch { - if (!TranslateNullable(array)) - { - return; - } - - int count = _reader.ReadInt32(); - array = new string[count]; - - for (int i = 0; i < count; i++) - { - array[i] = _reader.ReadString(); - } + value = CultureInfo.CurrentCulture; } + } - /// - /// Translates a collection of T into the specified type using an and - /// - /// The collection to be translated. - /// The translator to use for the values in the collection. - /// The factory to create the ICollection. - /// The type contained in the collection. - /// The type of collection to be created. - public void Translate(ref ICollection collection, ObjectTranslator objectTranslator, NodePacketCollectionCreator collectionFactory) where L : ICollection - { - if (!TranslateNullable(collection)) - { - return; - } + /// + /// Translates an enumeration. + /// + /// The enumeration type. + /// The enumeration instance to be translated. + /// The enumeration value as an integer. + /// This is a bit ugly, but it doesn't seem like a nice method signature is possible because + /// you can't pass the enum type as a reference and constrain the generic parameter to Enum. Nor + /// can you simply pass as ref Enum, because an enum instance doesn't match that function signature. + /// Finally, converting the enum to an int assumes that we always want to transport enums as ints. This + /// works in all of our current cases, but certainly isn't perfectly generic. + public void TranslateEnum(ref T value, int numericValue) + where T : struct, Enum + { + numericValue = Reader.ReadInt32(); + Type enumType = value.GetType(); + value = (T)Enum.ToObject(enumType, numericValue); + } - int count = _reader.ReadInt32(); - collection = collectionFactory(count); + public void TranslateException(ref Exception? value) + { + if (!TranslateNullable(value)) + { + return; + } - for (int i = 0; i < count; i++) - { - T value = default(T); - objectTranslator(this, ref value); - collection.Add(value); - } - } + value = BuildExceptionBase.ReadExceptionFromTranslator(this); + } - /// - /// Translates a DateTime. - /// - /// The value to be translated. - public void Translate(ref DateTime value) - { - DateTimeKind kind = DateTimeKind.Unspecified; - TranslateEnum(ref kind, 0); - value = new DateTime(_reader.ReadInt64(), kind); - } - - /// - /// Translates a CultureInfo - /// - /// The CultureInfo to translate - public void TranslateCulture(ref CultureInfo value) - { - string cultureName = _reader.ReadString(); - - // It may be that some culture codes are accepted on later .net framework versions - // but not on the older 3.5 or 2.0. Fallbacks are required in this case to prevent - // exceptions - value = LoadCultureWithFallback(cultureName); - } - - private static CultureInfo LoadCultureWithFallback(string cultureName) - { - CultureInfo cultureInfo; - - return TryLoadCulture(cultureName, out cultureInfo) ? cultureInfo : CultureInfo.CurrentCulture; - } - - private static bool TryLoadCulture(string cultureName, out CultureInfo cultureInfo) - { - try - { - cultureInfo = new CultureInfo(cultureName); - return true; - } - catch - { - cultureInfo = null; - return false; - } - } - - /// - /// Translates an enumeration. - /// - /// The enumeration type. - /// The enumeration instance to be translated. - /// The enumeration value as an integer. - /// This is a bit ugly, but it doesn't seem like a nice method signature is possible because - /// you can't pass the enum type as a reference and constrain the generic parameter to Enum. Nor - /// can you simply pass as ref Enum, because an enum instance doesn't match that function signature. - /// Finally, converting the enum to an int assumes that we always want to transport enums as ints. This - /// works in all of our current cases, but certainly isn't perfectly generic. - public void TranslateEnum(ref T value, int numericValue) - where T : struct, Enum + /// + /// Translates a dictionary of { string, string }. + /// + /// The dictionary to be translated. + /// The comparer used to instantiate the dictionary. + public void TranslateDictionary(ref Dictionary? dictionary, IEqualityComparer comparer) + { + if (!TranslateNullable(dictionary)) { - numericValue = _reader.ReadInt32(); - Type enumType = value.GetType(); - value = (T)Enum.ToObject(enumType, numericValue); + return; } - public void TranslateException(ref Exception value) - { - if (!TranslateNullable(value)) - { - return; - } + int count = Reader.ReadInt32(); + dictionary = new Dictionary(count, comparer); - value = BuildExceptionBase.ReadExceptionFromTranslator(this); - } - - /// - /// Translates a dictionary of { string, string }. - /// - /// The dictionary to be translated. - /// The comparer used to instantiate the dictionary. - public void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer) + for (int i = 0; i < count; i++) { - if (!TranslateNullable(dictionary)) - { - return; - } + string? key = null; + Translate(ref key); + string? value = null; + Translate(ref value); - int count = _reader.ReadInt32(); - dictionary = new Dictionary(count, comparer); - - for (int i = 0; i < count; i++) - { - string key = null; - Translate(ref key); - string value = null; - Translate(ref value); - dictionary[key] = value; - } + // NOTE: This can throw if key is null. + dictionary[key!] = value; } + } - /// - public void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) - where T : class + /// + public void TranslateDictionary( + ref Dictionary? dictionary, + IEqualityComparer comparer, + ObjectTranslatorWithValueFactory objectTranslator, + NodePacketValueFactory valueFactory) + where T : class + { + if (!TranslateNullable(dictionary)) { - if (!TranslateNullable(dictionary)) - { - return; - } + return; + } - int count = _reader.ReadInt32(); - dictionary = new Dictionary(count, comparer); + int count = Reader.ReadInt32(); + dictionary = new Dictionary(count, comparer); - for (int i = 0; i < count; i++) - { - string key = null; - Translate(ref key); - T value = null; - objectTranslator(this, valueFactory, ref value); - dictionary[key] = value; - } - } + for (int i = 0; i < count; i++) + { + string? key = null; + Translate(ref key); + T value = default!; + objectTranslator(this, valueFactory, ref value); - /// - /// Reads in the boolean which says if this object is null or not. - /// - /// The type of object to test. - /// True if the object should be read, false otherwise. - public bool TranslateNullable(T value) - { - bool haveRef = _reader.ReadBoolean(); - return haveRef; + // NOTE: This can throw if key is null. + dictionary[key!] = value; } } /// - /// Implementation of ITranslator for writing to a stream. + /// Reads in the boolean which says if this object is null or not. /// - private class BinaryWriteTranslator : ITranslator + /// The type of object to test. + /// True if the object should be read, false otherwise. + public bool TranslateNullable(T? value) + where T : class { - /// - /// The binary writer used in write mode. - /// - private BinaryWriter _writer; + bool haveRef = Reader.ReadBoolean(); + return haveRef; + } + } - /// - /// Constructs a serializer from the specified stream, operating in the designated mode. - /// - /// The stream serving as the source or destination of data. - public BinaryWriteTranslator(Stream packetStream) + /// + /// Implementation of ITranslator for writing to a stream. + /// + /// The stream serving as the source or destination of data. + private class BinaryWriteTranslator(Stream packetStream) : ITranslator + { + /// + /// Gets the reader, if any. + /// + public BinaryReader Reader + { + get { - _writer = new BinaryWriter(packetStream); + ErrorUtilities.ThrowInternalError("Cannot get reader from writer."); + return null; } + } - /// - /// Delegates the Dispose call the to the underlying BinaryWriter. - /// - public void Dispose() - { - _writer.Close(); - } + /// + /// Gets the writer, if any. + /// + public BinaryWriter Writer { get; } = new BinaryWriter(packetStream); - /// - /// Gets the reader, if any. - /// - public BinaryReader Reader - { - get - { - ErrorUtilities.ThrowInternalError("Cannot get reader from writer."); - return null; - } - } + /// + /// Gets the current serialization mode. + /// + public TranslationDirection Mode => TranslationDirection.WriteToStream; - /// - /// Gets the writer, if any. - /// - public BinaryWriter Writer - { - get { return _writer; } - } + /// + public byte NegotiatedPacketVersion { get; set; } - /// - /// Returns the current serialization mode. - /// - public TranslationDirection Mode + /// + /// Delegates the Dispose call the to the underlying BinaryWriter. + /// + public void Dispose() + => Writer.Close(); + + /// + /// Translates a boolean. + /// + /// The value to be translated. + public void Translate(ref bool value) + => Writer.Write(value); + + /// + /// Translates an array. + /// + /// The array to be translated. + public void Translate(ref bool[]? array) + { + if (!TranslateNullable(array)) { - [DebuggerStepThrough] - get - { return TranslationDirection.WriteToStream; } + return; } - /// - public byte NegotiatedPacketVersion { get; set; } + int count = array.Length; + Writer.Write(count); - /// - /// Translates a boolean. - /// - /// The value to be translated. - public void Translate(ref bool value) + for (int i = 0; i < count; i++) { - _writer.Write(value); + Writer.Write(array[i]); } + } - /// - /// Translates an array. - /// - /// The array to be translated. - public void Translate(ref bool[] array) - { - if (!TranslateNullable(array)) - { - return; - } + /// + /// Translates a byte. + /// + /// The value to be translated. + public void Translate(ref byte value) + => Writer.Write(value); - int count = array.Length; - _writer.Write(count); + /// + /// Translates a short. + /// + /// The value to be translated. + public void Translate(ref short value) + => Writer.Write(value); - for (int i = 0; i < count; i++) - { - _writer.Write(array[i]); - } - } + /// + /// Translates an unsigned short. + /// + /// The value to be translated. + public void Translate(ref ushort value) + => Writer.Write(value); - /// - /// Translates a byte. - /// - /// The value to be translated. - public void Translate(ref byte value) - { - _writer.Write(value); - } + /// + /// Translates an integer. + /// + /// The value to be translated. + public void Translate(ref int value) + => Writer.Write(value); - /// - /// Translates a short. - /// - /// The value to be translated. - public void Translate(ref short value) + /// + /// Translates an array. + /// + /// The array to be translated. + public void Translate(ref int[]? array) + { + if (!TranslateNullable(array)) { - _writer.Write(value); + return; } - /// - /// Translates an unsigned short. - /// - /// The value to be translated. - public void Translate(ref ushort value) - { - _writer.Write(value); - } + int count = array.Length; + Writer.Write(count); - /// - /// Translates an integer. - /// - /// The value to be translated. - public void Translate(ref int value) + for (int i = 0; i < count; i++) { - _writer.Write(value); + Writer.Write(array[i]); } + } - /// - /// Translates an array. - /// - /// The array to be translated. - public void Translate(ref int[] array) - { - if (!TranslateNullable(array)) - { - return; - } - - int count = array.Length; - _writer.Write(count); + /// + /// Translates a long. + /// + /// The value to be translated. + public void Translate(ref long value) + => Writer.Write(value); - for (int i = 0; i < count; i++) - { - _writer.Write(array[i]); - } - } + /// + /// Translates a double. + /// + /// The value to be translated. + public void Translate(ref double value) + => Writer.Write(value); - /// - /// Translates a long. - /// - /// The value to be translated. - public void Translate(ref long value) + /// + /// Translates a string. + /// + /// The value to be translated. + public void Translate(ref string? value) + { + if (!TranslateNullable(value)) { - _writer.Write(value); + return; } - /// - /// Translates a double. - /// - /// The value to be translated. - public void Translate(ref double value) + Writer.Write(value); + } + + /// + /// Translates a string array. + /// + /// The array to be translated. + public void Translate(ref string[]? array) + { + if (!TranslateNullable(array)) { - _writer.Write(value); + return; } - /// - /// Translates a string. - /// - /// The value to be translated. - public void Translate(ref string value) - { - if (!TranslateNullable(value)) - { - return; - } + int count = array.Length; + Writer.Write(count); - _writer.Write(value); + for (int i = 0; i < count; i++) + { + Writer.Write(array[i]); } + } - /// - /// Translates a string array. - /// - /// The array to be translated. - public void Translate(ref string[] array) + /// + /// Translates a collection of T into the specified type using an and . + /// + /// The collection to be translated. + /// The translator to use for the values in the collection. + /// The factory to create the ICollection. + /// The type contained in the collection. + /// The type of collection to be created. + public void Translate( + ref ICollection? collection, + ObjectTranslator objectTranslator, + NodePacketCollectionCreator collectionFactory) + where L : ICollection + { + if (!TranslateNullable(collection)) { - if (!TranslateNullable(array)) - { - return; - } - - int count = array.Length; - _writer.Write(count); - - for (int i = 0; i < count; i++) - { - _writer.Write(array[i]); - } + return; } - /// - /// Translates a collection of T into the specified type using an and - /// - /// The collection to be translated. - /// The translator to use for the values in the collection. - /// The factory to create the ICollection. - /// The type contained in the collection. - /// The type of collection to be created. - public void Translate(ref ICollection collection, ObjectTranslator objectTranslator, NodePacketCollectionCreator collectionFactory) where L : ICollection + Writer.Write(collection.Count); + + foreach (T item in collection) { - if (!TranslateNullable(collection)) - { - return; - } + T value = item; + objectTranslator(this, ref value); + } + } - _writer.Write(collection.Count); + /// + /// Translates a DateTime. + /// + /// The value to be translated. + public void Translate(ref DateTime value) + { + DateTimeKind kind = value.Kind; + TranslateEnum(ref kind, (int)kind); + Writer.Write(value.Ticks); + } - foreach (T item in collection) - { - T value = item; - objectTranslator(this, ref value); - } - } + /// + /// Translates a CultureInfo. + /// + /// The CultureInfo. + public void TranslateCulture(ref CultureInfo? value) + => Writer.Write(value!.Name); - /// - /// Translates a DateTime. - /// - /// The value to be translated. - public void Translate(ref DateTime value) + /// + /// Translates an enumeration. + /// + /// The enumeration type. + /// The enumeration instance to be translated. + /// The enumeration value as an integer. + /// This is a bit ugly, but it doesn't seem like a nice method signature is possible because + /// you can't pass the enum type as a reference and constrain the generic parameter to Enum. Nor + /// can you simply pass as ref Enum, because an enum instance doesn't match that function signature. + /// Finally, converting the enum to an int assumes that we always want to transport enums as ints. This + /// works in all of our current cases, but certainly isn't perfectly generic. + public void TranslateEnum(ref T value, int numericValue) + where T : struct, Enum + => Writer.Write(numericValue); + + public void TranslateException(ref Exception? value) + { + if (!TranslateNullable(value)) { - DateTimeKind kind = value.Kind; - TranslateEnum(ref kind, (int)kind); - _writer.Write(value.Ticks); + return; } - /// - /// Translates a CultureInfo - /// - /// The CultureInfo - public void TranslateCulture(ref CultureInfo value) - { - _writer.Write(value.Name); - } + BuildExceptionBase.WriteExceptionToTranslator(this, value); + } - /// - /// Translates an enumeration. - /// - /// The enumeration type. - /// The enumeration instance to be translated. - /// The enumeration value as an integer. - /// This is a bit ugly, but it doesn't seem like a nice method signature is possible because - /// you can't pass the enum type as a reference and constrain the generic parameter to Enum. Nor - /// can you simply pass as ref Enum, because an enum instance doesn't match that function signature. - /// Finally, converting the enum to an int assumes that we always want to transport enums as ints. This - /// works in all of our current cases, but certainly isn't perfectly generic. - public void TranslateEnum(ref T value, int numericValue) - where T : struct, Enum + /// + /// Translates a byte array. + /// + /// The byte array to be translated. + public void Translate(ref byte[]? byteArray) + { + if (!TranslateNullable(byteArray)) { - _writer.Write(numericValue); + return; } - public void TranslateException(ref Exception value) + int length = byteArray.Length; + + Writer.Write(length); + if (length > 0) { - if (!TranslateNullable(value)) - { - return; - } - - BuildExceptionBase.WriteExceptionToTranslator(this, value); + Writer.Write(byteArray, 0, length); } + } - /// - /// Translates a byte array - /// - /// The byte array to be translated - public void Translate(ref byte[] byteArray) + /// + /// Translates a dictionary of { string, string }. + /// + /// The dictionary to be translated. + /// The comparer used to instantiate the dictionary. + public void TranslateDictionary(ref Dictionary? dictionary, IEqualityComparer comparer) + { + if (!TranslateNullable(dictionary)) { - if (!TranslateNullable(byteArray)) - { - return; - } - - var length = byteArray?.Length ?? 0; - - _writer.Write(length); - if (length > 0) - { - _writer.Write(byteArray, 0, length); - } + return; } - /// - /// Translates a dictionary of { string, string }. - /// - /// The dictionary to be translated. - /// The comparer used to instantiate the dictionary. - public void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer) - { - if (!TranslateNullable(dictionary)) - { - return; - } - - int count = dictionary.Count; - _writer.Write(count); + int count = dictionary.Count; + Writer.Write(count); - foreach (KeyValuePair pair in dictionary) - { - string key = pair.Key; - Translate(ref key); - string value = pair.Value; - Translate(ref value); - } + foreach (KeyValuePair pair in dictionary) + { + string? key = pair.Key; + Translate(ref key); + string? value = pair.Value; + Translate(ref value); } + } - /// - public void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) - where T : class + /// + public void TranslateDictionary( + ref Dictionary? dictionary, + IEqualityComparer comparer, + ObjectTranslatorWithValueFactory objectTranslator, + NodePacketValueFactory valueFactory) + where T : class + { + if (!TranslateNullable(dictionary)) { - if (!TranslateNullable(dictionary)) - { - return; - } + return; + } - int count = dictionary.Count; - _writer.Write(count); + int count = dictionary.Count; + Writer.Write(count); - foreach (KeyValuePair pair in dictionary) - { - string key = pair.Key; - Translate(ref key); - T value = pair.Value; - objectTranslator(this, valueFactory, ref value); - } - } - - /// - /// Writes out the boolean which says if this object is null or not. - /// - /// The object to test. - /// The type of object to test. - /// True if the object should be written, false otherwise. - public bool TranslateNullable(T value) - { - bool haveRef = (value != null); - _writer.Write(haveRef); - return haveRef; + foreach (KeyValuePair pair in dictionary) + { + string? key = pair.Key; + Translate(ref key); + T value = pair.Value; + objectTranslator(this, valueFactory, ref value); } } + + /// + /// Writes out the boolean which says if this object is null or not. + /// + /// The object to test. + /// The type of object to test. + /// True if the object should be written, false otherwise. + public bool TranslateNullable([NotNullWhen(true)] T? value) + where T : class + { + bool haveRef = value != null; + Writer.Write(haveRef); + return haveRef; + } } } diff --git a/src/MSBuildTaskHost/BackEnd/ITranslator.cs b/src/MSBuildTaskHost/BackEnd/ITranslator.cs index 30be4c837af..f5473d38d8c 100644 --- a/src/MSBuildTaskHost/BackEnd/ITranslator.cs +++ b/src/MSBuildTaskHost/BackEnd/ITranslator.cs @@ -6,252 +6,249 @@ using System.Globalization; using System.IO; -#nullable disable - -namespace Microsoft.Build.TaskHost.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd; + +/// +/// This delegate is used for objects which do not have public parameterless constructors and must be constructed using +/// another method. When invoked, this delegate should return a new object which has been translated appropriately. +/// +/// The type to be translated. +internal delegate T NodePacketValueFactory(ITranslator translator); + +/// +/// Delegate for users that want to translate an arbitrary structure that doesn't implement (e.g. translating a complex collection) +/// +/// the translator +/// the object to translate +internal delegate void ObjectTranslator(ITranslator translator, ref T objectToTranslate); + +/// +/// Delegate for users that want to translate an arbitrary structure that doesn't implement (e.g. translating a complex collection) +/// +/// the translator +/// The factory to use to create the value. +/// the object to translate +internal delegate void ObjectTranslatorWithValueFactory(ITranslator translator, NodePacketValueFactory valueFactory, ref T objectToTranslate); + +/// +/// This delegate is used to create arbitrary collection types for serialization. +/// +/// The type of dictionary to be created. +internal delegate T NodePacketCollectionCreator(int capacity); + +/// +/// The serialization mode. +/// +internal enum TranslationDirection { /// - /// This delegate is used for objects which do not have public parameterless constructors and must be constructed using - /// another method. When invoked, this delegate should return a new object which has been translated appropriately. + /// Indicates the serializer is operating in write mode. /// - /// The type to be translated. - internal delegate T NodePacketValueFactory(ITranslator translator); + WriteToStream, /// - /// Delegate for users that want to translate an arbitrary structure that doesn't implement (e.g. translating a complex collection) + /// Indicates the serializer is operating in read mode. /// - /// the translator - /// the object to translate - internal delegate void ObjectTranslator(ITranslator translator, ref T objectToTranslate); + ReadFromStream +} +/// +/// This interface represents an object which aids objects in serializing and +/// deserializing INodePackets. +/// +/// +/// The reason we bother with a custom serialization mechanism at all is two fold: +/// 1. The .Net serialization mechanism is inefficient, even if you implement ISerializable +/// with your own custom mechanism. This is because the serializer uses a bag called +/// SerializationInfo into which you are expected to drop all your data. This adds +/// an unnecessary level of indirection to the serialization routines and prevents direct, +/// efficient access to the byte-stream. +/// 2. You have to implement both a reader and writer part, which introduces the potential for +/// error should the classes be later modified. If the reader and writer methods are not +/// kept in perfect sync, serialization errors will occur. Our custom serializer eliminates +/// that by ensuring a single Translate method on a given object can handle both reads and +/// writes without referencing any field more than once. +/// +internal interface ITranslator : IDisposable +{ /// - /// Delegate for users that want to translate an arbitrary structure that doesn't implement (e.g. translating a complex collection) + /// Gets or sets the negotiated packet version between the communicating nodes. + /// This represents the minimum packet version supported by both the sender and receiver, + /// ensuring backward compatibility during cross-version communication. /// - /// the translator - /// The factory to use to create the value. - /// the object to translate - internal delegate void ObjectTranslatorWithValueFactory(ITranslator translator, NodePacketValueFactory valueFactory, ref T objectToTranslate); + /// + /// This version is determined during the initial handshake between nodes and may differ + /// from NodePacketTypeExtensions.PacketVersion when nodes are running different MSBuild versions. + /// The negotiated version is used to conditionally serialize/deserialize fields that may + /// not be supported by older packet versions. + /// + byte NegotiatedPacketVersion { get; set; } /// - /// This delegate is used to create arbitrary collection types for serialization. + /// Gets the current serialization mode. /// - /// The type of dictionary to be created. - internal delegate T NodePacketCollectionCreator(int capacity); + TranslationDirection Mode { get; } /// - /// The serialization mode. + /// Gets the binary reader. /// - internal enum TranslationDirection - { - /// - /// Indicates the serializer is operating in write mode. - /// - WriteToStream, - - /// - /// Indicates the serializer is operating in read mode. - /// - ReadFromStream - } + /// + /// This should ONLY be used when absolutely necessary for translation. It is generally unnecessary for the + /// translating object to know the direction of translation. Use one of the Translate methods instead. + /// + BinaryReader Reader { get; } /// - /// This interface represents an object which aids objects in serializing and - /// deserializing INodePackets. + /// Gets the binary writer. /// /// - /// The reason we bother with a custom serialization mechanism at all is two fold: - /// 1. The .Net serialization mechanism is inefficient, even if you implement ISerializable - /// with your own custom mechanism. This is because the serializer uses a bag called - /// SerializationInfo into which you are expected to drop all your data. This adds - /// an unnecessary level of indirection to the serialization routines and prevents direct, - /// efficient access to the byte-stream. - /// 2. You have to implement both a reader and writer part, which introduces the potential for - /// error should the classes be later modified. If the reader and writer methods are not - /// kept in perfect sync, serialization errors will occur. Our custom serializer eliminates - /// that by ensuring a single Translate method on a given object can handle both reads and - /// writes without referencing any field more than once. + /// This should ONLY be used when absolutely necessary for translation. It is generally unnecessary for the + /// translating object to know the direction of translation. Use one of the Translate methods instead. /// - internal interface ITranslator : IDisposable - { - /// - /// Gets or sets the negotiated packet version between the communicating nodes. - /// This represents the minimum packet version supported by both the sender and receiver, - /// ensuring backward compatibility during cross-version communication. - /// - /// - /// This version is determined during the initial handshake between nodes and may differ - /// from NodePacketTypeExtensions.PacketVersion when nodes are running different MSBuild versions. - /// The negotiated version is used to conditionally serialize/deserialize fields that may - /// not be supported by older packet versions. - /// - byte NegotiatedPacketVersion { get; set; } - - /// - /// Returns the current serialization mode. - /// - TranslationDirection Mode - { - get; - } - - /// - /// Returns the binary reader. - /// - /// - /// This should ONLY be used when absolutely necessary for translation. It is generally unnecessary for the - /// translating object to know the direction of translation. Use one of the Translate methods instead. - /// - BinaryReader Reader - { - get; - } - - /// - /// Returns the binary writer. - /// - /// - /// This should ONLY be used when absolutely necessary for translation. It is generally unnecessary for the - /// translating object to know the direction of translation. Use one of the Translate methods instead. - /// - BinaryWriter Writer - { - get; - } - - /// - /// Translates a boolean. - /// - /// The value to be translated. - void Translate(ref bool value); - - /// - /// Translates an array. - /// - /// The array to be translated. - void Translate(ref bool[] array); - - /// - /// Translates a byte. - /// - /// The value to be translated. - void Translate(ref byte value); - - /// - /// Translates a short. - /// - /// The value to be translated. - void Translate(ref short value); - - /// - /// Translates a unsigned short. - /// - /// The value to be translated. - void Translate(ref ushort value); - - /// - /// Translates an integer. - /// - /// The value to be translated. - void Translate(ref int value); - - /// - /// Translates an array. - /// - /// The array to be translated. - void Translate(ref int[] array); - - /// - /// Translates a long. - /// - /// The value to be translated. - void Translate(ref long value); - - /// - /// Translates a string. - /// - /// The value to be translated. - void Translate(ref string value); - - /// - /// Translates a double. - /// - /// The value to be translated. - void Translate(ref double value); - - /// - /// Translates a string array. - /// - /// The array to be translated. - void Translate(ref string[] array); - - /// - /// Translates a collection of T into the specified type using an and - /// - /// The collection to be translated. - /// The translator to use for the values in the collection. - /// The factory to create the ICollection. - /// The type contained in the collection. - /// The type of collection to be created. - void Translate(ref ICollection collection, ObjectTranslator objectTranslator, NodePacketCollectionCreator collectionFactory) where L : ICollection; - - /// - /// Translates a DateTime. - /// - /// The value to be translated. - void Translate(ref DateTime value); - - /// - /// Translates an enumeration. - /// - /// The enumeration type. - /// The enumeration instance to be translated. - /// The enumeration value as an integer. - /// This is a bit ugly, but it doesn't seem like a nice method signature is possible because - /// you can't pass the enum type as a reference and constrain the generic parameter to Enum. Nor - /// can you simply pass as ref Enum, because an enum instance doesn't match that function signature. - /// Finally, converting the enum to an int assumes that we always want to transport enums as ints. This - /// works in all of our current cases, but certainly isn't perfectly generic. - void TranslateEnum(ref T value, int numericValue) - where T : struct, Enum; - - void TranslateException(ref Exception value); - - /// - /// Translates a culture - /// - /// The culture - void TranslateCulture(ref CultureInfo culture); - - /// - /// Translates a byte array - /// - /// The array to be translated. - void Translate(ref byte[] byteArray); - - /// - /// Translates a dictionary of { string, string }. - /// - /// The dictionary to be translated. - /// The comparer used to instantiate the dictionary. - void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer); - - /// - /// Translates a dictionary of { string, T }. - /// - /// The reference type for the values, which implements INodePacketTranslatable. - /// The dictionary to be translated. - /// The comparer used to instantiate the dictionary. - /// The translator to use for the values in the dictionary - /// /// The factory to use to create the value. - void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer, ObjectTranslatorWithValueFactory objectTranslator, NodePacketValueFactory valueFactory) - where T : class; - - /// - /// Translates the boolean that says whether this value is null or not - /// - /// The object to test. - /// The type of object to test. - /// True if the object should be written, false otherwise. - bool TranslateNullable(T value); - } + BinaryWriter Writer { get; } + + /// + /// Translates a boolean. + /// + /// The value to be translated. + void Translate(ref bool value); + + /// + /// Translates an array. + /// + /// The array to be translated. + void Translate(ref bool[]? array); + + /// + /// Translates a byte. + /// + /// The value to be translated. + void Translate(ref byte value); + + /// + /// Translates a short. + /// + /// The value to be translated. + void Translate(ref short value); + + /// + /// Translates a unsigned short. + /// + /// The value to be translated. + void Translate(ref ushort value); + + /// + /// Translates an integer. + /// + /// The value to be translated. + void Translate(ref int value); + + /// + /// Translates an array. + /// + /// The array to be translated. + void Translate(ref int[]? array); + + /// + /// Translates a long. + /// + /// The value to be translated. + void Translate(ref long value); + + /// + /// Translates a string. + /// + /// The value to be translated. + void Translate(ref string? value); + + /// + /// Translates a double. + /// + /// The value to be translated. + void Translate(ref double value); + + /// + /// Translates a string array. + /// + /// The array to be translated. + void Translate(ref string[]? array); + + /// + /// Translates a collection of T into the specified type using an and . + /// + /// The collection to be translated. + /// The translator to use for the values in the collection. + /// The factory to create the ICollection. + /// The type contained in the collection. + /// The type of collection to be created. + void Translate( + ref ICollection? collection, + ObjectTranslator objectTranslator, + NodePacketCollectionCreator collectionFactory) + where L : ICollection; + + /// + /// Translates a DateTime. + /// + /// The value to be translated. + void Translate(ref DateTime value); + + /// + /// Translates an enumeration. + /// + /// The enumeration type. + /// The enumeration instance to be translated. + /// The enumeration value as an integer. + /// This is a bit ugly, but it doesn't seem like a nice method signature is possible because + /// you can't pass the enum type as a reference and constrain the generic parameter to Enum. Nor + /// can you simply pass as ref Enum, because an enum instance doesn't match that function signature. + /// Finally, converting the enum to an int assumes that we always want to transport enums as ints. This + /// works in all of our current cases, but certainly isn't perfectly generic. + void TranslateEnum(ref T value, int numericValue) + where T : struct, Enum; + + void TranslateException(ref Exception? value); + + /// + /// Translates a culture. + /// + /// The culture. + void TranslateCulture(ref CultureInfo? culture); + + /// + /// Translates a byte array. + /// + /// The array to be translated. + void Translate(ref byte[]? byteArray); + + /// + /// Translates a dictionary of { string, string }. + /// + /// The dictionary to be translated. + /// The comparer used to instantiate the dictionary. + void TranslateDictionary(ref Dictionary? dictionary, IEqualityComparer comparer); + + /// + /// Translates a dictionary of { string, T }. + /// + /// The reference type for the values, which implements INodePacketTranslatable. + /// The dictionary to be translated. + /// The comparer used to instantiate the dictionary. + /// The translator to use for the values in the dictionary. + /// /// The factory to use to create the value. + void TranslateDictionary( + ref Dictionary? dictionary, + IEqualityComparer comparer, + ObjectTranslatorWithValueFactory objectTranslator, + NodePacketValueFactory valueFactory) + where T : class; + + /// + /// Translates the boolean that says whether this value is null or not. + /// + /// The object to test. + /// The type of object to test. + /// True if the object should be written, false otherwise. + bool TranslateNullable(T? value) + where T : class; } diff --git a/src/MSBuildTaskHost/BackEnd/TranslatorHelpers.cs b/src/MSBuildTaskHost/BackEnd/TranslatorHelpers.cs index f231634e7bf..554306a8583 100644 --- a/src/MSBuildTaskHost/BackEnd/TranslatorHelpers.cs +++ b/src/MSBuildTaskHost/BackEnd/TranslatorHelpers.cs @@ -3,60 +3,53 @@ using System.Collections.Generic; -#nullable disable +namespace Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.TaskHost.BackEnd +/// +/// This class provides helper methods to adapt from to +/// . +/// +internal static class TranslatorHelpers { /// - /// This class provides helper methods to adapt from to - /// . + /// Translates an object implementing which does not expose a + /// public parameterless constructor. /// - internal static class TranslatorHelpers + /// The reference type. + /// The translator. + /// The value to be translated. + /// The factory method used to instantiate values of type T. + public static void Translate( + this ITranslator translator, + ref T instance, + NodePacketValueFactory valueFactory) + where T : class, ITranslatable { - /// - /// Translates an object implementing which does not expose a - /// public parameterless constructor. - /// - /// The reference type. - /// The translator - /// The value to be translated. - /// The factory method used to instantiate values of type T. - public static void Translate( - this ITranslator translator, - ref T instance, - NodePacketValueFactory valueFactory) where T : ITranslatable + if (!translator.TranslateNullable(instance)) { - if (!translator.TranslateNullable(instance)) - { - return; - } - if (translator.Mode == TranslationDirection.ReadFromStream) - { - instance = valueFactory(translator); - } - else - { - instance.Translate(translator); - } + return; } - private static ObjectTranslatorWithValueFactory AdaptFactory(NodePacketValueFactory valueFactory) where T : ITranslatable + if (translator.Mode == TranslationDirection.ReadFromStream) { - static void TranslateUsingValueFactory(ITranslator translator, NodePacketValueFactory valueFactory, ref T objectToTranslate) - { - translator.Translate(ref objectToTranslate, valueFactory); - } - - return TranslateUsingValueFactory; + instance = valueFactory(translator); } - - public static void TranslateDictionary( - this ITranslator translator, - ref Dictionary dictionary, - IEqualityComparer comparer, - NodePacketValueFactory valueFactory) where T : class, ITranslatable + else { - translator.TranslateDictionary(ref dictionary, comparer, AdaptFactory(valueFactory), valueFactory); + instance.Translate(translator); } } + + private static ObjectTranslatorWithValueFactory AdaptFactory(NodePacketValueFactory valueFactory) + where T : class, ITranslatable + => static (ITranslator translator, NodePacketValueFactory valueFactory, ref T objectToTranslate) + => translator.Translate(ref objectToTranslate, valueFactory); + + public static void TranslateDictionary( + this ITranslator translator, + ref Dictionary? dictionary, + IEqualityComparer comparer, + NodePacketValueFactory valueFactory) + where T : class, ITranslatable + => translator.TranslateDictionary(ref dictionary, comparer, AdaptFactory(valueFactory), valueFactory); } From f07f4ce9597eadc323cf3e614723ffa5e5f11edd Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 5 Feb 2026 10:20:14 -0800 Subject: [PATCH 097/136] MSBuildTaskHost: Clean up BufferedReadStream - File-scoped namespace - Expression-bodied members - Primary constructor - Enable nullability --- .../BackEnd/BufferedReadStream.cs | 194 ++++++++---------- 1 file changed, 85 insertions(+), 109 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/BufferedReadStream.cs b/src/MSBuildTaskHost/BackEnd/BufferedReadStream.cs index 832a0fb023c..3343b74be91 100644 --- a/src/MSBuildTaskHost/BackEnd/BufferedReadStream.cs +++ b/src/MSBuildTaskHost/BackEnd/BufferedReadStream.cs @@ -5,145 +5,121 @@ using System.IO; using System.IO.Pipes; -#nullable disable +namespace Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.TaskHost.BackEnd +internal sealed class BufferedReadStream(NamedPipeServerStream innerStream) : Stream { - internal class BufferedReadStream : Stream - { - private const int BUFFER_SIZE = 1024; - private NamedPipeServerStream _innerStream; - private byte[] _buffer; + private const int BUFFER_SIZE = 1024; - // The number of bytes in the buffer that have been read from the underlying stream but not read by consumers of this stream - private int _currentlyBufferedByteCount; - private int _currentIndexInBuffer; + private readonly NamedPipeServerStream _innerStream = innerStream; + private readonly byte[] _buffer = new byte[BUFFER_SIZE]; - public BufferedReadStream(NamedPipeServerStream innerStream) - { - _innerStream = innerStream; - _buffer = new byte[BUFFER_SIZE]; + // The number of bytes in the buffer that have been read from the underlying stream but not read by consumers of this stream + private int _currentlyBufferedByteCount = 0; + private int _currentIndexInBuffer; - _currentlyBufferedByteCount = 0; - } + public override bool CanRead => _innerStream.CanRead; - public override bool CanRead { get { return _innerStream.CanRead; } } + public override bool CanSeek => false; - public override bool CanSeek { get { return false; } } + public override bool CanWrite => _innerStream.CanWrite; - public override bool CanWrite { get { return _innerStream.CanWrite; } } + public override long Length => _innerStream.Length; - public override long Length { get { return _innerStream.Length; } } + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } - public override long Position + protected override void Dispose(bool disposing) + { + if (disposing) { - get { throw new NotSupportedException(); } - set { throw new NotSupportedException(); } + _innerStream.Dispose(); } - public override void Flush() + base.Dispose(disposing); + } + + public override void Flush() + => _innerStream.Flush(); + + public override int ReadByte() + { + if (_currentlyBufferedByteCount > 0) { - _innerStream.Flush(); + int ret = _buffer[_currentIndexInBuffer]; + _currentIndexInBuffer++; + _currentlyBufferedByteCount--; + return ret; } - - public override int ReadByte() + else { - if (_currentlyBufferedByteCount > 0) - { - int ret = _buffer[_currentIndexInBuffer]; - _currentIndexInBuffer++; - _currentlyBufferedByteCount--; - return ret; - } - else - { - // Let the base class handle it, which will end up calling the Read() method - return base.ReadByte(); - } + // Let the base class handle it, which will end up calling the Read() method + return base.ReadByte(); } + } - public override int Read(byte[] buffer, int offset, int count) + public override int Read(byte[] buffer, int offset, int count) + { + if (count > BUFFER_SIZE) { - if (count > BUFFER_SIZE) - { - // Trying to read more data than the buffer can hold - int alreadyCopied = 0; - if (_currentlyBufferedByteCount > 0) - { - Array.Copy(_buffer, _currentIndexInBuffer, buffer, offset, _currentlyBufferedByteCount); - alreadyCopied = _currentlyBufferedByteCount; - _currentIndexInBuffer = 0; - _currentlyBufferedByteCount = 0; - } - int innerReadCount = _innerStream.Read(buffer, offset + alreadyCopied, count - alreadyCopied); - return innerReadCount + alreadyCopied; - } - else if (count <= _currentlyBufferedByteCount) - { - // Enough data buffered to satisfy read request - Array.Copy(_buffer, _currentIndexInBuffer, buffer, offset, count); - _currentIndexInBuffer += count; - _currentlyBufferedByteCount -= count; - return count; - } - else + // Trying to read more data than the buffer can hold + int alreadyCopied = 0; + if (_currentlyBufferedByteCount > 0) { - // Need to read more data - int alreadyCopied = 0; - if (_currentlyBufferedByteCount > 0) - { - Array.Copy(_buffer, _currentIndexInBuffer, buffer, offset, _currentlyBufferedByteCount); - alreadyCopied = _currentlyBufferedByteCount; - _currentIndexInBuffer = 0; - _currentlyBufferedByteCount = 0; - } - - int innerReadCount = _innerStream.Read(_buffer, 0, BUFFER_SIZE); + Array.Copy(_buffer, _currentIndexInBuffer, buffer, offset, _currentlyBufferedByteCount); + alreadyCopied = _currentlyBufferedByteCount; _currentIndexInBuffer = 0; - _currentlyBufferedByteCount = innerReadCount; - - int remainingCopyCount; - - if (alreadyCopied + innerReadCount >= count) - { - remainingCopyCount = count - alreadyCopied; - } - else - { - remainingCopyCount = innerReadCount; - } - - Array.Copy(_buffer, 0, buffer, offset + alreadyCopied, remainingCopyCount); - _currentIndexInBuffer += remainingCopyCount; - _currentlyBufferedByteCount -= remainingCopyCount; - - return alreadyCopied + remainingCopyCount; + _currentlyBufferedByteCount = 0; } - } - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); + int innerReadCount = _innerStream.Read(buffer, offset + alreadyCopied, count - alreadyCopied); + return innerReadCount + alreadyCopied; } - - public override void SetLength(long value) + else if (count <= _currentlyBufferedByteCount) { - throw new NotSupportedException(); + // Enough data buffered to satisfy read request + Array.Copy(_buffer, _currentIndexInBuffer, buffer, offset, count); + _currentIndexInBuffer += count; + _currentlyBufferedByteCount -= count; + return count; } - - public override void Write(byte[] buffer, int offset, int count) + else { - _innerStream.Write(buffer, offset, count); - } - - protected override void Dispose(bool disposing) - { - if (disposing) + // Need to read more data + int alreadyCopied = 0; + if (_currentlyBufferedByteCount > 0) { - _innerStream.Dispose(); + Array.Copy(_buffer, _currentIndexInBuffer, buffer, offset, _currentlyBufferedByteCount); + alreadyCopied = _currentlyBufferedByteCount; + _currentIndexInBuffer = 0; + _currentlyBufferedByteCount = 0; } - base.Dispose(disposing); + int innerReadCount = _innerStream.Read(_buffer, 0, BUFFER_SIZE); + _currentIndexInBuffer = 0; + _currentlyBufferedByteCount = innerReadCount; + + int remainingCopyCount = alreadyCopied + innerReadCount >= count + ? count - alreadyCopied + : innerReadCount; + + Array.Copy(_buffer, 0, buffer, offset + alreadyCopied, remainingCopyCount); + _currentIndexInBuffer += remainingCopyCount; + _currentlyBufferedByteCount -= remainingCopyCount; + + return alreadyCopied + remainingCopyCount; } } + + public override long Seek(long offset, SeekOrigin origin) + => throw new NotSupportedException(); + + public override void SetLength(long value) + => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) + => _innerStream.Write(buffer, offset, count); } From 34fa5a7f4f98ac1c5e17b827280ca511138f49e8 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 5 Feb 2026 10:40:14 -0800 Subject: [PATCH 098/136] MSBuildTaskHost: Clean up INode* interfaces and related types - File-scoped namespaces - Remove uncalled members - Remove unused NodePacketType enum members - Enable nullability --- src/MSBuildTaskHost/BackEnd/INodeEndpoint.cs | 153 +++--- src/MSBuildTaskHost/BackEnd/INodePacket.cs | 492 ++++++------------ .../BackEnd/INodePacketFactory.cs | 89 ++-- .../BackEnd/INodePacketHandler.cs | 23 +- .../BackEnd/NodeEndpointOutOfProcTaskHost.cs | 4 +- src/Shared/INodePacket.cs | 6 + 6 files changed, 268 insertions(+), 499 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/INodeEndpoint.cs b/src/MSBuildTaskHost/BackEnd/INodeEndpoint.cs index d1d6b4df01a..a3c8528d94e 100644 --- a/src/MSBuildTaskHost/BackEnd/INodeEndpoint.cs +++ b/src/MSBuildTaskHost/BackEnd/INodeEndpoint.cs @@ -1,113 +1,86 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable - -namespace Microsoft.Build.TaskHost.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd; + +/// +/// Used to receive link status updates from an endpoint. +/// +/// The endpoint invoking the delegate. +/// The current status of the link. +internal delegate void LinkStatusChangedDelegate(INodeEndpoint endpoint, LinkStatus status); + +/// +/// The connection status of a link between the NodeEndpoint on the host and the NodeEndpoint +/// on the peer. +/// +internal enum LinkStatus { - #region Delegates /// - /// Used to receive link status updates from an endpoint. + /// The connection has never been started. /// - /// The endpoint invoking the delegate. - /// The current status of the link. - internal delegate void LinkStatusChangedDelegate(INodeEndpoint endpoint, LinkStatus status); + Inactive, /// - /// Used to receive data from a node + /// The connection is active, the most recent data has been successfully sent, and the + /// node is responding to pings. /// - /// The endpoint invoking the delegate. - /// The packet received. - internal delegate void DataReceivedDelegate(INodeEndpoint endpoint, INodePacket packet); - #endregion + Active, - #region Enums /// - /// The connection status of a link between the NodeEndpoint on the host and the NodeEndpoint - /// on the peer. + /// The connection has failed and been terminated. /// - internal enum LinkStatus - { - /// - /// The connection has never been started. - /// - Inactive, - - /// - /// The connection is active, the most recent data has been successfully sent, and the - /// node is responding to pings. - /// - Active, - - /// - /// The connection has failed and been terminated. - /// - Failed, - - /// - /// The connection could not be made/timed out. - /// - ConnectionFailed, - } - - #endregion + Failed, /// - /// This interface represents one end of a connection between the INodeProvider and a Node. - /// Implementations of this interface define the actual mechanism by which data is communicated. + /// The connection could not be made/timed out. /// - internal interface INodeEndpoint - { - #region Events - - /// - /// Raised when the status of the node's link has changed. - /// - event LinkStatusChangedDelegate OnLinkStatusChanged; - - #endregion + ConnectionFailed, +} - #region Properties +/// +/// This interface represents one end of a connection between the INodeProvider and a Node. +/// Implementations of this interface define the actual mechanism by which data is communicated. +/// +internal interface INodeEndpoint +{ + /// + /// Raised when the status of the node's link has changed. + /// + event LinkStatusChangedDelegate OnLinkStatusChanged; - /// - /// The current link status for this endpoint. - /// - LinkStatus LinkStatus - { - get; - } - #endregion + /// + /// Gets the current link status for this endpoint. + /// + LinkStatus LinkStatus { get; } - #region Methods - /// - /// Waits for the remote node to establish a connection. - /// - /// The factory used to deserialize packets. - /// Only one of Listen() or Connect() may be called on an endpoint. - void Listen(INodePacketFactory factory); + /// + /// Waits for the remote node to establish a connection. + /// + /// The factory used to deserialize packets. + /// Only one of Listen() or Connect() may be called on an endpoint. + void Listen(INodePacketFactory factory); - /// - /// Instructs the node to connect to its peer endpoint. - /// - /// The factory used to deserialize packets. - void Connect(INodePacketFactory factory); + /// + /// Instructs the node to connect to its peer endpoint. + /// + /// The factory used to deserialize packets. + void Connect(INodePacketFactory factory); - /// - /// Instructs the node to disconnect from its peer endpoint. - /// - void Disconnect(); + /// + /// Instructs the node to disconnect from its peer endpoint. + /// + void Disconnect(); - /// - /// Sends a data packet to the node. - /// - /// The packet to be sent. - void SendData(INodePacket packet); - #endregion + /// + /// Sends a data packet to the node. + /// + /// The packet to be sent. + void SendData(INodePacket packet); - /// - /// Called when we are about to send last packet to finalize graceful disconnection with client. - /// This is needed to handle race condition when both client and server is gracefully about to close connection. - /// - void ClientWillDisconnect(); - } + /// + /// Called when we are about to send last packet to finalize graceful disconnection with client. + /// This is needed to handle race condition when both client and server is gracefully about to close connection. + /// + void ClientWillDisconnect(); } diff --git a/src/MSBuildTaskHost/BackEnd/INodePacket.cs b/src/MSBuildTaskHost/BackEnd/INodePacket.cs index f25d8a0a22f..348f8c08388 100644 --- a/src/MSBuildTaskHost/BackEnd/INodePacket.cs +++ b/src/MSBuildTaskHost/BackEnd/INodePacket.cs @@ -1,380 +1,178 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable - using System; using System.IO; -namespace Microsoft.Build.TaskHost.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd; + +/// +/// Enumeration of all of the packet types used for communication. +/// Uses lower 6 bits for packet type (0-63), upper 2 bits reserved for flags. +/// +/// +/// This is a reduced set of packet types used by MSBuildTaskHost. +/// It is derived from the full set of packet types used for MSBuild's internal node communication. +/// +internal enum NodePacketType : byte { - #region Enums + // Mask for extracting packet type (lower 6 bits) + TypeMask = 0x3F, // 00111111 /// - /// Enumeration of all of the packet types used for communication. - /// Uses lower 6 bits for packet type (0-63), upper 2 bits reserved for flags. + /// A logging message. + /// + /// Contents: + /// Build Event Type + /// Build Event Args /// - internal enum NodePacketType : byte - { - // Mask for extracting packet type (lower 6 bits) - TypeMask = 0x3F, // 00111111 - - /// - /// Notifies the Node to set a configuration for a particular build. This is sent before - /// any BuildRequests are made and will not be sent again for a particular build. This instructs - /// the node to prepare to receive build requests. - /// - /// Contains: - /// Build ID - /// Environment variables - /// Logging Services Configuration - /// Node ID - /// Default Global Properties - /// Toolset Definition Locations - /// Startup Directory - /// UI Culture Information - /// App Domain Configuration XML - /// - NodeConfiguration = 0x00, - - /// - /// A BuildRequestConfiguration object. - /// When sent TO a node, this informs the node of a build configuration. - /// When sent FROM a node, this requests a BuildRequestConfigurationResponse to map the configuration to the - /// appropriate global configuration ID. - /// - /// Contents: - /// Configuration ID - /// Project Filename - /// Project Properties - /// Project Tools Version - /// - BuildRequestConfiguration, // 0x01 - - /// - /// A response to a request to map a build configuration - /// - /// Contents: - /// Node Configuration ID - /// Global Configuration ID - /// - BuildRequestConfigurationResponse, // 0x02 - - /// - /// Information about a project that has been loaded by a node. - /// - /// Contents: - /// Global Configuration ID - /// Initial Targets - /// Default Targets - /// - ProjectLoadInfo, // 0x03 - - /// - /// Packet used to inform the scheduler that a node's active build request is blocked. - /// - /// Contents: - /// Build Request ID - /// Active Targets - /// Blocked Target, if any - /// Child Requests, if any - /// - BuildRequestBlocker, // 0x04 - - /// - /// Packet used to unblocked a blocked request on a node. - /// - /// Contents: - /// Build Request ID - /// Build Results for child requests, if any. - /// - BuildRequestUnblocker, // 0x05 - - /// - /// A BuildRequest object - /// - /// Contents: - /// Build Request ID - /// Configuration ID - /// Project Instance ID - /// Targets - /// - BuildRequest, // 0x06 - - /// - /// A BuildResult object - /// - /// Contents: - /// Build ID - /// Project Instance ID - /// Targets - /// Outputs (per Target) - /// Results (per Target) - /// - BuildResult, // 0x07 - - /// - /// A logging message. - /// - /// Contents: - /// Build Event Type - /// Build Event Args - /// - LogMessage, // 0x08 - - /// - /// Informs the node that the build is complete. - /// - /// Contents: - /// Prepare For Reuse - /// - NodeBuildComplete, // 0x09 - - /// - /// Reported by the node (or node provider) when a node has terminated. This is the final packet that will be received - /// from a node. - /// - /// Contents: - /// Reason - /// - NodeShutdown, // 0x0A - - /// - /// Notifies the task host to set the task-specific configuration for a particular task execution. - /// This is sent in place of NodeConfiguration and gives the task host all the information it needs - /// to set itself up and execute the task that matches this particular configuration. - /// - /// Contains: - /// Node ID (of parent MSBuild node, to make the logging work out) - /// Startup directory - /// Environment variables - /// UI Culture information - /// App Domain Configuration XML - /// Task name - /// Task assembly location - /// Parameter names and values to set to the task prior to execution - /// - TaskHostConfiguration, // 0x0B - - /// - /// Informs the parent node that the task host has finished executing a - /// particular task. Does not need to contain identifying information - /// about the task, because the task host will only ever be connected to - /// one parent node at a a time, and will only ever be executing one task - /// for that node at any one time. - /// - /// Contents: - /// Task result (success / failure) - /// Resultant parameter values (for output gathering) - /// - TaskHostTaskComplete, // 0x0C - - /// - /// Message sent from the node to its paired task host when a task that - /// supports ICancellableTask is cancelled. - /// - /// Contents: - /// (nothing) - /// - TaskHostTaskCancelled, // 0x0D - - /// - /// Message sent from a node when it needs to have an SDK resolved. - /// - ResolveSdkRequest, // 0x0E + LogMessage = 0x08, - /// - /// Message sent back to a node when an SDK has been resolved. - /// - ResolveSdkResponse, // 0x0F - - /// - /// Message sent from a node when a task is requesting or returning resources from the scheduler. - /// - ResourceRequest, // 0x10 - - /// - /// Message sent back to a node informing it about the resource that were granted by the scheduler. - /// - ResourceResponse, // 0x11 - - /// - /// Message sent from a node reporting a file access. - /// - FileAccessReport, // 0x12 - - /// - /// Message sent from a node reporting process data. - /// - ProcessReport, // 0x13 - - - /// Notifies the RAR node to set a configuration for a particular build. - RarNodeEndpointConfiguration, - - /// - /// A request contains the inputs to the RAR task. - /// - RarNodeExecuteRequest, // 0x14 + /// + /// Informs the node that the build is complete. + /// + /// Contents: + /// Prepare For Reuse + /// + NodeBuildComplete = 0x09, - /// - /// A request contains the outputs and log events of a completed RAR task. - /// - RarNodeExecuteResponse, // 0x15 + /// + /// Reported by the node (or node provider) when a node has terminated. This is the final packet that will be received + /// from a node. + /// + /// Contents: + /// Reason + /// + NodeShutdown = 0x0A, - // Reserve space for future core packet types (0x16-0x3B available for expansion) + /// + /// Notifies the task host to set the task-specific configuration for a particular task execution. + /// This is sent in place of NodeConfiguration and gives the task host all the information it needs + /// to set itself up and execute the task that matches this particular configuration. + /// + /// Contains: + /// Node ID (of parent MSBuild node, to make the logging work out) + /// Startup directory + /// Environment variables + /// UI Culture information + /// App Domain Configuration XML + /// Task name + /// Task assembly location + /// Parameter names and values to set to the task prior to execution + /// + TaskHostConfiguration = 0x0B, - // Server command packets placed at end of safe range to maintain separation from core packets - #region ServerNode enums + /// + /// Informs the parent node that the task host has finished executing a + /// particular task. Does not need to contain identifying information + /// about the task, because the task host will only ever be connected to + /// one parent node at a a time, and will only ever be executing one task + /// for that node at any one time. + /// + /// Contents: + /// Task result (success / failure) + /// Resultant parameter values (for output gathering) + /// + TaskHostTaskComplete = 0x0C, - /// - /// A batch of log events emitted while the RAR task is executing. - /// - RarNodeBufferedLogEvents, + /// + /// Message sent from the node to its paired task host when a task that + /// supports ICancellableTask is cancelled. + /// + /// Contents: + /// (nothing) + /// + TaskHostTaskCancelled = 0x0D, +} - /// - /// Command in form of MSBuild command line for server node - MSBuild Server. - /// - ServerNodeBuildCommand = 0x3C, // End of safe range +/// +/// This interface represents a packet which may be transmitted using an INodeEndpoint. +/// Implementations define the serialized form of the data. +/// +internal interface INodePacket : ITranslatable +{ + /// + /// Gets the type of the packet. Used to reconstitute the packet using the correct factory. + /// + NodePacketType Type { get; } +} - /// - /// Response from server node command. - /// - ServerNodeBuildResult = 0x3D, +/// +/// Provides utilities for handling node packet types and extended headers in MSBuild's distributed build system. +/// +/// This class manages the communication protocol between build nodes, including: +/// - Packet versioning for protocol compatibility +/// - Extended header flags for enhanced packet metadata +/// - Type extraction and manipulation for network communication +/// +/// The packet format uses the upper 2 bits (6-7) for flags while preserving +/// the lower 6 bits for the actual packet type enumeration. +/// +internal static class NodePacketTypeExtensions +{ + /// + /// Defines the communication protocol version for node communication. + /// + /// Version 1: Introduced for the .NET Task Host protocol. This version + /// excludes the translation of appDomainConfig within TaskHostConfiguration + /// to maintain backward compatibility and reduce serialization overhead. + /// + /// Version 2: Adds support of HostServices and target name translation in TaskHostConfiguration. + /// + /// When incrementing this version, ensure compatibility with existing + /// task hosts and update the corresponding deserialization logic. + /// + public const byte PacketVersion = 2; - /// - /// Info about server console activity. - /// - ServerNodeConsoleWrite = 0x3E, + // Flag bits in upper 2 bits + private const byte ExtendedHeaderFlag = 0x40; // Bit 6: 01000000 - /// - /// Command to cancel ongoing build. - /// - ServerNodeBuildCancel = 0x3F, // Last value in safe range (0x3F = 00111111) + /// + /// Determines if a packet has an extended header by checking if the extended header flag is set. + /// Uses bit 6 which is now safely separated from packet type values. + /// + /// The raw packet type byte. + /// True if the packet has an extended header; otherwise, false. + public static bool HasExtendedHeader(byte rawType) + => (rawType & ExtendedHeaderFlag) != 0; - #endregion - } - #endregion + /// + /// Get base packet type, stripping all flag bits (bits 6 and 7). + /// + /// The raw packet type byte with potential flags. + /// The clean packet type without flag bits. + public static NodePacketType GetNodePacketType(byte rawType) + => (NodePacketType)(rawType & (byte)NodePacketType.TypeMask); /// - /// This interface represents a packet which may be transmitted using an INodeEndpoint. - /// Implementations define the serialized form of the data. + /// Reads the protocol version from an extended header in the stream. + /// This method expects the stream to be positioned at the version byte. /// - internal interface INodePacket : ITranslatable + /// The stream to read the version byte from. + /// The protocol version byte read from the stream. + /// Thrown when the stream ends unexpectedly while reading the version. + public static byte ReadVersion(Stream stream) { - #region Properties - /// - /// The type of the packet. Used to reconstitute the packet using the correct factory. - /// - NodePacketType Type + int value = stream.ReadByte(); + if (value == -1) { - get; + throw new EndOfStreamException("Unexpected end of stream while reading version"); } - #endregion + return (byte)value; } /// - /// Provides utilities for handling node packet types and extended headers in MSBuild's distributed build system. + /// Negotiates the packet version to use for communication between nodes. + /// Returns the lower of the two versions to ensure compatibility between + /// nodes that may be running different versions of MSBuild. /// - /// This class manages the communication protocol between build nodes, including: - /// - Packet versioning for protocol compatibility - /// - Extended header flags for enhanced packet metadata - /// - Type extraction and manipulation for network communication - /// - /// The packet format uses the upper 2 bits (6-7) for flags while preserving - /// the lower 6 bits for the actual packet type enumeration. + /// This allows forward and backward compatibility when nodes with different + /// packet versions communicate - they will use the lowest common version + /// that both understand. /// - internal static class NodePacketTypeExtensions - { - /// - /// Defines the communication protocol version for node communication. - /// - /// Version 1: Introduced for the .NET Task Host protocol. This version - /// excludes the translation of appDomainConfig within TaskHostConfiguration - /// to maintain backward compatibility and reduce serialization overhead. - /// - /// Version 2: Adds support of HostServices and target name translation in TaskHostConfiguration. - /// - /// When incrementing this version, ensure compatibility with existing - /// task hosts and update the corresponding deserialization logic. - /// - public const byte PacketVersion = 2; - - // Flag bits in upper 2 bits - private const byte ExtendedHeaderFlag = 0x40; // Bit 6: 01000000 - - /// - /// Determines if a packet has an extended header by checking if the extended header flag is set. - /// Uses bit 6 which is now safely separated from packet type values. - /// - /// The raw packet type byte. - /// True if the packet has an extended header, false otherwise - public static bool HasExtendedHeader(byte rawType) => (rawType & ExtendedHeaderFlag) != 0; - - /// - /// Get base packet type, stripping all flag bits (bits 6 and 7). - /// - /// The raw packet type byte with potential flags. - /// The clean packet type without flag bits. - public static NodePacketType GetNodePacketType(byte rawType) => (NodePacketType)(rawType & (byte)NodePacketType.TypeMask); - - /// - /// Create a packet type byte with extended header flag for net task host packets. - /// - /// Handshake options to check. - /// Base packet type. - /// Output byte with flag set if applicable. - /// True if extended header flag was set, false otherwise. - public static bool TryCreateExtendedHeaderType(HandshakeOptions handshakeOptions, NodePacketType type, out byte extendedheader) - { - if (Handshake.IsHandshakeOptionEnabled(handshakeOptions, HandshakeOptions.TaskHost) && Handshake.IsHandshakeOptionEnabled(handshakeOptions, HandshakeOptions.NET)) - { - extendedheader = (byte)((byte)type | ExtendedHeaderFlag); - return true; - } - - extendedheader = (byte)type; - return false; - } - - /// - /// Reads the protocol version from an extended header in the stream. - /// This method expects the stream to be positioned at the version byte. - /// - /// The stream to read the version byte from. - /// The protocol version byte read from the stream. - /// Thrown when the stream ends unexpectedly while reading the version. - public static byte ReadVersion(Stream stream) - { - int value = stream.ReadByte(); - if (value == -1) - { - throw new EndOfStreamException("Unexpected end of stream while reading version"); - } - - return (byte)value; - } - - /// - /// Writes the protocol version byte to the extended header in the stream. - /// This is typically called after writing a packet type with the extended header flag. - /// - /// The stream to write the version byte to. - /// The protocol version to write to the stream. - public static void WriteVersion(Stream stream, byte version) => stream.WriteByte(version); - - /// - /// Negotiates the packet version to use for communication between nodes. - /// Returns the lower of the two versions to ensure compatibility between - /// nodes that may be running different versions of MSBuild. - /// - /// This allows forward and backward compatibility when nodes with different - /// packet versions communicate - they will use the lowest common version - /// that both understand. - /// - /// The packet version supported by the other node. - /// The negotiated protocol version that both nodes can use (the minimum of the two versions). - public static byte GetNegotiatedPacketVersion(byte otherPacketVersion) => Math.Min(PacketVersion, otherPacketVersion); - } + /// The packet version supported by the other node. + /// The negotiated protocol version that both nodes can use (the minimum of the two versions). + public static byte GetNegotiatedPacketVersion(byte otherPacketVersion) + => Math.Min(PacketVersion, otherPacketVersion); } diff --git a/src/MSBuildTaskHost/BackEnd/INodePacketFactory.cs b/src/MSBuildTaskHost/BackEnd/INodePacketFactory.cs index db8493c0157..a115dddc453 100644 --- a/src/MSBuildTaskHost/BackEnd/INodePacketFactory.cs +++ b/src/MSBuildTaskHost/BackEnd/INodePacketFactory.cs @@ -1,61 +1,54 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable - -namespace Microsoft.Build.TaskHost.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd; + +/// +/// A delegate representing factory methods used to re-create packets deserialized from a stream. +/// +/// The translator containing the packet data. +/// The packet reconstructed from the stream. +internal delegate INodePacket NodePacketFactoryMethod(ITranslator translator); + +/// +/// This interface represents an object which is used to reconstruct packet objects from +/// binary data. +/// +internal interface INodePacketFactory { /// - /// A delegate representing factory methods used to re-create packets deserialized from a stream. + /// Registers the specified handler for a particular packet type. /// - /// The translator containing the packet data. - /// The packet reconstructed from the stream. - internal delegate INodePacket NodePacketFactoryMethod(ITranslator translator); + /// The packet type. + /// The factory for packets of the specified type. + /// The handler to be called when packets of the specified type are received. + void RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory, INodePacketHandler handler); /// - /// This interface represents an object which is used to reconstruct packet objects from - /// binary data. + /// Unregisters a packet handler. /// - internal interface INodePacketFactory - { - #region Methods - - /// - /// Registers the specified handler for a particular packet type. - /// - /// The packet type. - /// The factory for packets of the specified type. - /// The handler to be called when packets of the specified type are received. - void RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory, INodePacketHandler handler); - - /// - /// Unregisters a packet handler. - /// - /// The packet type. - void UnregisterPacketHandler(NodePacketType packetType); + /// The packet type. + void UnregisterPacketHandler(NodePacketType packetType); - /// - /// Takes a serializer, deserializes the packet and routes it to the appropriate handler. - /// - /// The node from which the packet was received. - /// The packet type. - /// The translator containing the data from which the packet should be reconstructed. - void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITranslator translator); - - /// - /// Takes a serializer and deserializes the packet. - /// - /// The packet type. - /// The translator containing the data from which the packet should be reconstructed. - INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator); + /// + /// Takes a serializer, deserializes the packet and routes it to the appropriate handler. + /// + /// The node from which the packet was received. + /// The packet type. + /// The translator containing the data from which the packet should be reconstructed. + void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITranslator translator); - /// - /// Routes the specified packet. - /// - /// The node from which the packet was received. - /// The packet to route. - void RoutePacket(int nodeId, INodePacket packet); + /// + /// Takes a serializer and deserializes the packet. + /// + /// The packet type. + /// The translator containing the data from which the packet should be reconstructed. + INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator); - #endregion - } + /// + /// Routes the specified packet. + /// + /// The node from which the packet was received. + /// The packet to route. + void RoutePacket(int nodeId, INodePacket packet); } diff --git a/src/MSBuildTaskHost/BackEnd/INodePacketHandler.cs b/src/MSBuildTaskHost/BackEnd/INodePacketHandler.cs index 3ab8dd022b3..acdf0d48205 100644 --- a/src/MSBuildTaskHost/BackEnd/INodePacketHandler.cs +++ b/src/MSBuildTaskHost/BackEnd/INodePacketHandler.cs @@ -1,21 +1,18 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable +namespace Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.TaskHost.BackEnd +/// +/// Objects which wish to receive packets from the NodePacketRouter must implement this interface. +/// +internal interface INodePacketHandler { /// - /// Objects which wish to receive packets from the NodePacketRouter must implement this interface. + /// This method is invoked by the NodePacketRouter when a packet is received and is intended for + /// this recipient. /// - internal interface INodePacketHandler - { - /// - /// This method is invoked by the NodePacketRouter when a packet is received and is intended for - /// this recipient. - /// - /// The node from which the packet was received. - /// The packet. - void PacketReceived(int node, INodePacket packet); - } + /// The node from which the packet was received. + /// The packet. + void PacketReceived(int node, INodePacket packet); } diff --git a/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs b/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs index ab86d54fe5d..7dc24c37609 100644 --- a/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs +++ b/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs @@ -555,7 +555,9 @@ private void RunReadLoop( // Check if this packet has an extended header that includes a version part. byte rawType = headerByte[0]; bool hasExtendedHeader = NodePacketTypeExtensions.HasExtendedHeader(rawType); - NodePacketType packetType = hasExtendedHeader ? NodePacketTypeExtensions.GetNodePacketType(rawType) : (NodePacketType)rawType; + NodePacketType packetType = hasExtendedHeader + ? NodePacketTypeExtensions.GetNodePacketType(rawType) + : (NodePacketType)rawType; byte parentVersion = 0; if (hasExtendedHeader) diff --git a/src/Shared/INodePacket.cs b/src/Shared/INodePacket.cs index 690420ba214..c49c228d511 100644 --- a/src/Shared/INodePacket.cs +++ b/src/Shared/INodePacket.cs @@ -15,6 +15,12 @@ namespace Microsoft.Build.BackEnd /// Enumeration of all of the packet types used for communication. /// Uses lower 6 bits for packet type (0-63), upper 2 bits reserved for flags. /// + /// + /// Several of these values must be kept in sync with MSBuildTaskHost's NodePacketType. + /// The values shared with MSBuildTaskHost are , + /// , , , + /// , and ."/>. + /// internal enum NodePacketType : byte { // Mask for extracting packet type (lower 6 bits) From b367f7c378df262a14e6beb2fe0092307be7e3ec Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 5 Feb 2026 10:55:28 -0800 Subject: [PATCH 099/136] MSBuildTaskHost: Clean up InterningBinaryReader - File-scoped namespaces - Remove uncalled members - Enable nullability --- .../BackEnd/InterningBinaryReader.cs | 348 ++++++------------ 1 file changed, 116 insertions(+), 232 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/InterningBinaryReader.cs b/src/MSBuildTaskHost/BackEnd/InterningBinaryReader.cs index 3e03e92d26b..96356caf896 100644 --- a/src/MSBuildTaskHost/BackEnd/InterningBinaryReader.cs +++ b/src/MSBuildTaskHost/BackEnd/InterningBinaryReader.cs @@ -9,283 +9,167 @@ using Microsoft.Build.TaskHost.Utilities; using Microsoft.NET.StringTools; -#nullable disable +namespace Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.TaskHost.BackEnd +/// +/// Replacement for BinaryReader which attempts to intern the strings read by ReadString. +/// +internal sealed class InterningBinaryReader : BinaryReader { /// - /// Replacement for BinaryReader which attempts to intern the strings read by ReadString. + /// The maximum size, in bytes, to read at once. /// - internal class InterningBinaryReader : BinaryReader - { - /// - /// The maximum size, in bytes, to read at once. - /// #if DEBUG - private const int MaxCharsBuffer = 10; + private const int MaxCharsBuffer = 10; #else - private const int MaxCharsBuffer = 20000; + private const int MaxCharsBuffer = 20000; #endif - /// - /// A cache of recently used buffers. This is a pool of size 1 to avoid allocating moderately sized - /// objects repeatedly. Used in scenarios that don't have a good context to attach - /// a shared buffer to. - /// - private static Buffer s_bufferPool; + /// + /// Shared buffer saves allocating these arrays many times. + /// + private readonly Buffer _buffer; - /// - /// Shared buffer saves allocating these arrays many times. - /// - private Buffer _buffer; + /// + /// The decoder used to translate from UTF8 (or whatever). + /// + private readonly Decoder _decoder; - /// - /// True if is owned by this instance, false if it was passed by the caller. - /// - private bool _isPrivateBuffer; + private InterningBinaryReader(Stream input, Buffer buffer) + : base(input, Encoding.UTF8) + { + if (input == null) + { + throw new InvalidOperationException(); + } - /// - /// The decoder used to translate from UTF8 (or whatever). - /// - private Decoder _decoder; + _buffer = buffer; + _decoder = Encoding.UTF8.GetDecoder(); + } - /// - /// Comment about constructing. - /// - private InterningBinaryReader(Stream input, Buffer buffer, bool isPrivateBuffer) - : base(input, Encoding.UTF8) + /// + /// Read a string while checking the string precursor for intern opportunities. + /// Taken from ndp\clr\src\bcl\system\io\binaryreader.cs-ReadString(). + /// + public override string ReadString() + { + char[]? resultBuffer = null; + try { - if (input == null) + int currPos = 0; + int n = 0; + int stringLength; + int readLength; + int charsRead = 0; + + // Length of the string in bytes, not chars + stringLength = Read7BitEncodedInt(); + if (stringLength < 0) { - throw new InvalidOperationException(); + throw new IOException(); } - _buffer = buffer; - _isPrivateBuffer = isPrivateBuffer; - _decoder = Encoding.UTF8.GetDecoder(); - } - - /// - /// Read a string while checking the string precursor for intern opportunities. - /// Taken from ndp\clr\src\bcl\system\io\binaryreader.cs-ReadString() - /// - public override String ReadString() - { - char[] resultBuffer = null; - try + if (stringLength == 0) { - MemoryStream memoryStream = this.BaseStream as MemoryStream; + return string.Empty; + } - int currPos = 0; - int n = 0; - int stringLength; - int readLength; - int charsRead = 0; + char[] charBuffer = _buffer.CharBuffer; + do + { + readLength = ((stringLength - currPos) > MaxCharsBuffer) ? MaxCharsBuffer : (stringLength - currPos); - // Length of the string in bytes, not chars - stringLength = Read7BitEncodedInt(); - if (stringLength < 0) - { - throw new IOException(); - } + byte[]? rawBuffer = null; + int rawPosition = 0; - if (stringLength == 0) + if (BaseStream is MemoryStream memoryStream) { - return String.Empty; - } - - char[] charBuffer = _buffer.CharBuffer; - do - { - readLength = ((stringLength - currPos) > MaxCharsBuffer) ? MaxCharsBuffer : (stringLength - currPos); - - byte[] rawBuffer = null; - int rawPosition = 0; - - if (memoryStream != null) + // Optimization: we can avoid reading into a byte buffer + // and instead read directly from the memorystream's backing buffer + rawBuffer = memoryStream.GetBuffer(); + rawPosition = (int)memoryStream.Position; + int length = (int)memoryStream.Length; + n = (rawPosition + readLength) < length ? readLength : length - rawPosition; + + // Attempt to track down an intermittent failure -- n should not ever be negative, but + // we're occasionally seeing it when we do the decoder.GetChars below -- by providing + // a bit more information when we do hit the error, in the place where (by code inspection) + // the actual error seems most likely to be occurring. + if (n < 0) { - // Optimization: we can avoid reading into a byte buffer - // and instead read directly from the memorystream's backing buffer - rawBuffer = memoryStream.GetBuffer(); - rawPosition = (int)memoryStream.Position; - int length = (int)memoryStream.Length; - n = (rawPosition + readLength) < length ? readLength : length - rawPosition; - - // Attempt to track down an intermittent failure -- n should not ever be negative, but - // we're occasionally seeing it when we do the decoder.GetChars below -- by providing - // a bit more information when we do hit the error, in the place where (by code inspection) - // the actual error seems most likely to be occurring. - if (n < 0) - { - ErrorUtilities.ThrowInternalError($"From calculating based on the memorystream, about to read n = {n}. length = {length}, rawPosition = {rawPosition}, readLength = {readLength}, stringLength = {stringLength}, currPos = {currPos}."); - } - - memoryStream.Seek(n, SeekOrigin.Current); + ErrorUtilities.ThrowInternalError($"From calculating based on the memorystream, about to read n = {n}. length = {length}, rawPosition = {rawPosition}, readLength = {readLength}, stringLength = {stringLength}, currPos = {currPos}."); } - if (rawBuffer == null) - { - rawBuffer = _buffer.ByteBuffer; - rawPosition = 0; - n = BaseStream.Read(rawBuffer, 0, readLength); + memoryStream.Seek(n, SeekOrigin.Current); + } - // See above explanation -- the OutOfRange exception may also be coming from our setting of n here ... - if (n < 0) - { - ErrorUtilities.ThrowInternalError($"From getting the length out of BaseStream.Read directly, about to read n = {n}. readLength = {readLength}, stringLength = {stringLength}, currPos = {currPos}"); - } - } + if (rawBuffer == null) + { + rawBuffer = _buffer.ByteBuffer; + rawPosition = 0; + n = BaseStream.Read(rawBuffer, 0, readLength); - if (n == 0) + // See above explanation -- the OutOfRange exception may also be coming from our setting of n here ... + if (n < 0) { - throw new EndOfStreamException(); + ErrorUtilities.ThrowInternalError($"From getting the length out of BaseStream.Read directly, about to read n = {n}. readLength = {readLength}, stringLength = {stringLength}, currPos = {currPos}"); } + } - if (currPos == 0 && n == stringLength) - { - charsRead = _decoder.GetChars(rawBuffer, rawPosition, n, charBuffer, 0); - return Strings.WeakIntern(charBuffer.AsSpan(0, charsRead)); - } - // Since NET35 is only used in rare TaskHost processes, we decided to leave it as-is. - resultBuffer ??= new char[stringLength]; // Actual string length in chars may be smaller. - charsRead += _decoder.GetChars(rawBuffer, rawPosition, n, resultBuffer, charsRead); + if (n == 0) + { + throw new EndOfStreamException(); + } - currPos += n; + if (currPos == 0 && n == stringLength) + { + charsRead = _decoder.GetChars(rawBuffer, rawPosition, n, charBuffer, 0); + return Strings.WeakIntern(charBuffer.AsSpan(0, charsRead)); } - while (currPos < stringLength); - var retval = Strings.WeakIntern(resultBuffer.AsSpan(0, charsRead)); + resultBuffer ??= new char[stringLength]; // Actual string length in chars may be smaller. + charsRead += _decoder.GetChars(rawBuffer, rawPosition, n, resultBuffer, charsRead); - return retval; + currPos += n; } - catch (Exception e) - { - Debug.Assert(false, e.ToString()); - throw; - } - } + while (currPos < stringLength); - /// - /// A shared buffer to avoid extra allocations in InterningBinaryReader. - /// - /// - /// The caller is responsible for managing the lifetime of the returned buffer and for passing it to . - /// - internal static BinaryReaderFactory CreateSharedBuffer() - { - return new Buffer(); + return Strings.WeakIntern(resultBuffer.AsSpan(0, charsRead)); } - - /// - /// A placeholder instructing InterningBinaryReader to use pooled buffer (to avoid extra allocations). - /// - /// - /// Lifetime of the pooled buffer is managed by InterningBinaryReader (tied to BinaryReader lifetime wrapping the buffer) - /// - internal static BinaryReaderFactory PoolingBuffer => NullBuffer.Instance; - - /// - /// Gets a buffer from the pool or creates a new one. - /// - /// The . Should be returned to the pool after we're done with it. - private static Buffer GetPooledBuffer() + catch (Exception e) { - Buffer buffer = Interlocked.Exchange(ref s_bufferPool, null); - if (buffer != null) - { - return buffer; - } - return new Buffer(); + Debug.Fail(e.ToString()); + throw; } + } - #region IDisposable pattern + /// + /// A shared buffer to avoid extra allocations in InterningBinaryReader. + /// + /// + /// The caller is responsible for managing the lifetime of the returned buffer and for passing it to . + /// + internal static BinaryReaderFactory CreateSharedBuffer() + => new Buffer(); + /// + /// Holds the preallocated buffer. + /// + private sealed class Buffer : BinaryReaderFactory + { /// - /// Returns our buffer to the pool if we were not passed one by the caller. + /// Gets the char buffer. /// - protected override void Dispose(bool disposing) - { - if (_isPrivateBuffer) - { - // If we created this buffer then try to return it to the pool. If s_bufferPool is non-null we leave it alone, - // the idea being that it's more likely to have lived longer than our buffer. - Interlocked.CompareExchange(ref s_bufferPool, _buffer, null); - } - base.Dispose(disposing); - } - - #endregion + internal char[] CharBuffer + => field ??= new char[MaxCharsBuffer]; /// - /// Create a BinaryReader. It will either be an interning reader or standard binary reader - /// depending on whether the interning reader is possible given the buffer and stream. + /// Gets the byte buffer. /// - private static BinaryReader Create(Stream stream, BinaryReaderFactory sharedBuffer) - { - Buffer buffer = (Buffer)sharedBuffer; - if (buffer != null) - { - return new InterningBinaryReader(stream, buffer, false); - } - return new InterningBinaryReader(stream, GetPooledBuffer(), true); - } + internal byte[] ByteBuffer + => field ??= new byte[Encoding.UTF8.GetMaxByteCount(MaxCharsBuffer)]; - /// - /// Holds thepreallocated buffer. - /// - private class Buffer : BinaryReaderFactory - { - private char[] _charBuffer; - private byte[] _byteBuffer; - - /// - /// Yes, we are constructing. - /// - internal Buffer() - { - } - - /// - /// The char buffer. - /// - internal char[] CharBuffer - { - get - { - _charBuffer ??= new char[MaxCharsBuffer]; - return _charBuffer; - } - } - - /// - /// The byte buffer. - /// - internal byte[] ByteBuffer - { - get - { - _byteBuffer ??= new byte[Encoding.UTF8.GetMaxByteCount(MaxCharsBuffer)]; - return _byteBuffer; - } - } - - public override BinaryReader Create(Stream stream) - { - return InterningBinaryReader.Create(stream, this); - } - } - - private class NullBuffer : BinaryReaderFactory - { - private NullBuffer() - { } - - public static readonly BinaryReaderFactory Instance = new NullBuffer(); - - public override BinaryReader Create(Stream stream) - { - return InterningBinaryReader.Create(stream, null); - } - } + public override BinaryReader Create(Stream stream) + => new InterningBinaryReader(stream, buffer: this); } } From 118990908244cd38aa1a360f75c7aef188af6f2c Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 5 Feb 2026 11:32:43 -0800 Subject: [PATCH 100/136] MSBuildTaskHost: Clean up LogMessagePacketBase and fix bug - File-scoped namespace - Remove unused members/parameters - Expression-bodied members - Enable nullability - Fix "reader" delegate type. A bug was introduced *many* years ago to add an "int version" parameter to the delegate to capture BuildEventArgs' "CreateFromStream". However, that parameter is not present on the .NET 3.5 versions of the BuildEventArgs types. --- .../BackEnd/LogMessagePacketBase.cs | 1163 ++++++++--------- src/Shared/LogMessagePacketBase.cs | 3 + 2 files changed, 552 insertions(+), 614 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/LogMessagePacketBase.cs b/src/MSBuildTaskHost/BackEnd/LogMessagePacketBase.cs index 10586d7f673..195bf6fe771 100644 --- a/src/MSBuildTaskHost/BackEnd/LogMessagePacketBase.cs +++ b/src/MSBuildTaskHost/BackEnd/LogMessagePacketBase.cs @@ -9,719 +9,654 @@ using Microsoft.Build.TaskHost.Exceptions; using Microsoft.Build.TaskHost.Utilities; -#nullable disable - -namespace Microsoft.Build.TaskHost.BackEnd +namespace Microsoft.Build.TaskHost.BackEnd; + +// On .NET Framework 3.5, Microsoft.Build.Framework includes the following concrete event args types: +// +// - BuildErrorEventArgs +// - BuildFinishedEventArgs +// - BuildMessageEventArgs +// - BuildStartedEventArgs +// - BuildWarningEventArgs +// - ExternalProjectFinishedEventArgs +// - ExternalProjectStartedEventArgs +// - ProjectFinishedEventArgs +// - ProjectStartedEventArgs +// - TargetFinishedEventArgs +// - TargetStartedEventArgs +// - TaskCommandLineEventArgs +// - TaskFinishedEventArgs +// - TaskStartedEventArgs + +/// +/// An enumeration of all the types of BuildEventArgs that can be +/// packaged by this logMessagePacket +/// +internal enum LoggingEventType : int { - // On .NET Framework 3.5, Microsoft.Build.Framework includes the following concrete event args types: - // - // - BuildErrorEventArgs - // - BuildFinishedEventArgs - // - BuildMessageEventArgs - // - BuildStartedEventArgs - // - BuildWarningEventArgs - // - ExternalProjectFinishedEventArgs - // - ExternalProjectStartedEventArgs - // - ProjectFinishedEventArgs - // - ProjectStartedEventArgs - // - TargetFinishedEventArgs - // - TargetStartedEventArgs - // - TaskCommandLineEventArgs - // - TaskFinishedEventArgs - // - TaskStartedEventArgs - - #region Enumerations - /// - /// An enumeration of all the types of BuildEventArgs that can be - /// packaged by this logMessagePacket - /// - internal enum LoggingEventType : int - { - /// - /// An invalid eventId, used during initialization of a . - /// - Invalid = -1, - - /// - /// Event is a CustomEventArgs. - /// - CustomEvent = 0, - - /// - /// Event is a . - /// - BuildErrorEvent = 1, - - /// - /// Event is a . - /// - BuildFinishedEvent = 2, - - /// - /// Event is a . - /// - BuildMessageEvent = 3, - - /// - /// Event is a . - /// - BuildStartedEvent = 4, - - /// - /// Event is a . - /// - BuildWarningEvent = 5, - - /// - /// Event is a . - /// - ProjectFinishedEvent = 6, - - /// - /// Event is a . - /// - ProjectStartedEvent = 7, - - /// - /// Event is a . - /// - TargetStartedEvent = 8, - - /// - /// Event is a . - /// - TargetFinishedEvent = 9, - - /// - /// Event is a . - /// - TaskStartedEvent = 10, - - /// - /// Event is a . - /// - TaskFinishedEvent = 11, - - /// - /// Event is a . - /// - TaskCommandLineEvent = 12, - - /// - /// Event is . - /// - ExternalProjectStartedEvent = 22, - - /// - /// Event is . - /// - ExternalProjectFinishedEvent = 23, - } - #endregion - /// - /// A packet to encapsulate a BuildEventArg logging message. - /// Contents: - /// Build Event Type - /// Build Event Args + /// An invalid eventId, used during initialization of a . /// - internal class LogMessagePacketBase : INodePacket - { - /// - /// The packet version, which is based on the CLR version. Cached because querying Environment.Version each time becomes an allocation bottleneck. - /// - private static readonly int s_defaultPacketVersion = (Environment.Version.Major * 10) + Environment.Version.Minor; - - /// - /// Dictionary of methods used to read BuildEventArgs. - /// - private static readonly Dictionary s_readMethodCache = new Dictionary(); - - /// - /// Dictionary of methods used to write BuildEventArgs. - /// - private static readonly Dictionary s_writeMethodCache = new Dictionary(); - - #region Data - - /// - /// The event type of the buildEventArg based on the - /// LoggingEventType enumeration - /// - private LoggingEventType _eventType = LoggingEventType.Invalid; - - /// - /// The buildEventArg which is encapsulated by the packet - /// - private BuildEventArgs _buildEvent; - - /// - /// The sink id - /// - private int _sinkId; - - #endregion - - #region Constructors - - /// - /// Encapsulates the buildEventArg in this packet. - /// - internal LogMessagePacketBase(KeyValuePair? nodeBuildEvent) - { - ErrorUtilities.VerifyThrow(nodeBuildEvent != null, "nodeBuildEvent was null"); - _buildEvent = nodeBuildEvent.Value.Value; - _sinkId = nodeBuildEvent.Value.Key; - _eventType = GetLoggingEventId(_buildEvent); - } - - #endregion + Invalid = -1, - #region Delegates + /// + /// Event is a CustomEventArgs. + /// + CustomEvent = 0, - /// - /// Delegate representing a method on the BuildEventArgs classes used to write to a stream. - /// - private delegate void ArgsWriterDelegate(BinaryWriter writer); + /// + /// Event is a . + /// + BuildErrorEvent = 1, - /// - /// Delegate representing a method on the BuildEventArgs classes used to read from a stream. - /// - private delegate void ArgsReaderDelegate(BinaryReader reader, int version); + /// + /// Event is a . + /// + BuildFinishedEvent = 2, - #endregion + /// + /// Event is a . + /// + BuildMessageEvent = 3, - #region Properties + /// + /// Event is a . + /// + BuildStartedEvent = 4, - /// - /// The nodePacket Type, in this case the packet is a Logging Message - /// - public NodePacketType Type - { - get { return NodePacketType.LogMessage; } - } - #endregion + /// + /// Event is a . + /// + BuildWarningEvent = 5, - #region INodePacket Methods + /// + /// Event is a . + /// + ProjectFinishedEvent = 6, - /// - /// Reads/writes this packet - /// - public void Translate(ITranslator translator) - { - translator.TranslateEnum(ref _eventType, (int)_eventType); - translator.Translate(ref _sinkId); - if (translator.Mode == TranslationDirection.ReadFromStream) - { - ReadFromStream(translator); - } - else - { - WriteToStream(translator); - } - } + /// + /// Event is a . + /// + ProjectStartedEvent = 7, - #endregion + /// + /// Event is a . + /// + TargetStartedEvent = 8, - /// - /// Writes the logging packet to the translator. - /// - internal void WriteToStream(ITranslator translator) - { - ErrorUtilities.VerifyThrow(_eventType != LoggingEventType.CustomEvent, "_eventType should not be a custom event"); + /// + /// Event is a . + /// + TargetFinishedEvent = 9, - MethodInfo methodInfo = null; - lock (s_writeMethodCache) - { - if (!s_writeMethodCache.TryGetValue(_eventType, out methodInfo)) - { - Type eventDerivedType = _buildEvent.GetType(); - methodInfo = eventDerivedType.GetMethod("WriteToStream", BindingFlags.NonPublic | BindingFlags.Instance); - s_writeMethodCache.Add(_eventType, methodInfo); - } - } + /// + /// Event is a . + /// + TaskStartedEvent = 10, - int packetVersion = s_defaultPacketVersion; + /// + /// Event is a . + /// + TaskFinishedEvent = 11, - // Make sure the other side knows what sort of serialization is coming - translator.Translate(ref packetVersion); + /// + /// Event is a . + /// + TaskCommandLineEvent = 12, - bool eventCanSerializeItself = EventCanSerializeItself(_eventType, methodInfo); + /// + /// Event is . + /// + ExternalProjectStartedEvent = 22, - translator.Translate(ref eventCanSerializeItself); + /// + /// Event is . + /// + ExternalProjectFinishedEvent = 23, +} - if (eventCanSerializeItself) - { - // 3.5 or later -- we have custom serialization methods, so let's use them. - ArgsWriterDelegate writerMethod = (ArgsWriterDelegate)CreateDelegateRobust(typeof(ArgsWriterDelegate), _buildEvent, methodInfo); - writerMethod(translator.Writer); +/// +/// A packet to encapsulate a BuildEventArg logging message. +/// Contents: +/// Build Event Type +/// Build Event Args +/// +internal sealed class LogMessagePacketBase : INodePacket +{ + private const string WriteToStreamMethodName = "WriteToStream"; + private const string CreateFromStreamMethodName = "CreateFromStream"; - TranslateAdditionalProperties(translator, _eventType, _buildEvent); - } - else - { - WriteEventToStream(_buildEvent, _eventType, translator); - } - } + private const BindingFlags NonPublicInstance = BindingFlags.NonPublic | BindingFlags.Instance; - /// - /// Reads the logging packet from the translator. - /// - internal void ReadFromStream(ITranslator translator) - { - ErrorUtilities.VerifyThrow(_eventType != LoggingEventType.CustomEvent, "_eventType should not be a custom event"); + /// + /// The packet version, which is based on the CLR version. Cached because querying Environment.Version each time becomes an allocation bottleneck. + /// + private static readonly int s_defaultPacketVersion = (Environment.Version.Major * 10) + Environment.Version.Minor; - _buildEvent = GetBuildEventArgFromId(); + /// + /// Dictionary of methods used to read BuildEventArgs. + /// + private static readonly Dictionary s_readMethodCache = []; - // The other side is telling us whether the event knows how to log itself, or whether we're going to have - // to do it manually - int packetVersion = s_defaultPacketVersion; - translator.Translate(ref packetVersion); + /// + /// Dictionary of methods used to write BuildEventArgs. + /// + private static readonly Dictionary s_writeMethodCache = []; - bool eventCanSerializeItself = true; - translator.Translate(ref eventCanSerializeItself); + /// + /// The event type of the buildEventArg based on the + /// LoggingEventType enumeration + /// + private LoggingEventType _eventType = LoggingEventType.Invalid; - if (eventCanSerializeItself) - { + /// + /// The buildEventArg which is encapsulated by the packet. + /// + private BuildEventArgs? _buildEvent; - MethodInfo methodInfo = null; - lock (s_readMethodCache) - { - if (!s_readMethodCache.TryGetValue(_eventType, out methodInfo)) - { - Type eventDerivedType = _buildEvent.GetType(); - methodInfo = eventDerivedType.GetMethod("CreateFromStream", BindingFlags.NonPublic | BindingFlags.Instance); - s_readMethodCache.Add(_eventType, methodInfo); - } - } + /// + /// The sink id + /// + private int _sinkId; - ArgsReaderDelegate readerMethod = (ArgsReaderDelegate)CreateDelegateRobust(typeof(ArgsReaderDelegate), _buildEvent, methodInfo); + /// + /// Encapsulates the buildEventArg in this packet. + /// + public LogMessagePacketBase(KeyValuePair? nodeBuildEvent) + { + ErrorUtilities.VerifyThrow(nodeBuildEvent != null, "nodeBuildEvent was null"); + _buildEvent = nodeBuildEvent.Value.Value; + _sinkId = nodeBuildEvent.Value.Key; + _eventType = GetLoggingEventId(_buildEvent); + } - readerMethod(translator.Reader, packetVersion); + /// + /// Delegate representing a method on the BuildEventArgs classes used to write to a stream. + /// + private delegate void WriteToStreamMethod(BinaryWriter writer); - TranslateAdditionalProperties(translator, _eventType, _buildEvent); - } - else - { - _buildEvent = ReadEventFromStream(_eventType, translator); - ErrorUtilities.VerifyThrow(_buildEvent is not null, "Not Supported LoggingEventType {0}", _eventType.ToString()); - } + /// + /// Delegate representing a method on the BuildEventArgs classes used to read from a stream. + /// + private delegate void CreateFromStreamMethod(BinaryReader reader); - _eventType = GetLoggingEventId(_buildEvent); - } + /// + /// Gets the nodePacket Type, in this case the packet is a Logging Message. + /// + public NodePacketType Type => NodePacketType.LogMessage; - /// - /// Returns whether to use the event's own serialization method, if found. - /// If false, defers to overridable implementations in and - /// . - /// - protected virtual bool EventCanSerializeItself(LoggingEventType eventType, MethodInfo methodInfo) - => methodInfo != null; - - /// - /// Translates additional properties that are not handled by the default serialization. - /// - protected virtual void TranslateAdditionalProperties(ITranslator translator, LoggingEventType eventType, BuildEventArgs buildEvent) + /// + /// Reads/writes this packet. + /// + public void Translate(ITranslator translator) + { + translator.TranslateEnum(ref _eventType, (int)_eventType); + translator.Translate(ref _sinkId); + if (translator.Mode == TranslationDirection.ReadFromStream) { + ReadFromStream(translator); } - - #region Private Methods - - /// - /// Wrapper for Delegate.CreateDelegate with retries. - /// - /// - /// TODO: Investigate if it would be possible to use one of the overrides of CreateDelegate - /// that doesn't force the delegate to be closed over its first argument, so that we can - /// only create the delegate once per event type and cache it. - /// - private static Delegate CreateDelegateRobust(Type type, object firstArgument, MethodInfo methodInfo) + else { - Delegate delegateMethod = null; - - for (int i = 0; delegateMethod == null && i < 5; i++) - { - try - { - delegateMethod = Delegate.CreateDelegate(type, firstArgument, methodInfo); - } - catch (FileLoadException) when (i < 5) - { - // Sometimes, in 64-bit processes, the fusion load of Microsoft.Build.Framework.dll - // spontaneously fails when trying to bind to the delegate. However, it seems to - // not repeat on additional tries -- so we'll try again a few times. However, if - // it keeps happening, it's probably a real problem, so we want to go ahead and - // throw to let the user know what's up. - } - } - - return delegateMethod; + WriteToStream(translator); } + } - /// - /// Takes in a id (LoggingEventType as an int) and creates the correct specific logging class - /// - private BuildEventArgs GetBuildEventArgFromId() - { - return _eventType switch - { - LoggingEventType.BuildErrorEvent => new BuildErrorEventArgs(null, null, null, -1, -1, -1, -1, null, null, null), - LoggingEventType.BuildFinishedEvent => new BuildFinishedEventArgs(null, null, false), - LoggingEventType.BuildMessageEvent => new BuildMessageEventArgs(null, null, null, MessageImportance.Normal), - LoggingEventType.BuildStartedEvent => new BuildStartedEventArgs(null, null), - LoggingEventType.BuildWarningEvent => new BuildWarningEventArgs(null, null, null, -1, -1, -1, -1, null, null, null), - LoggingEventType.ProjectFinishedEvent => new ProjectFinishedEventArgs(null, null, null, false), - LoggingEventType.ProjectStartedEvent => new ProjectStartedEventArgs(null, null, null, null, null, null), - LoggingEventType.TargetStartedEvent => new TargetStartedEventArgs(null, null, null, null, null), - LoggingEventType.TargetFinishedEvent => new TargetFinishedEventArgs(null, null, null, null, null, false), - LoggingEventType.TaskStartedEvent => new TaskStartedEventArgs(null, null, null, null, null), - LoggingEventType.TaskFinishedEvent => new TaskFinishedEventArgs(null, null, null, null, null, false), - LoggingEventType.TaskCommandLineEvent => new TaskCommandLineEventArgs(null, null, MessageImportance.Normal), - LoggingEventType.ExternalProjectStartedEvent => new ExternalProjectStartedEventArgs(null, null, null, null, null), - LoggingEventType.ExternalProjectFinishedEvent => new ExternalProjectFinishedEventArgs(null, null, null, null, false), - - _ => throw new InternalErrorException("Should not get to the default of GetBuildEventArgFromId ID: " + _eventType) - }; - } + /// + /// Writes the logging packet to the translator. + /// + private void WriteToStream(ITranslator translator) + { + ErrorUtilities.VerifyThrow(_eventType != LoggingEventType.CustomEvent, "_eventType should not be a custom event"); - /// - /// Based on the type of the BuildEventArg to be wrapped - /// generate an Id which identifies which concrete type the - /// BuildEventArg is. - /// - /// Argument to get the type Id for - /// An enumeration entry which represents the type - private LoggingEventType GetLoggingEventId(BuildEventArgs eventArg) + MethodInfo? methodInfo = null; + lock (s_writeMethodCache) { - Type eventType = eventArg.GetType(); - if (eventType == typeof(BuildMessageEventArgs)) - { - return LoggingEventType.BuildMessageEvent; - } - else if (eventType == typeof(TaskCommandLineEventArgs)) - { - return LoggingEventType.TaskCommandLineEvent; - } - else if (eventType == typeof(ProjectFinishedEventArgs)) + if (!s_writeMethodCache.TryGetValue(_eventType, out methodInfo)) { - return LoggingEventType.ProjectFinishedEvent; - } - else if (eventType == typeof(ProjectStartedEventArgs)) - { - return LoggingEventType.ProjectStartedEvent; - } - else if (eventType == typeof(ExternalProjectStartedEventArgs)) - { - return LoggingEventType.ExternalProjectStartedEvent; - } - else if (eventType == typeof(ExternalProjectFinishedEventArgs)) - { - return LoggingEventType.ExternalProjectFinishedEvent; - } - else if (eventType == typeof(TargetStartedEventArgs)) - { - return LoggingEventType.TargetStartedEvent; - } - else if (eventType == typeof(TargetFinishedEventArgs)) - { - return LoggingEventType.TargetFinishedEvent; - } - else if (eventType == typeof(TaskStartedEventArgs)) - { - return LoggingEventType.TaskStartedEvent; - } - else if (eventType == typeof(TaskFinishedEventArgs)) - { - return LoggingEventType.TaskFinishedEvent; - } - else if (eventType == typeof(BuildFinishedEventArgs)) - { - return LoggingEventType.BuildFinishedEvent; - } - else if (eventType == typeof(BuildStartedEventArgs)) - { - return LoggingEventType.BuildStartedEvent; - } - else if (eventType == typeof(BuildWarningEventArgs)) - { - return LoggingEventType.BuildWarningEvent; - } - else if (eventType == typeof(BuildErrorEventArgs)) - { - return LoggingEventType.BuildErrorEvent; - } - else - { - return LoggingEventType.CustomEvent; + Type eventDerivedType = _buildEvent!.GetType(); + methodInfo = eventDerivedType.GetMethod(WriteToStreamMethodName, NonPublicInstance); + s_writeMethodCache.Add(_eventType, methodInfo); } } - /// - /// Given a build event that is presumed to be 2.0 (due to its lack of a "WriteToStream" method) and its - /// LoggingEventType, serialize that event to the stream. - /// - /// - /// Override to customize serialization per-assembly without relying on compile directives. - /// - protected virtual void WriteEventToStream(BuildEventArgs buildEvent, LoggingEventType eventType, ITranslator translator) - { - string message = buildEvent.Message; - string helpKeyword = buildEvent.HelpKeyword; - string senderName = buildEvent.SenderName; + int packetVersion = s_defaultPacketVersion; - translator.Translate(ref message); - translator.Translate(ref helpKeyword); - translator.Translate(ref senderName); + // Make sure the other side knows what sort of serialization is coming + translator.Translate(ref packetVersion); - // It is essential that you translate in the same order during writing and reading - switch (eventType) - { - case LoggingEventType.BuildMessageEvent: - WriteBuildMessageEventToStream((BuildMessageEventArgs)buildEvent, translator); - break; - case LoggingEventType.TaskCommandLineEvent: - WriteTaskCommandLineEventToStream((TaskCommandLineEventArgs)buildEvent, translator); - break; - case LoggingEventType.BuildErrorEvent: - WriteBuildErrorEventToStream((BuildErrorEventArgs)buildEvent, translator); - break; - case LoggingEventType.BuildWarningEvent: - WriteBuildWarningEventToStream((BuildWarningEventArgs)buildEvent, translator); - break; - default: - ErrorUtilities.ThrowInternalError($"Not Supported LoggingEventType {eventType}"); - break; - } - } + // Note: This should always be true in MSBuildTaskHost (and methodInfo should never be null). + // The .NET 3.5 event args have correct "WriteToStream" methods. + bool eventCanSerializeItself = methodInfo != null; - #region Writes to Stream + translator.Translate(ref eventCanSerializeItself); - /// - /// Write Build Warning Log message into the translator - /// - private void WriteBuildWarningEventToStream(BuildWarningEventArgs buildWarningEventArgs, ITranslator translator) + if (eventCanSerializeItself) + { + WriteToStreamMethod writerMethod = CreateDelegateRobust(_buildEvent!, methodInfo!); + writerMethod(translator.Writer); + } + else { - string code = buildWarningEventArgs.Code; - translator.Translate(ref code); + WriteEventToStream(_buildEvent!, _eventType, translator); + } + } + + /// + /// Reads the logging packet from the translator. + /// + private void ReadFromStream(ITranslator translator) + { + ErrorUtilities.VerifyThrow(_eventType != LoggingEventType.CustomEvent, "_eventType should not be a custom event"); - int columnNumber = buildWarningEventArgs.ColumnNumber; - translator.Translate(ref columnNumber); + _buildEvent = GetBuildEventArgFromId(); - int endColumnNumber = buildWarningEventArgs.EndColumnNumber; - translator.Translate(ref endColumnNumber); + // The other side is telling us whether the event knows how to log itself, or whether we're going to have + // to do it manually + int packetVersion = s_defaultPacketVersion; + translator.Translate(ref packetVersion); - int endLineNumber = buildWarningEventArgs.EndLineNumber; - translator.Translate(ref endLineNumber); + bool eventCanSerializeItself = true; + translator.Translate(ref eventCanSerializeItself); - string file = buildWarningEventArgs.File; - translator.Translate(ref file); + // Note: This should always be true in MSBuildTaskHost (and methodInfo should never be null). + // The .NET 3.5 event args have correct "CreateFromStream" methods. + if (eventCanSerializeItself) + { + MethodInfo? methodInfo = null; + lock (s_readMethodCache) + { + if (!s_readMethodCache.TryGetValue(_eventType, out methodInfo)) + { + Type eventDerivedType = _buildEvent.GetType(); + methodInfo = eventDerivedType.GetMethod(CreateFromStreamMethodName, NonPublicInstance); + s_readMethodCache.Add(_eventType, methodInfo); + } + } - int lineNumber = buildWarningEventArgs.LineNumber; - translator.Translate(ref lineNumber); + CreateFromStreamMethod readerMethod = CreateDelegateRobust(_buildEvent, methodInfo); - string subCategory = buildWarningEventArgs.Subcategory; - translator.Translate(ref subCategory); + readerMethod(translator.Reader); } - - /// - /// Write a Build Error message into the translator - /// - private void WriteBuildErrorEventToStream(BuildErrorEventArgs buildErrorEventArgs, ITranslator translator) + else { - string code = buildErrorEventArgs.Code; - translator.Translate(ref code); + _buildEvent = ReadEventFromStream(_eventType, translator); + ErrorUtilities.VerifyThrow(_buildEvent is not null, $"Unsupported LoggingEventType {_eventType}"); + } - int columnNumber = buildErrorEventArgs.ColumnNumber; - translator.Translate(ref columnNumber); + _eventType = GetLoggingEventId(_buildEvent); + } - int endColumnNumber = buildErrorEventArgs.EndColumnNumber; - translator.Translate(ref endColumnNumber); + /// + /// Wrapper for Delegate.CreateDelegate with retries. + /// + private static T CreateDelegateRobust(object firstArgument, MethodInfo methodInfo) + where T : class, Delegate + { + Type type = typeof(T); - int endLineNumber = buildErrorEventArgs.EndLineNumber; - translator.Translate(ref endLineNumber); + for (int i = 0; i < 5; i++) + { + try + { + return (T)Delegate.CreateDelegate(type, firstArgument, methodInfo); + } + catch (FileLoadException) + { + // Sometimes, in 64-bit processes, the fusion load of Microsoft.Build.Framework.dll + // spontaneously fails when trying to bind to the delegate. However, it seems to + // not repeat on additional tries -- so we'll try again a few times. However, if + // it keeps happening, it's probably a real problem, so we want to go ahead and + // throw to let the user know what's up. + } + } - string file = buildErrorEventArgs.File; - translator.Translate(ref file); + ErrorUtilities.ThrowInternalErrorUnreachable(); + return null; + } - int lineNumber = buildErrorEventArgs.LineNumber; - translator.Translate(ref lineNumber); + /// + /// Takes in a id (LoggingEventType as an int) and creates the correct specific logging class + /// + private BuildEventArgs GetBuildEventArgFromId() => _eventType switch + { + LoggingEventType.BuildErrorEvent => new BuildErrorEventArgs(null, null, null, -1, -1, -1, -1, null, null, null), + LoggingEventType.BuildFinishedEvent => new BuildFinishedEventArgs(null, null, false), + LoggingEventType.BuildMessageEvent => new BuildMessageEventArgs(null, null, null, MessageImportance.Normal), + LoggingEventType.BuildStartedEvent => new BuildStartedEventArgs(null, null), + LoggingEventType.BuildWarningEvent => new BuildWarningEventArgs(null, null, null, -1, -1, -1, -1, null, null, null), + LoggingEventType.ProjectFinishedEvent => new ProjectFinishedEventArgs(null, null, null, false), + LoggingEventType.ProjectStartedEvent => new ProjectStartedEventArgs(null, null, null, null, null, null), + LoggingEventType.TargetStartedEvent => new TargetStartedEventArgs(null, null, null, null, null), + LoggingEventType.TargetFinishedEvent => new TargetFinishedEventArgs(null, null, null, null, null, false), + LoggingEventType.TaskStartedEvent => new TaskStartedEventArgs(null, null, null, null, null), + LoggingEventType.TaskFinishedEvent => new TaskFinishedEventArgs(null, null, null, null, null, false), + LoggingEventType.TaskCommandLineEvent => new TaskCommandLineEventArgs(null, null, MessageImportance.Normal), + LoggingEventType.ExternalProjectStartedEvent => new ExternalProjectStartedEventArgs(null, null, null, null, null), + LoggingEventType.ExternalProjectFinishedEvent => new ExternalProjectFinishedEventArgs(null, null, null, null, false), + + _ => throw new InternalErrorException($"Should not get to the default of GetBuildEventArgFromId ID: {_eventType}") + }; - string subCategory = buildErrorEventArgs.Subcategory; - translator.Translate(ref subCategory); + /// + /// Based on the type of the BuildEventArg to be wrapped + /// generate an Id which identifies which concrete type the + /// BuildEventArg is. + /// + /// Argument to get the type Id for + /// An enumeration entry which represents the type + private LoggingEventType GetLoggingEventId(BuildEventArgs eventArg) + { + Type eventType = eventArg.GetType(); + if (eventType == typeof(BuildMessageEventArgs)) + { + return LoggingEventType.BuildMessageEvent; } - - /// - /// Write Task Command Line log message into the translator - /// - private void WriteTaskCommandLineEventToStream(TaskCommandLineEventArgs taskCommandLineEventArgs, ITranslator translator) + else if (eventType == typeof(TaskCommandLineEventArgs)) { - MessageImportance importance = taskCommandLineEventArgs.Importance; - translator.TranslateEnum(ref importance, (int)importance); - - string commandLine = taskCommandLineEventArgs.CommandLine; - translator.Translate(ref commandLine); - - string taskName = taskCommandLineEventArgs.TaskName; - translator.Translate(ref taskName); + return LoggingEventType.TaskCommandLineEvent; } - - /// - /// Write a "standard" Message Log the translator - /// - private void WriteBuildMessageEventToStream(BuildMessageEventArgs buildMessageEventArgs, ITranslator translator) + else if (eventType == typeof(ProjectFinishedEventArgs)) + { + return LoggingEventType.ProjectFinishedEvent; + } + else if (eventType == typeof(ProjectStartedEventArgs)) + { + return LoggingEventType.ProjectStartedEvent; + } + else if (eventType == typeof(ExternalProjectStartedEventArgs)) + { + return LoggingEventType.ExternalProjectStartedEvent; + } + else if (eventType == typeof(ExternalProjectFinishedEventArgs)) + { + return LoggingEventType.ExternalProjectFinishedEvent; + } + else if (eventType == typeof(TargetStartedEventArgs)) + { + return LoggingEventType.TargetStartedEvent; + } + else if (eventType == typeof(TargetFinishedEventArgs)) + { + return LoggingEventType.TargetFinishedEvent; + } + else if (eventType == typeof(TaskStartedEventArgs)) { - MessageImportance importance = buildMessageEventArgs.Importance; - translator.TranslateEnum(ref importance, (int)importance); + return LoggingEventType.TaskStartedEvent; } + else if (eventType == typeof(TaskFinishedEventArgs)) + { + return LoggingEventType.TaskFinishedEvent; + } + else if (eventType == typeof(BuildFinishedEventArgs)) + { + return LoggingEventType.BuildFinishedEvent; + } + else if (eventType == typeof(BuildStartedEventArgs)) + { + return LoggingEventType.BuildStartedEvent; + } + else if (eventType == typeof(BuildWarningEventArgs)) + { + return LoggingEventType.BuildWarningEvent; + } + else if (eventType == typeof(BuildErrorEventArgs)) + { + return LoggingEventType.BuildErrorEvent; + } + else + { + return LoggingEventType.CustomEvent; + } + } - #endregion + /// + /// Given a build event that is presumed to be 2.0 (due to its lack of a "WriteToStream" method) and its + /// LoggingEventType, serialize that event to the stream. + /// + /// + /// Override to customize serialization per-assembly without relying on compile directives. + /// + private void WriteEventToStream(BuildEventArgs buildEvent, LoggingEventType eventType, ITranslator translator) + { + string? message = buildEvent.Message; + string? helpKeyword = buildEvent.HelpKeyword; + string? senderName = buildEvent.SenderName; - #region Reads from Stream + translator.Translate(ref message); + translator.Translate(ref helpKeyword); + translator.Translate(ref senderName); - /// - /// Given a build event that is presumed to be 2.0 (due to its lack of a "ReadFromStream" method) and its - /// LoggingEventType, read that event from the stream. - /// - /// - /// Override to customize serialization per-assembly without relying on compile directives. - /// - protected virtual BuildEventArgs ReadEventFromStream(LoggingEventType eventType, ITranslator translator) + // It is essential that you translate in the same order during writing and reading + switch (eventType) { - string message = null; - string helpKeyword = null; - string senderName = null; + case LoggingEventType.BuildMessageEvent: + WriteBuildMessageEventToStream((BuildMessageEventArgs)buildEvent, translator); + break; + case LoggingEventType.TaskCommandLineEvent: + WriteTaskCommandLineEventToStream((TaskCommandLineEventArgs)buildEvent, translator); + break; + case LoggingEventType.BuildErrorEvent: + WriteBuildErrorEventToStream((BuildErrorEventArgs)buildEvent, translator); + break; + case LoggingEventType.BuildWarningEvent: + WriteBuildWarningEventToStream((BuildWarningEventArgs)buildEvent, translator); + break; + default: + ErrorUtilities.ThrowInternalError($"Not Supported LoggingEventType {eventType}"); + break; + } + } - translator.Translate(ref message); - translator.Translate(ref helpKeyword); - translator.Translate(ref senderName); + /// + /// Write Build Warning Log message into the translator + /// + private void WriteBuildWarningEventToStream(BuildWarningEventArgs buildWarningEventArgs, ITranslator translator) + { + string? code = buildWarningEventArgs.Code; + translator.Translate(ref code); - return eventType switch - { - LoggingEventType.TaskCommandLineEvent => ReadTaskCommandLineEventFromStream(translator, message, helpKeyword, senderName), - LoggingEventType.BuildErrorEvent => ReadTaskBuildErrorEventFromStream(translator, message, helpKeyword, senderName), - LoggingEventType.BuildMessageEvent => ReadBuildMessageEventFromStream(translator, message, helpKeyword, senderName), - LoggingEventType.BuildWarningEvent => ReadBuildWarningEventFromStream(translator, message, helpKeyword, senderName), - _ => null, - }; - } + int columnNumber = buildWarningEventArgs.ColumnNumber; + translator.Translate(ref columnNumber); - /// - /// Read and reconstruct a BuildWarningEventArgs from the stream - /// - private BuildWarningEventArgs ReadBuildWarningEventFromStream(ITranslator translator, string message, string helpKeyword, string senderName) - { - string code = null; - translator.Translate(ref code); + int endColumnNumber = buildWarningEventArgs.EndColumnNumber; + translator.Translate(ref endColumnNumber); - int columnNumber = -1; - translator.Translate(ref columnNumber); + int endLineNumber = buildWarningEventArgs.EndLineNumber; + translator.Translate(ref endLineNumber); - int endColumnNumber = -1; - translator.Translate(ref endColumnNumber); + string? file = buildWarningEventArgs.File; + translator.Translate(ref file); - int endLineNumber = -1; - translator.Translate(ref endLineNumber); + int lineNumber = buildWarningEventArgs.LineNumber; + translator.Translate(ref lineNumber); - string file = null; - translator.Translate(ref file); + string? subCategory = buildWarningEventArgs.Subcategory; + translator.Translate(ref subCategory); + } - int lineNumber = -1; - translator.Translate(ref lineNumber); + /// + /// Write a Build Error message into the translator + /// + private void WriteBuildErrorEventToStream(BuildErrorEventArgs buildErrorEventArgs, ITranslator translator) + { + string? code = buildErrorEventArgs.Code; + translator.Translate(ref code); - string subCategory = null; - translator.Translate(ref subCategory); + int columnNumber = buildErrorEventArgs.ColumnNumber; + translator.Translate(ref columnNumber); - BuildWarningEventArgs buildEvent = - new BuildWarningEventArgs( - subCategory, - code, - file, - lineNumber, - columnNumber, - endLineNumber, - endColumnNumber, - message, - helpKeyword, - senderName); + int endColumnNumber = buildErrorEventArgs.EndColumnNumber; + translator.Translate(ref endColumnNumber); - return buildEvent; - } + int endLineNumber = buildErrorEventArgs.EndLineNumber; + translator.Translate(ref endLineNumber); - /// - /// Read and reconstruct a BuildErrorEventArgs from the stream - /// - private BuildErrorEventArgs ReadTaskBuildErrorEventFromStream(ITranslator translator, string message, string helpKeyword, string senderName) - { - string code = null; - translator.Translate(ref code); + string? file = buildErrorEventArgs.File; + translator.Translate(ref file); - int columnNumber = -1; - translator.Translate(ref columnNumber); + int lineNumber = buildErrorEventArgs.LineNumber; + translator.Translate(ref lineNumber); - int endColumnNumber = -1; - translator.Translate(ref endColumnNumber); + string? subCategory = buildErrorEventArgs.Subcategory; + translator.Translate(ref subCategory); + } - int endLineNumber = -1; - translator.Translate(ref endLineNumber); + /// + /// Write Task Command Line log message into the translator. + /// + private void WriteTaskCommandLineEventToStream(TaskCommandLineEventArgs taskCommandLineEventArgs, ITranslator translator) + { + MessageImportance importance = taskCommandLineEventArgs.Importance; + translator.TranslateEnum(ref importance, (int)importance); - string file = null; - translator.Translate(ref file); + string? commandLine = taskCommandLineEventArgs.CommandLine; + translator.Translate(ref commandLine); - int lineNumber = -1; - translator.Translate(ref lineNumber); + string? taskName = taskCommandLineEventArgs.TaskName; + translator.Translate(ref taskName); + } - string subCategory = null; - translator.Translate(ref subCategory); + /// + /// Write a "standard" Message Log the translator + /// + private void WriteBuildMessageEventToStream(BuildMessageEventArgs buildMessageEventArgs, ITranslator translator) + { + MessageImportance importance = buildMessageEventArgs.Importance; + translator.TranslateEnum(ref importance, (int)importance); + } - BuildErrorEventArgs buildEvent = - new BuildErrorEventArgs( - subCategory, - code, - file, - lineNumber, - columnNumber, - endLineNumber, - endColumnNumber, - message, - helpKeyword, - senderName); + /// + /// Given a build event that is presumed to be 2.0 (due to its lack of a "ReadFromStream" method) and its + /// LoggingEventType, read that event from the stream. + /// + /// + /// Override to customize serialization per-assembly without relying on compile directives. + /// + private BuildEventArgs? ReadEventFromStream(LoggingEventType eventType, ITranslator translator) + { + string? message = null; + string? helpKeyword = null; + string? senderName = null; - return buildEvent; - } + translator.Translate(ref message); + translator.Translate(ref helpKeyword); + translator.Translate(ref senderName); - /// - /// Read and reconstruct a TaskCommandLineEventArgs from the stream - /// - private TaskCommandLineEventArgs ReadTaskCommandLineEventFromStream(ITranslator translator, string message, string helpKeyword, string senderName) + return eventType switch { - MessageImportance importance = MessageImportance.Normal; - translator.TranslateEnum(ref importance, (int)importance); + LoggingEventType.TaskCommandLineEvent => ReadTaskCommandLineEventFromStream(translator), + LoggingEventType.BuildErrorEvent => ReadTaskBuildErrorEventFromStream(translator, message, helpKeyword, senderName), + LoggingEventType.BuildMessageEvent => ReadBuildMessageEventFromStream(translator, message, helpKeyword, senderName), + LoggingEventType.BuildWarningEvent => ReadBuildWarningEventFromStream(translator, message, helpKeyword, senderName), + _ => null, + }; + } - string commandLine = null; - translator.Translate(ref commandLine); + /// + /// Read and reconstruct a BuildWarningEventArgs from the stream. + /// + private BuildWarningEventArgs ReadBuildWarningEventFromStream(ITranslator translator, string? message, string? helpKeyword, string? senderName) + { + string? code = null; + translator.Translate(ref code); + + int columnNumber = -1; + translator.Translate(ref columnNumber); + + int endColumnNumber = -1; + translator.Translate(ref endColumnNumber); + + int endLineNumber = -1; + translator.Translate(ref endLineNumber); + + string? file = null; + translator.Translate(ref file); + + int lineNumber = -1; + translator.Translate(ref lineNumber); + + string? subCategory = null; + translator.Translate(ref subCategory); + + return new BuildWarningEventArgs( + subCategory, + code, + file, + lineNumber, + columnNumber, + endLineNumber, + endColumnNumber, + message, + helpKeyword, + senderName); + } - string taskName = null; - translator.Translate(ref taskName); + /// + /// Read and reconstruct a BuildErrorEventArgs from the stream. + /// + private BuildErrorEventArgs ReadTaskBuildErrorEventFromStream(ITranslator translator, string? message, string? helpKeyword, string? senderName) + { + string? code = null; + translator.Translate(ref code); + + int columnNumber = -1; + translator.Translate(ref columnNumber); + + int endColumnNumber = -1; + translator.Translate(ref endColumnNumber); + + int endLineNumber = -1; + translator.Translate(ref endLineNumber); + + string? file = null; + translator.Translate(ref file); + + int lineNumber = -1; + translator.Translate(ref lineNumber); + + string? subCategory = null; + translator.Translate(ref subCategory); + + return new BuildErrorEventArgs( + subCategory, + code, + file, + lineNumber, + columnNumber, + endLineNumber, + endColumnNumber, + message, + helpKeyword, + senderName); + } - TaskCommandLineEventArgs buildEvent = new TaskCommandLineEventArgs(commandLine, taskName, importance); - return buildEvent; - } + /// + /// Read and reconstruct a TaskCommandLineEventArgs from the stream. + /// + private TaskCommandLineEventArgs ReadTaskCommandLineEventFromStream(ITranslator translator) + { + MessageImportance importance = MessageImportance.Normal; + translator.TranslateEnum(ref importance, (int)importance); - /// - /// Read and reconstruct a BuildMessageEventArgs from the stream - /// - private BuildMessageEventArgs ReadBuildMessageEventFromStream(ITranslator translator, string message, string helpKeyword, string senderName) - { - MessageImportance importance = MessageImportance.Normal; + string? commandLine = null; + translator.Translate(ref commandLine); - translator.TranslateEnum(ref importance, (int)importance); + string? taskName = null; + translator.Translate(ref taskName); - BuildMessageEventArgs buildEvent = new BuildMessageEventArgs(message, helpKeyword, senderName, importance); - return buildEvent; - } + return new TaskCommandLineEventArgs(commandLine, taskName, importance); + } + + /// + /// Read and reconstruct a BuildMessageEventArgs from the stream. + /// + private BuildMessageEventArgs ReadBuildMessageEventFromStream(ITranslator translator, string? message, string? helpKeyword, string? senderName) + { + MessageImportance importance = MessageImportance.Normal; - #endregion + translator.TranslateEnum(ref importance, (int)importance); - #endregion + return new BuildMessageEventArgs(message, helpKeyword, senderName, importance); } } diff --git a/src/Shared/LogMessagePacketBase.cs b/src/Shared/LogMessagePacketBase.cs index 33e7c619c97..57c7ffe3c46 100644 --- a/src/Shared/LogMessagePacketBase.cs +++ b/src/Shared/LogMessagePacketBase.cs @@ -23,6 +23,9 @@ namespace Microsoft.Build.Shared /// An enumeration of all the types of BuildEventArgs that can be /// packaged by this logMessagePacket /// + /// + /// Several of these values must be kept in sync with MSBuildTaskHost's LoggingEventType. + /// internal enum LoggingEventType : int { /// From 5a337fdbf79684b0fa2540cc473dd7ad582424da Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 5 Feb 2026 13:18:12 -0800 Subject: [PATCH 101/136] MSBuildTaskHost: Clean up NodeBuildComplete - File-scoped namespace - Expression-bodied members - Enable nullability --- .../BackEnd/NodeBuildComplete.cs | 100 ++++++------------ 1 file changed, 34 insertions(+), 66 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/NodeBuildComplete.cs b/src/MSBuildTaskHost/BackEnd/NodeBuildComplete.cs index 4a56debc189..725708126c8 100644 --- a/src/MSBuildTaskHost/BackEnd/NodeBuildComplete.cs +++ b/src/MSBuildTaskHost/BackEnd/NodeBuildComplete.cs @@ -3,81 +3,49 @@ using System.Diagnostics; -#nullable disable +namespace Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.TaskHost.BackEnd +/// +/// The NodeBuildComplete packet is used to indicate to a node that it should clean up its current build and +/// possibly prepare for node reuse. +/// +internal sealed class NodeBuildComplete : INodePacket { /// - /// The NodeBuildComplete packet is used to indicate to a node that it should clean up its current build and - /// possibly prepare for node reuse. + /// Flag indicating if the node should prepare for reuse after cleanup. /// - internal class NodeBuildComplete : INodePacket - { - /// - /// Flag indicating if the node should prepare for reuse after cleanup. - /// - private bool _prepareForReuse; - - /// - /// Constructor. - /// - public NodeBuildComplete(bool prepareForReuse) - { - _prepareForReuse = prepareForReuse; - } - - /// - /// Private constructor for translation - /// - private NodeBuildComplete() - { - } - - /// - /// Flag indicating if the node should prepare for reuse. - /// - public bool PrepareForReuse - { - [DebuggerStepThrough] - get - { return _prepareForReuse; } - } + private bool _prepareForReuse; - #region INodePacket Members - - /// - /// The packet type - /// - public NodePacketType Type - { - [DebuggerStepThrough] - get - { return NodePacketType.NodeBuildComplete; } - } + public NodeBuildComplete(bool prepareForReuse) + { + _prepareForReuse = prepareForReuse; + } - #endregion + private NodeBuildComplete() + { + } - #region INodePacketTranslatable Members + /// + /// Gets a value indicating whether the node should prepare for reuse. + /// + public bool PrepareForReuse => _prepareForReuse; - /// - /// Translates the packet to/from binary form. - /// - /// The translator to use. - public void Translate(ITranslator translator) - { - translator.Translate(ref _prepareForReuse); - } + /// + /// Gets the packet type. + /// + public NodePacketType Type => NodePacketType.NodeBuildComplete; - /// - /// Factory for deserialization. - /// - internal static NodeBuildComplete FactoryForDeserialization(ITranslator translator) - { - NodeBuildComplete packet = new NodeBuildComplete(); - packet.Translate(translator); - return packet; - } + /// + /// Translates the packet to/from binary form. + /// + /// The translator to use. + public void Translate(ITranslator translator) + => translator.Translate(ref _prepareForReuse); - #endregion + internal static NodeBuildComplete FactoryForDeserialization(ITranslator translator) + { + var packet = new NodeBuildComplete(); + packet.Translate(translator); + return packet; } } From bff8b3cb22462699b27d64d87d3ca07161104b91 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 5 Feb 2026 13:19:38 -0800 Subject: [PATCH 102/136] MSBuildTaskHost: Clean up NodeEngineShutdownReason - File-scoped namespace - Enable nullability --- .../BackEnd/NodeEngineShutdownReason.cs | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/NodeEngineShutdownReason.cs b/src/MSBuildTaskHost/BackEnd/NodeEngineShutdownReason.cs index 8df1134faf4..52d6ee5b2ca 100644 --- a/src/MSBuildTaskHost/BackEnd/NodeEngineShutdownReason.cs +++ b/src/MSBuildTaskHost/BackEnd/NodeEngineShutdownReason.cs @@ -1,35 +1,30 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable +namespace Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.TaskHost.BackEnd +/// +/// Reasons for a node to shutdown. +/// +internal enum NodeEngineShutdownReason { - #region Enums /// - /// Reasons for a node to shutdown. + /// The BuildManager sent a command instructing the node to terminate. /// - public enum NodeEngineShutdownReason - { - /// - /// The BuildManager sent a command instructing the node to terminate. - /// - BuildComplete, + BuildComplete, - /// - /// The BuildManager sent a command instructing the node to terminate, but to restart for reuse. - /// - BuildCompleteReuse, + /// + /// The BuildManager sent a command instructing the node to terminate, but to restart for reuse. + /// + BuildCompleteReuse, - /// - /// The communication link failed. - /// - ConnectionFailed, + /// + /// The communication link failed. + /// + ConnectionFailed, - /// - /// The NodeEngine caught an exception which requires the Node to shut down. - /// - Error, - } - #endregion + /// + /// The NodeEngine caught an exception which requires the Node to shut down. + /// + Error, } From ea0e75a25da54b31e914ead42504d0575d8ee978 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 5 Feb 2026 14:38:39 -0800 Subject: [PATCH 103/136] MSBuildTaskHost: Clean up NodeShutdown - File-scoped namespace - Expression-bodied members - Enable nullability --- src/MSBuildTaskHost/BackEnd/NodeShutdown.cs | 167 ++++++++------------ 1 file changed, 69 insertions(+), 98 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/NodeShutdown.cs b/src/MSBuildTaskHost/BackEnd/NodeShutdown.cs index e63d6b0b115..27e27c9dd51 100644 --- a/src/MSBuildTaskHost/BackEnd/NodeShutdown.cs +++ b/src/MSBuildTaskHost/BackEnd/NodeShutdown.cs @@ -3,121 +3,92 @@ using System; -#nullable disable +namespace Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.TaskHost.BackEnd +/// +/// Reasons why the node shut down. +/// +internal enum NodeShutdownReason { /// - /// Reasons why the node shut down. + /// The node shut down because it was requested to shut down. /// - internal enum NodeShutdownReason - { - /// - /// The node shut down because it was requested to shut down. - /// - Requested, - - /// - /// The node shut down because of an error. - /// - Error, - - /// - /// The node shut down because the connection failed. - /// - ConnectionFailed, - } + Requested, /// - /// Implementation of INodePacket for the packet informing the build manager than a node has shut down. - /// This is the last packet the BuildManager will receive from a Node, and as such can be used to trigger - /// any appropriate cleanup behavior. + /// The node shut down because of an error. /// - internal class NodeShutdown : INodePacket - { - /// - /// The reason the node shut down. - /// - private NodeShutdownReason _reason; + Error, - /// - /// The exception - if any. - /// - private Exception _exception; - - /// - /// Constructor - /// - public NodeShutdown(NodeShutdownReason reason) - : this(reason, null) - { - } - - /// - /// Constructor - /// - public NodeShutdown(NodeShutdownReason reason, Exception e) - { - _reason = reason; - _exception = e; - } + /// + /// The node shut down because the connection failed. + /// + ConnectionFailed, +} - /// - /// Constructor for deserialization - /// - private NodeShutdown() - { - } +/// +/// Implementation of INodePacket for the packet informing the build manager than a node has shut down. +/// This is the last packet the BuildManager will receive from a Node, and as such can be used to trigger +/// any appropriate cleanup behavior. +/// +internal sealed class NodeShutdown : INodePacket +{ + /// + /// The reason the node shut down. + /// + private NodeShutdownReason _reason; - #region INodePacket Members + /// + /// The exception - if any. + /// + private Exception? _exception; - /// - /// Returns the packet type. - /// - public NodePacketType Type - { - get { return NodePacketType.NodeShutdown; } - } + /// + /// Constructor + /// + public NodeShutdown(NodeShutdownReason reason) + : this(reason, exception: null) + { + } - #endregion + public NodeShutdown(NodeShutdownReason reason, Exception? exception) + { + _reason = reason; + _exception = exception; + } - /// - /// The reason for shutting down. - /// - public NodeShutdownReason Reason - { - get { return _reason; } - } + private NodeShutdown() + { + } - /// - /// The exception, if any. - /// - public Exception Exception - { - get { return _exception; } - } + /// + /// Gets the packet type. + /// + public NodePacketType Type => NodePacketType.NodeShutdown; - #region INodePacketTranslatable Members + /// + /// Gets the reason for shutting down. + /// + public NodeShutdownReason Reason => _reason; - /// - /// Serializes or deserializes a packet. - /// - public void Translate(ITranslator translator) - { - translator.TranslateEnum(ref _reason, (int)_reason); - translator.TranslateException(ref _exception); - } + /// + /// Gets the exception, if any. + /// + public Exception? Exception => _exception; - /// - /// Factory method for deserialization - /// - internal static NodeShutdown FactoryForDeserialization(ITranslator translator) - { - NodeShutdown shutdown = new NodeShutdown(); - shutdown.Translate(translator); - return shutdown; - } + /// + /// Serializes or deserializes a packet. + /// + public void Translate(ITranslator translator) + { + translator.TranslateEnum(ref _reason, (int)_reason); + translator.TranslateException(ref _exception); + } - #endregion + internal static NodeShutdown FactoryForDeserialization(ITranslator translator) + { + var shutdown = new NodeShutdown(); + shutdown.Translate(translator); + return shutdown; } } From 52f148bac498dbf2cc5eeb17a17de58959442000 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 5 Feb 2026 15:08:41 -0800 Subject: [PATCH 104/136] MSBuildTaskHost: Clean up TaskHostConfiguration - File-scoped namespace - Expression-bodied members - Enable nullability --- .../BackEnd/TaskHostConfiguration.cs | 729 +++++++----------- 1 file changed, 300 insertions(+), 429 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/TaskHostConfiguration.cs b/src/MSBuildTaskHost/BackEnd/TaskHostConfiguration.cs index beb8c8bbcc2..f532b01c7d4 100644 --- a/src/MSBuildTaskHost/BackEnd/TaskHostConfiguration.cs +++ b/src/MSBuildTaskHost/BackEnd/TaskHostConfiguration.cs @@ -3,483 +3,354 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using Microsoft.Build.TaskHost.Utilities; -#nullable disable +namespace Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.TaskHost.BackEnd +/// +/// TaskHostConfiguration contains information needed for the task host to +/// configure itself for to execute a particular task. +/// +internal sealed class TaskHostConfiguration : INodePacket { /// - /// TaskHostConfiguration contains information needed for the task host to - /// configure itself for to execute a particular task. + /// The node id (of the parent node, to make the logging work out). /// - internal class TaskHostConfiguration : INodePacket - { - /// - /// The node id (of the parent node, to make the logging work out) - /// - private int _nodeId; - - /// - /// The startup directory - /// - private string _startupDirectory; - - /// - /// The process environment. - /// - private Dictionary _buildProcessEnvironment; - - /// - /// The culture - /// - private CultureInfo _culture = CultureInfo.CurrentCulture; - - /// - /// The UI culture. - /// - private CultureInfo _uiCulture = CultureInfo.CurrentUICulture; - - /// - /// The AppDomainSetup that we may want to use on AppDomainIsolated tasks. - /// - private AppDomainSetup _appDomainSetup; - - /// - /// Line number where the instance of this task is defined. - /// - private int _lineNumberOfTask; - - /// - /// Column number where the instance of this task is defined. - /// - private int _columnNumberOfTask; - - /// - /// Project file where the instance of this task is defined. - /// - private string _projectFileOfTask; - - /// - /// ContinueOnError flag for this particular task. - /// - private bool _continueOnError; - - /// - /// Name of the task to be executed on the task host. - /// - private string _taskName; - - /// - /// Location of the assembly containing the task to be executed. - /// - private string _taskLocation; - - /// - /// Whether task inputs are logged. - /// - private bool _isTaskInputLoggingEnabled; - - /// - /// Target name that is requesting the task execution. - /// - private string _targetName; - - /// - /// Project file path that is requesting the task execution. - /// - private string _projectFile; - - /// - /// The set of parameters to apply to the task prior to execution. - /// - private Dictionary _taskParameters; - - private Dictionary _globalParameters; - - private ICollection _warningsAsErrors; - private ICollection _warningsNotAsErrors; - - private ICollection _warningsAsMessages; - - /// - /// Initializes a new instance of the class. - /// - /// The ID of the node being configured. - /// The startup directory for the task being executed. - /// The set of environment variables to apply to the task execution process. - /// The culture of the thread that will execute the task. - /// The UI culture of the thread that will execute the task. - /// The host services to be used by the task host. - /// The AppDomainSetup that may be used to pass information to an AppDomainIsolated task. - /// The line number of the location from which this task was invoked. - /// The column number of the location from which this task was invoked. - /// The project file from which this task was invoked. - /// A flag to indicate whether to continue with the build after the task fails. - /// The name of the task. - /// The location of the assembly from which the task is to be loaded. - /// The name of the target that is requesting the task execution. - /// The project path that invokes the task. - /// A flag to indicate whether task inputs are logged. - /// The parameters to apply to the task. - /// The global properties for the current project. - /// A collection of warning codes to be treated as errors. - /// A collection of warning codes not to be treated as errors. - /// A collection of warning codes to be treated as messages. - public TaskHostConfiguration( - int nodeId, - string startupDirectory, - IDictionary buildProcessEnvironment, - CultureInfo culture, - CultureInfo uiCulture, - AppDomainSetup appDomainSetup, - int lineNumberOfTask, - int columnNumberOfTask, - string projectFileOfTask, - bool continueOnError, - string taskName, - string taskLocation, - string targetName, - string projectFile, - bool isTaskInputLoggingEnabled, - IDictionary taskParameters, - Dictionary globalParameters, - ICollection warningsAsErrors, - ICollection warningsNotAsErrors, - ICollection warningsAsMessages) - { - ErrorUtilities.VerifyThrowInternalLength(taskName, nameof(taskName)); - ErrorUtilities.VerifyThrowInternalLength(taskLocation, nameof(taskLocation)); + private int _nodeId; - _nodeId = nodeId; - _startupDirectory = startupDirectory; + /// + /// The startup directory. + /// + private string? _startupDirectory; - if (buildProcessEnvironment != null) - { - _buildProcessEnvironment = buildProcessEnvironment as Dictionary; + /// + /// The process environment. + /// + private Dictionary? _buildProcessEnvironment; - if (_buildProcessEnvironment == null) - { - _buildProcessEnvironment = new Dictionary(buildProcessEnvironment); - } - } + /// + /// The culture. + /// + private CultureInfo? _culture = CultureInfo.CurrentCulture; - _culture = culture; - _uiCulture = uiCulture; - _appDomainSetup = appDomainSetup; - _lineNumberOfTask = lineNumberOfTask; - _columnNumberOfTask = columnNumberOfTask; - _projectFileOfTask = projectFileOfTask; - _projectFile = projectFile; - _continueOnError = continueOnError; - _taskName = taskName; - _taskLocation = taskLocation; - _targetName = targetName; - _isTaskInputLoggingEnabled = isTaskInputLoggingEnabled; - _warningsAsErrors = warningsAsErrors; - _warningsNotAsErrors = warningsNotAsErrors; - _warningsAsMessages = warningsAsMessages; - - if (taskParameters != null) - { - _taskParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); + /// + /// The UI culture. + /// + private CultureInfo? _uiCulture = CultureInfo.CurrentUICulture; - foreach (KeyValuePair parameter in taskParameters) - { - _taskParameters[parameter.Key] = new TaskParameter(parameter.Value); - } - } + /// + /// The AppDomainSetup that we may want to use on AppDomainIsolated tasks. + /// + private AppDomainSetup? _appDomainSetup; - _globalParameters = globalParameters ?? new Dictionary(); - } + /// + /// Line number where the instance of this task is defined. + /// + private int _lineNumberOfTask; - /// - /// Constructor for deserialization. - /// - private TaskHostConfiguration() - { - } + /// + /// Column number where the instance of this task is defined. + /// + private int _columnNumberOfTask; - /// - /// The node id - /// - public int NodeId - { - [DebuggerStepThrough] - get - { return _nodeId; } - } + /// + /// Project file where the instance of this task is defined. + /// + private string? _projectFileOfTask; - /// - /// The startup directory - /// - public string StartupDirectory - { - [DebuggerStepThrough] - get - { return _startupDirectory; } - } + /// + /// ContinueOnError flag for this particular task. + /// + private bool _continueOnError; - /// - /// The process environment. - /// - public Dictionary BuildProcessEnvironment - { - [DebuggerStepThrough] - get - { return _buildProcessEnvironment; } - } + /// + /// Name of the task to be executed on the task host. + /// + private string? _taskName; - /// - /// The culture - /// - public CultureInfo Culture - { - [DebuggerStepThrough] - get - { return _culture; } - } + /// + /// Location of the assembly containing the task to be executed. + /// + private string? _taskLocation; - /// - /// The UI culture. - /// - public CultureInfo UICulture - { - [DebuggerStepThrough] - get - { return _uiCulture; } - } + /// + /// Whether task inputs are logged. + /// + private bool _isTaskInputLoggingEnabled; - /// - /// The AppDomain configuration bytes that we may want to use to initialize - /// AppDomainIsolated tasks. - /// - public AppDomainSetup AppDomainSetup - { - [DebuggerStepThrough] - get - { return _appDomainSetup; } - } + /// + /// Target name that is requesting the task execution. + /// + private string? _targetName; - /// - /// Line number where the instance of this task is defined. - /// - public int LineNumberOfTask - { - [DebuggerStepThrough] - get - { return _lineNumberOfTask; } - } + /// + /// Project file path that is requesting the task execution. + /// + private string? _projectFile; - /// - /// Column number where the instance of this task is defined. - /// - public int ColumnNumberOfTask - { - [DebuggerStepThrough] - get - { return _columnNumberOfTask; } - } + /// + /// The set of parameters to apply to the task prior to execution. + /// + private Dictionary? _taskParameters; - /// - /// Project file path that is requesting the task execution. - /// - public string ProjectFile - { - [DebuggerStepThrough] - get - { return _projectFile; } - } + private Dictionary? _globalParameters; - /// - /// Target name that is requesting the task execution. - /// - public string TargetName - { - [DebuggerStepThrough] - get - { return _targetName; } - } + private ICollection? _warningsAsErrors; + private ICollection? _warningsNotAsErrors; - /// - /// ContinueOnError flag for this particular task - /// - public bool ContinueOnError - { - [DebuggerStepThrough] - get - { return _continueOnError; } - } + private ICollection? _warningsAsMessages; - /// - /// Project file where the instance of this task is defined. - /// - public string ProjectFileOfTask + /// + /// Initializes a new instance of the class. + /// + /// The ID of the node being configured. + /// The startup directory for the task being executed. + /// The set of environment variables to apply to the task execution process. + /// The culture of the thread that will execute the task. + /// The UI culture of the thread that will execute the task. + /// The host services to be used by the task host. + /// The AppDomainSetup that may be used to pass information to an AppDomainIsolated task. + /// The line number of the location from which this task was invoked. + /// The column number of the location from which this task was invoked. + /// The project file from which this task was invoked. + /// A flag to indicate whether to continue with the build after the task fails. + /// The name of the task. + /// The location of the assembly from which the task is to be loaded. + /// The name of the target that is requesting the task execution. + /// The project path that invokes the task. + /// A flag to indicate whether task inputs are logged. + /// The parameters to apply to the task. + /// The global properties for the current project. + /// A collection of warning codes to be treated as errors. + /// A collection of warning codes not to be treated as errors. + /// A collection of warning codes to be treated as messages. + public TaskHostConfiguration( + int nodeId, + string startupDirectory, + Dictionary buildProcessEnvironment, + CultureInfo culture, + CultureInfo uiCulture, + AppDomainSetup appDomainSetup, + int lineNumberOfTask, + int columnNumberOfTask, + string projectFileOfTask, + bool continueOnError, + string taskName, + string taskLocation, + string targetName, + string projectFile, + bool isTaskInputLoggingEnabled, + Dictionary taskParameters, + Dictionary globalParameters, + ICollection warningsAsErrors, + ICollection warningsNotAsErrors, + ICollection warningsAsMessages) + { + ErrorUtilities.VerifyThrowInternalLength(taskName, nameof(taskName)); + ErrorUtilities.VerifyThrowInternalLength(taskLocation, nameof(taskLocation)); + + _nodeId = nodeId; + _startupDirectory = startupDirectory; + + _buildProcessEnvironment = buildProcessEnvironment; + + _culture = culture; + _uiCulture = uiCulture; + _appDomainSetup = appDomainSetup; + _lineNumberOfTask = lineNumberOfTask; + _columnNumberOfTask = columnNumberOfTask; + _projectFileOfTask = projectFileOfTask; + _projectFile = projectFile; + _continueOnError = continueOnError; + _taskName = taskName; + _taskLocation = taskLocation; + _targetName = targetName; + _isTaskInputLoggingEnabled = isTaskInputLoggingEnabled; + _warningsAsErrors = warningsAsErrors; + _warningsNotAsErrors = warningsNotAsErrors; + _warningsAsMessages = warningsAsMessages; + + if (taskParameters != null) { - [DebuggerStepThrough] - get - { return _projectFileOfTask; } - } + _taskParameters = new(StringComparer.OrdinalIgnoreCase); - /// - /// Name of the task to execute. - /// - public string TaskName - { - [DebuggerStepThrough] - get - { return _taskName; } + foreach (KeyValuePair parameter in taskParameters) + { + _taskParameters[parameter.Key] = new TaskParameter(parameter.Value); + } } - /// - /// Path to the assembly to load the task from. - /// - public string TaskLocation - { - [DebuggerStepThrough] - get - { return _taskLocation; } - } + _globalParameters = globalParameters ?? []; + } - /// - /// Returns if the build is configured to log all task inputs. - /// - public bool IsTaskInputLoggingEnabled - { - [DebuggerStepThrough] - get - { return _isTaskInputLoggingEnabled; } - } + private TaskHostConfiguration() + { + } - /// - /// Parameters to set on the instantiated task prior to execution. - /// - public Dictionary TaskParameters - { - [DebuggerStepThrough] - get - { return _taskParameters; } - } + /// + /// Gets the node id. + /// + public int NodeId => _nodeId; - /// - /// Gets the global properties for the current project. - /// - public Dictionary GlobalProperties - { - [DebuggerStepThrough] - get - { return _globalParameters; } - } + /// + /// Gets the startup directory. + /// + public string? StartupDirectory => _startupDirectory; - /// - /// The NodePacketType of this NodePacket - /// - public NodePacketType Type - { - [DebuggerStepThrough] - get - { return NodePacketType.TaskHostConfiguration; } - } + /// + /// Gets the process environment. + /// + public Dictionary? BuildProcessEnvironment => _buildProcessEnvironment; - public ICollection WarningsAsErrors - { - [DebuggerStepThrough] - get - { - return _warningsAsErrors; - } - } + /// + /// Gets the culture. + /// + public CultureInfo? Culture => _culture; - public ICollection WarningsNotAsErrors - { - [DebuggerStepThrough] - get - { - return _warningsNotAsErrors; - } - } + /// + /// Gets the UI culture. + /// + public CultureInfo? UICulture => _uiCulture; - public ICollection WarningsAsMessages - { - [DebuggerStepThrough] - get - { - return _warningsAsMessages; - } - } + /// + /// Gets the AppDomain configuration bytes that we may want to use to initialize + /// AppDomainIsolated tasks. + /// + public AppDomainSetup? AppDomainSetup => _appDomainSetup; - /// - /// Translates the packet to/from binary form. - /// - /// The translator to use. - public void Translate(ITranslator translator) - { - translator.Translate(ref _nodeId); - translator.Translate(ref _startupDirectory); - translator.TranslateDictionary(ref _buildProcessEnvironment, StringComparer.OrdinalIgnoreCase); - translator.TranslateCulture(ref _culture); - translator.TranslateCulture(ref _uiCulture); - - // The packet version is used to determine if the AppDomain configuration should be serialized. - // If the packet version is bigger then 0, it means the task host will running under .NET. - // Although MSBuild.exe runs under .NET Framework and has AppDomain support, - // we don't transmit AppDomain config when communicating with dotnet.exe (it is not supported in .NET 5+). - if (translator.NegotiatedPacketVersion == 0) - { - byte[] appDomainConfigBytes = null; + /// + /// Gets the line number where the instance of this task is defined. + /// + public int LineNumberOfTask => _lineNumberOfTask; - // Set the configuration bytes just before serialization in case the SetConfigurationBytes was invoked during lifetime of this instance. - if (translator.Mode == TranslationDirection.WriteToStream) - { - appDomainConfigBytes = _appDomainSetup?.GetConfigurationBytes(); - } + /// + /// Gets the column number where the instance of this task is defined. + /// + public int ColumnNumberOfTask => _columnNumberOfTask; - translator.Translate(ref appDomainConfigBytes); + /// + /// Gets the project file path that is requesting the task execution. + /// + public string? ProjectFile => _projectFile; - if (translator.Mode == TranslationDirection.ReadFromStream) - { - _appDomainSetup = new AppDomainSetup(); - _appDomainSetup.SetConfigurationBytes(appDomainConfigBytes); - } - } + /// + /// Gets the target name that is requesting the task execution. + /// + public string? TargetName => _targetName; + + /// + /// Gets the ContinueOnError flag for this particular task. + /// + public bool ContinueOnError => _continueOnError; + + /// + /// Gets the project file where the instance of this task is defined. + /// + public string? ProjectFileOfTask => _projectFileOfTask; + + /// + /// Gets the name of the task to execute. + /// + public string? TaskName => _taskName; + + /// + /// Gets the path to the assembly to load the task from. + /// + public string? TaskLocation => _taskLocation; - translator.Translate(ref _lineNumberOfTask); - translator.Translate(ref _columnNumberOfTask); - translator.Translate(ref _projectFileOfTask); - translator.Translate(ref _taskName); - translator.Translate(ref _taskLocation); - if (translator.NegotiatedPacketVersion >= 2) + /// + /// Returns if the build is configured to log all task inputs. + /// + public bool IsTaskInputLoggingEnabled => _isTaskInputLoggingEnabled; + + /// + /// Gets the parameters to set on the instantiated task prior to execution. + /// + public Dictionary? TaskParameters => _taskParameters; + + /// + /// Gets the global properties for the current project. + /// + public Dictionary? GlobalProperties => _globalParameters; + + /// + /// Gets the NodePacketType of this NodePacket. + /// + public NodePacketType Type => NodePacketType.TaskHostConfiguration; + + public ICollection? WarningsAsErrors => _warningsAsErrors; + + public ICollection? WarningsNotAsErrors => _warningsNotAsErrors; + + public ICollection? WarningsAsMessages => _warningsAsMessages; + + /// + /// Translates the packet to/from binary form. + /// + /// The translator to use. + public void Translate(ITranslator translator) + { + translator.Translate(ref _nodeId); + translator.Translate(ref _startupDirectory); + translator.TranslateDictionary(ref _buildProcessEnvironment, StringComparer.OrdinalIgnoreCase); + translator.TranslateCulture(ref _culture); + translator.TranslateCulture(ref _uiCulture); + + // The packet version is used to determine if the AppDomain configuration should be serialized. + // If the packet version is bigger then 0, it means the task host will running under .NET. + // Although MSBuild.exe runs under .NET Framework and has AppDomain support, + // we don't transmit AppDomain config when communicating with dotnet.exe (it is not supported in .NET 5+). + if (translator.NegotiatedPacketVersion == 0) + { + byte[]? appDomainConfigBytes = null; + + // Set the configuration bytes just before serialization in case the SetConfigurationBytes was invoked during lifetime of this instance. + if (translator.Mode == TranslationDirection.WriteToStream) { - translator.Translate(ref _targetName); - translator.Translate(ref _projectFile); + appDomainConfigBytes = _appDomainSetup?.GetConfigurationBytes(); } - translator.Translate(ref _isTaskInputLoggingEnabled); - translator.TranslateDictionary(ref _taskParameters, StringComparer.OrdinalIgnoreCase, TaskParameter.FactoryForDeserialization); - translator.Translate(ref _continueOnError); - translator.TranslateDictionary(ref _globalParameters, StringComparer.OrdinalIgnoreCase); - translator.Translate(collection: ref _warningsAsErrors, - objectTranslator: (ITranslator t, ref string s) => t.Translate(ref s), - collectionFactory: count => new HashSet(StringComparer.OrdinalIgnoreCase)); - translator.Translate(collection: ref _warningsNotAsErrors, - objectTranslator: (ITranslator t, ref string s) => t.Translate(ref s), - collectionFactory: count => new HashSet(StringComparer.OrdinalIgnoreCase)); - translator.Translate(collection: ref _warningsAsMessages, - objectTranslator: (ITranslator t, ref string s) => t.Translate(ref s), - collectionFactory: count => new HashSet(StringComparer.OrdinalIgnoreCase)); + translator.Translate(ref appDomainConfigBytes); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + _appDomainSetup = new AppDomainSetup(); + _appDomainSetup.SetConfigurationBytes(appDomainConfigBytes); + } } - /// - /// Factory for deserialization. - /// - internal static INodePacket FactoryForDeserialization(ITranslator translator) + translator.Translate(ref _lineNumberOfTask); + translator.Translate(ref _columnNumberOfTask); + translator.Translate(ref _projectFileOfTask); + translator.Translate(ref _taskName); + translator.Translate(ref _taskLocation); + if (translator.NegotiatedPacketVersion >= 2) { - TaskHostConfiguration configuration = new TaskHostConfiguration(); - configuration.Translate(translator); - - return configuration; + translator.Translate(ref _targetName); + translator.Translate(ref _projectFile); } + + translator.Translate(ref _isTaskInputLoggingEnabled); + translator.TranslateDictionary(ref _taskParameters, StringComparer.OrdinalIgnoreCase, TaskParameter.FactoryForDeserialization); + translator.Translate(ref _continueOnError); + translator.TranslateDictionary(ref _globalParameters, StringComparer.OrdinalIgnoreCase); + translator.Translate(collection: ref _warningsAsErrors, + objectTranslator: (t, ref s) => t.Translate(ref s!), + collectionFactory: count => new HashSet(StringComparer.OrdinalIgnoreCase)); + translator.Translate(collection: ref _warningsNotAsErrors, + objectTranslator: (t, ref s) => t.Translate(ref s!), + collectionFactory: count => new HashSet(StringComparer.OrdinalIgnoreCase)); + translator.Translate(collection: ref _warningsAsMessages, + objectTranslator: (t, ref s) => t.Translate(ref s!), + collectionFactory: count => new HashSet(StringComparer.OrdinalIgnoreCase)); + } + + internal static INodePacket FactoryForDeserialization(ITranslator translator) + { + var configuration = new TaskHostConfiguration(); + configuration.Translate(translator); + + return configuration; } } From 5487183caf001eb53c37d417054388418b5ae9f9 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 5 Feb 2026 15:09:58 -0800 Subject: [PATCH 105/136] MSBuildTaskHost: Clean up TaskHostTaskCancelled - File-scoped namespace - Expression-bodied members - Enable nullability --- .../BackEnd/TaskHostTaskCancelled.cs | 65 ++++++++----------- 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/TaskHostTaskCancelled.cs b/src/MSBuildTaskHost/BackEnd/TaskHostTaskCancelled.cs index 7a2beb05a2f..6f38556f6f3 100644 --- a/src/MSBuildTaskHost/BackEnd/TaskHostTaskCancelled.cs +++ b/src/MSBuildTaskHost/BackEnd/TaskHostTaskCancelled.cs @@ -1,48 +1,39 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable +namespace Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.TaskHost.BackEnd +/// +/// TaskHostTaskCancelled informs the task host that the task it is +/// currently executing has been canceled. +/// +internal sealed class TaskHostTaskCancelled : INodePacket { - /// - /// TaskHostTaskCancelled informs the task host that the task it is - /// currently executing has been canceled. - /// - internal class TaskHostTaskCancelled : INodePacket + public TaskHostTaskCancelled() { - /// - /// Constructor - /// - public TaskHostTaskCancelled() - { - } + } - /// - /// The type of this NodePacket - /// - public NodePacketType Type - { - get { return NodePacketType.TaskHostTaskCancelled; } - } + /// + /// Gets the type of this NodePacket. + /// + public NodePacketType Type => NodePacketType.TaskHostTaskCancelled; - /// - /// Translates the packet to/from binary form. - /// - /// The translator to use. - public void Translate(ITranslator translator) - { - // Do nothing -- this packet doesn't contain any parameters. - } + /// + /// Translates the packet to/from binary form. + /// + /// The translator to use. + public void Translate(ITranslator translator) + { + // Do nothing -- this packet doesn't contain any parameters. + } - /// - /// Factory for deserialization. - /// - internal static INodePacket FactoryForDeserialization(ITranslator translator) - { - TaskHostTaskCancelled taskCancelled = new TaskHostTaskCancelled(); - taskCancelled.Translate(translator); - return taskCancelled; - } + /// + /// Factory for deserialization. + /// + internal static INodePacket FactoryForDeserialization(ITranslator translator) + { + var taskCancelled = new TaskHostTaskCancelled(); + taskCancelled.Translate(translator); + return taskCancelled; } } From 0db87d4349c311a13a61213eafe4d458da1c24f0 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 5 Feb 2026 15:14:31 -0800 Subject: [PATCH 106/136] MSBuildTaskHost: Clean up TaskHostTaskComplete - File-scoped namespace - Expression-bodied members - Enable nullability --- .../BackEnd/TaskHostTaskComplete.cs | 343 +++++++----------- src/MSBuildTaskHost/OutOfProcTaskHostNode.cs | 6 +- 2 files changed, 144 insertions(+), 205 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/TaskHostTaskComplete.cs b/src/MSBuildTaskHost/BackEnd/TaskHostTaskComplete.cs index 163f7200a5e..1dd88584e3c 100644 --- a/src/MSBuildTaskHost/BackEnd/TaskHostTaskComplete.cs +++ b/src/MSBuildTaskHost/BackEnd/TaskHostTaskComplete.cs @@ -3,234 +3,173 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using Microsoft.Build.TaskHost.Utilities; -#nullable disable +namespace Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.TaskHost.BackEnd +/// +/// How the task completed -- successful, failed, or crashed. +/// +internal enum TaskCompleteType { /// - /// How the task completed -- successful, failed, or crashed + /// Task execution succeeded /// - internal enum TaskCompleteType - { - /// - /// Task execution succeeded - /// - Success, - - /// - /// Task execution failed - /// - Failure, - - /// - /// Task crashed during initialization steps -- loading the task, - /// validating or setting the parameters, etc. - /// - CrashedDuringInitialization, - - /// - /// Task crashed while being executed - /// - CrashedDuringExecution, - - /// - /// Task crashed after being executed - /// -- Getting outputs, etc - /// - CrashedAfterExecution - } + Success, /// - /// TaskHostTaskComplete contains all the information the parent node - /// needs from the task host on completion of task execution. + /// Task execution failed /// - internal class TaskHostTaskComplete : INodePacket - { - /// - /// Result of the task's execution. - /// - private TaskCompleteType _taskResult; - - /// - /// If the task threw an exception during its initialization or execution, - /// save it here. - /// - private Exception _taskException; - - /// - /// If there's an additional message that should be attached to the error - /// logged beyond "task X failed unexpectedly", save it here. May be null. - /// - private string _taskExceptionMessage; - - /// - /// If the message saved in taskExceptionMessage requires arguments, save - /// them here. May be null. - /// - private string[] _taskExceptionMessageArgs; - - /// - /// The set of parameters / values from the task after it finishes execution. - /// - private Dictionary _taskOutputParameters = null; - - /// - /// The process environment at the end of task execution. - /// - private Dictionary _buildProcessEnvironment = null; - - -#pragma warning disable CS1572 // XML comment has a param tag, but there is no parameter by that name. Justification: xmldoc doesn't seem to interact well with #ifdef of params. - /// - /// Initializes a new instance of the class. - /// - /// The result of the task's execution. - /// The file accesses reported by the task. - /// The build process environment as it was at the end of the task's execution. -#pragma warning restore CS1572 // XML comment has a param tag, but there is no parameter by that name - public TaskHostTaskComplete( - OutOfProcTaskHostTaskResult result, - IDictionary buildProcessEnvironment) - { - ErrorUtilities.VerifyThrowInternalNull(result); + Failure, - _taskResult = result.Result; - _taskException = result.TaskException; - _taskExceptionMessage = result.ExceptionMessage; - _taskExceptionMessageArgs = result.ExceptionMessageArgs; + /// + /// Task crashed during initialization steps -- loading the task, + /// validating or setting the parameters, etc. + /// + CrashedDuringInitialization, - if (result.FinalParameterValues != null) - { - _taskOutputParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (KeyValuePair parameter in result.FinalParameterValues) - { - _taskOutputParameters[parameter.Key] = new TaskParameter(parameter.Value); - } - } + /// + /// Task crashed while being executed + /// + CrashedDuringExecution, - if (buildProcessEnvironment != null) - { - _buildProcessEnvironment = buildProcessEnvironment as Dictionary; + /// + /// Task crashed after being executed + /// -- Getting outputs, etc + /// + CrashedAfterExecution +} - if (_buildProcessEnvironment == null) - { - _buildProcessEnvironment = new Dictionary(buildProcessEnvironment); - } - } - } +/// +/// TaskHostTaskComplete contains all the information the parent node +/// needs from the task host on completion of task execution. +/// +internal sealed class TaskHostTaskComplete : INodePacket +{ + /// + /// Result of the task's execution. + /// + private TaskCompleteType _taskResult; - /// - /// For deserialization. - /// - private TaskHostTaskComplete() - { - } + /// + /// If the task threw an exception during its initialization or execution, + /// save it here. + /// + private Exception? _taskException; - /// - /// Result of the task's execution. - /// - public TaskCompleteType TaskResult - { - [DebuggerStepThrough] - get - { return _taskResult; } - } + /// + /// If there's an additional message that should be attached to the error + /// logged beyond "task X failed unexpectedly", save it here. May be null. + /// + private string? _taskExceptionMessage; - /// - /// If the task threw an exception during its initialization or execution, - /// save it here. - /// - public Exception TaskException - { - [DebuggerStepThrough] - get - { return _taskException; } - } + /// + /// If the message saved in taskExceptionMessage requires arguments, save + /// them here. May be null. + /// + private string[]? _taskExceptionMessageArgs; - /// - /// If there's an additional message that should be attached to the error - /// logged beyond "task X failed unexpectedly", put it here. May be null. - /// - public string TaskExceptionMessage - { - [DebuggerStepThrough] - get - { return _taskExceptionMessage; } - } + /// + /// The set of parameters / values from the task after it finishes execution. + /// + private Dictionary? _taskOutputParameters; - /// - /// If there are arguments that need to be formatted into the message being - /// sent, set them here. May be null. - /// - public string[] TaskExceptionMessageArgs - { - [DebuggerStepThrough] - get - { return _taskExceptionMessageArgs; } - } + /// + /// The process environment at the end of task execution. + /// + private Dictionary? _buildProcessEnvironment; - /// - /// Task parameters and their values after the task has finished. - /// - public Dictionary TaskOutputParameters + /// + /// Initializes a new instance of the class. + /// + /// The result of the task's execution. + /// The build process environment as it was at the end of the task's execution. + public TaskHostTaskComplete( + OutOfProcTaskHostTaskResult result, + Dictionary? buildProcessEnvironment) + { + ErrorUtilities.VerifyThrowInternalNull(result); + + _taskResult = result.Result; + _taskException = result.TaskException; + _taskExceptionMessage = result.ExceptionMessage; + _taskExceptionMessageArgs = result.ExceptionMessageArgs; + + if (result.FinalParameterValues != null) { - [DebuggerStepThrough] - get - { - if (_taskOutputParameters == null) - { - _taskOutputParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - } + _taskOutputParameters = new(StringComparer.OrdinalIgnoreCase); - return _taskOutputParameters; + foreach (KeyValuePair parameter in result.FinalParameterValues) + { + _taskOutputParameters[parameter.Key] = new TaskParameter(parameter.Value); } } - /// - /// The process environment. - /// - public Dictionary BuildProcessEnvironment - { - [DebuggerStepThrough] - get - { return _buildProcessEnvironment; } - } + _buildProcessEnvironment = buildProcessEnvironment; + } - /// - /// The type of this packet. - /// - public NodePacketType Type - { - get { return NodePacketType.TaskHostTaskComplete; } - } + private TaskHostTaskComplete() + { + } - /// - /// Translates the packet to/from binary form. - /// - /// The translator to use. - public void Translate(ITranslator translator) - { - translator.TranslateEnum(ref _taskResult, (int)_taskResult); - translator.TranslateException(ref _taskException); - translator.Translate(ref _taskExceptionMessage); - translator.Translate(ref _taskExceptionMessageArgs); - translator.TranslateDictionary(ref _taskOutputParameters, StringComparer.OrdinalIgnoreCase, TaskParameter.FactoryForDeserialization); - translator.TranslateDictionary(ref _buildProcessEnvironment, StringComparer.OrdinalIgnoreCase); - bool hasFileAccessData = false; - translator.Translate(ref hasFileAccessData); - } + /// + /// Gets the result of the task's execution. + /// + public TaskCompleteType TaskResult => _taskResult; - /// - /// Factory for deserialization. - /// - internal static INodePacket FactoryForDeserialization(ITranslator translator) - { - TaskHostTaskComplete taskComplete = new TaskHostTaskComplete(); - taskComplete.Translate(translator); - return taskComplete; - } + /// + /// Gets the exception thrown be the task during initialization or execution, if any. + /// save it here. + /// + public Exception? TaskException => _taskException; + + /// + /// If there's an additional message that should be attached to the error + /// logged beyond "task X failed unexpectedly", put it here. May be null. + /// + public string? TaskExceptionMessage => _taskExceptionMessage; + + /// + /// If there are arguments that need to be formatted into the message being + /// sent, set them here. May be null. + /// + public string[]? TaskExceptionMessageArgs => _taskExceptionMessageArgs; + + /// + /// Task parameters and their values after the task has finished. + /// + public Dictionary? TaskOutputParameters => _taskOutputParameters ??= new(StringComparer.OrdinalIgnoreCase); + + /// + /// The process environment. + /// + public Dictionary? BuildProcessEnvironment => _buildProcessEnvironment; + + /// + /// Gets the type of this packet. + /// + public NodePacketType Type => NodePacketType.TaskHostTaskComplete; + + /// + /// Translates the packet to/from binary form. + /// + /// The translator to use. + public void Translate(ITranslator translator) + { + translator.TranslateEnum(ref _taskResult, (int)_taskResult); + translator.TranslateException(ref _taskException); + translator.Translate(ref _taskExceptionMessage); + translator.Translate(ref _taskExceptionMessageArgs); + translator.TranslateDictionary(ref _taskOutputParameters, StringComparer.OrdinalIgnoreCase, TaskParameter.FactoryForDeserialization); + translator.TranslateDictionary(ref _buildProcessEnvironment, StringComparer.OrdinalIgnoreCase); + bool hasFileAccessData = false; + translator.Translate(ref hasFileAccessData); + } + + internal static INodePacket FactoryForDeserialization(ITranslator translator) + { + var taskComplete = new TaskHostTaskComplete(); + taskComplete.Translate(translator); + return taskComplete; } } diff --git a/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs index ebac8961e09..42c56662df3 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs @@ -689,7 +689,7 @@ private void RunTask(object state) { _isTaskExecuting = false; - IDictionary currentEnvironment = CommunicationsUtilities.GetEnvironmentVariables(); + Dictionary currentEnvironment = CommunicationsUtilities.GetEnvironmentVariables(); currentEnvironment = UpdateEnvironmentForMainNode(currentEnvironment); taskResult ??= OutOfProcTaskHostTaskResult.Failure(); @@ -797,10 +797,10 @@ private void SetTaskHostEnvironment(IDictionary environment) /// so that when we pass this dictionary back to the main node, all it should have to do /// is just set it. /// - private IDictionary UpdateEnvironmentForMainNode(IDictionary environment) + private Dictionary UpdateEnvironmentForMainNode(Dictionary environment) { ErrorUtilities.VerifyThrowInternalNull(s_mismatchedEnvironmentValues, "mismatchedEnvironmentValues"); - IDictionary updatedEnvironment = null; + Dictionary updatedEnvironment = null; if (_updateEnvironment) { From 3a3db6d35a441b4cd45b1ffac895ce7bf24f2a41 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 5 Feb 2026 15:36:38 -0800 Subject: [PATCH 107/136] MSBuildTaskHost: Clean up TaskParameter - File-scoped namespace - Expression-bodied members - Enable nullability --- src/MSBuildTaskHost/BackEnd/TaskParameter.cs | 1264 +++++++++--------- 1 file changed, 615 insertions(+), 649 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/TaskParameter.cs b/src/MSBuildTaskHost/BackEnd/TaskParameter.cs index e206faf84a3..9975810aeca 100644 --- a/src/MSBuildTaskHost/BackEnd/TaskParameter.cs +++ b/src/MSBuildTaskHost/BackEnd/TaskParameter.cs @@ -10,246 +10,554 @@ using Microsoft.Build.TaskHost.Resources; using Microsoft.Build.TaskHost.Utilities; -#nullable disable +namespace Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.TaskHost.BackEnd +/// +/// Type of parameter, used to figure out how to serialize it. +/// +internal enum TaskParameterType { /// - /// Type of parameter, used to figure out how to serialize it. + /// Parameter is null. /// - internal enum TaskParameterType - { - /// - /// Parameter is null. - /// - Null, + Null, - /// - /// Parameter is of a type described by a . - /// - PrimitiveType, + /// + /// Parameter is of a type described by a . + /// + PrimitiveType, - /// - /// Parameter is an array of a type described by a . - /// - PrimitiveTypeArray, + /// + /// Parameter is an array of a type described by a . + /// + PrimitiveTypeArray, - /// - /// Parameter is a value type. Note: Must be . - /// - ValueType, + /// + /// Parameter is a value type. Note: Must be . + /// + ValueType, - /// - /// Parameter is an array of value types. Note: Must be . - /// - ValueTypeArray, + /// + /// Parameter is an array of value types. Note: Must be . + /// + ValueTypeArray, - /// - /// Parameter is an ITaskItem. - /// - ITaskItem, + /// + /// Parameter is an ITaskItem. + /// + ITaskItem, - /// - /// Parameter is an array of ITaskItems. - /// - ITaskItemArray, + /// + /// Parameter is an array of ITaskItems. + /// + ITaskItemArray, - /// - /// An invalid parameter -- the value of this parameter contains the exception - /// that is thrown when trying to access it. - /// - Invalid, - } + /// + /// An invalid parameter -- the value of this parameter contains the exception + /// that is thrown when trying to access it. + /// + Invalid, +} +/// +/// Wrapper for task parameters, to allow proper serialization even +/// in cases where the parameter is not .NET serializable. +/// +internal class TaskParameter : MarshalByRefObject, ITranslatable +{ /// - /// Wrapper for task parameters, to allow proper serialization even - /// in cases where the parameter is not .NET serializable. + /// The TaskParameterType of the wrapped parameter. /// - internal class TaskParameter : MarshalByRefObject, ITranslatable - { - /// - /// The TaskParameterType of the wrapped parameter. - /// - private TaskParameterType _parameterType; + private TaskParameterType _parameterType; - /// - /// The of the wrapped parameter if it's a primitive type. - /// - private TypeCode _parameterTypeCode; + /// + /// The of the wrapped parameter if it's a primitive type. + /// + private TypeCode _parameterTypeCode; - /// - /// The actual task parameter that we're wrapping - /// - private object _wrappedParameter; + /// + /// The actual task parameter that we're wrapping + /// + private object? _wrappedParameter; - /// - /// Create a new TaskParameter - /// - public TaskParameter(object wrappedParameter) + public TaskParameter(object? wrappedParameter) + { + if (wrappedParameter == null) { - if (wrappedParameter == null) - { - _parameterType = TaskParameterType.Null; - _wrappedParameter = null; - return; - } + _parameterType = TaskParameterType.Null; + _wrappedParameter = null; + return; + } + + Type wrappedParameterType = wrappedParameter.GetType(); + + if (wrappedParameter is Exception) + { + _parameterType = TaskParameterType.Invalid; + _wrappedParameter = wrappedParameter; + return; + } - Type wrappedParameterType = wrappedParameter.GetType(); + // It's not null or invalid, so it should be a valid parameter type. + ErrorUtilities.VerifyThrow( + TaskParameterTypeVerifier.IsValidInputParameter(wrappedParameterType) || TaskParameterTypeVerifier.IsValidOutputParameter(wrappedParameterType), + "How did we manage to get a task parameter of type {0} that isn't a valid parameter type?", + wrappedParameterType); - if (wrappedParameter is Exception) + if (wrappedParameterType.IsArray) + { + TypeCode typeCode = Type.GetTypeCode(wrappedParameterType.GetElementType()); + if (typeCode != TypeCode.Object && typeCode != TypeCode.DBNull) { - _parameterType = TaskParameterType.Invalid; + _parameterType = TaskParameterType.PrimitiveTypeArray; + _parameterTypeCode = typeCode; _wrappedParameter = wrappedParameter; - return; } - - // It's not null or invalid, so it should be a valid parameter type. - ErrorUtilities.VerifyThrow( - TaskParameterTypeVerifier.IsValidInputParameter(wrappedParameterType) || TaskParameterTypeVerifier.IsValidOutputParameter(wrappedParameterType), - "How did we manage to get a task parameter of type {0} that isn't a valid parameter type?", - wrappedParameterType); - - if (wrappedParameterType.IsArray) + else if (typeof(ITaskItem[]).IsAssignableFrom(wrappedParameterType)) { - TypeCode typeCode = Type.GetTypeCode(wrappedParameterType.GetElementType()); - if (typeCode != TypeCode.Object && typeCode != TypeCode.DBNull) - { - _parameterType = TaskParameterType.PrimitiveTypeArray; - _parameterTypeCode = typeCode; - _wrappedParameter = wrappedParameter; - } - else if (typeof(ITaskItem[]).IsAssignableFrom(wrappedParameterType)) - { - _parameterType = TaskParameterType.ITaskItemArray; - ITaskItem[] inputAsITaskItemArray = (ITaskItem[])wrappedParameter; - ITaskItem[] taskItemArrayParameter = new ITaskItem[inputAsITaskItemArray.Length]; + _parameterType = TaskParameterType.ITaskItemArray; + ITaskItem[] inputAsITaskItemArray = (ITaskItem[])wrappedParameter; + ITaskItem[] taskItemArrayParameter = new ITaskItem[inputAsITaskItemArray.Length]; - for (int i = 0; i < inputAsITaskItemArray.Length; i++) + for (int i = 0; i < inputAsITaskItemArray.Length; i++) + { + if (inputAsITaskItemArray[i] != null) { - if (inputAsITaskItemArray[i] != null) - { - taskItemArrayParameter[i] = new TaskParameterTaskItem(inputAsITaskItemArray[i]); - } + taskItemArrayParameter[i] = new TaskParameterTaskItem(inputAsITaskItemArray[i]); } - - _wrappedParameter = taskItemArrayParameter; - } - else if (wrappedParameterType.GetElementType().IsValueType) - { - _parameterType = TaskParameterType.ValueTypeArray; - _wrappedParameter = wrappedParameter; - } - else - { - ErrorUtilities.ThrowInternalErrorUnreachable(); } + + _wrappedParameter = taskItemArrayParameter; + } + else if (wrappedParameterType.GetElementType().IsValueType) + { + _parameterType = TaskParameterType.ValueTypeArray; + _wrappedParameter = wrappedParameter; } else { - // scalar parameter - // Preserve enums as strings: the enum type itself may not - // be loaded on the other side of the serialization, but - // we would convert to string anyway after pulling the - // task output into a property or item. - if (wrappedParameterType.IsEnum) - { - wrappedParameter = (string)Convert.ChangeType(wrappedParameter, typeof(string), CultureInfo.InvariantCulture); - wrappedParameterType = typeof(string); - } + ErrorUtilities.ThrowInternalErrorUnreachable(); + } + } + else + { + // scalar parameter + // Preserve enums as strings: the enum type itself may not + // be loaded on the other side of the serialization, but + // we would convert to string anyway after pulling the + // task output into a property or item. + if (wrappedParameterType.IsEnum) + { + wrappedParameter = (string)Convert.ChangeType(wrappedParameter, typeof(string), CultureInfo.InvariantCulture); + wrappedParameterType = typeof(string); + } - TypeCode typeCode = Type.GetTypeCode(wrappedParameterType); - if (typeCode != TypeCode.Object && typeCode != TypeCode.DBNull) + TypeCode typeCode = Type.GetTypeCode(wrappedParameterType); + if (typeCode != TypeCode.Object && typeCode != TypeCode.DBNull) + { + _parameterType = TaskParameterType.PrimitiveType; + _parameterTypeCode = typeCode; + _wrappedParameter = wrappedParameter; + } + else if (typeof(ITaskItem).IsAssignableFrom(wrappedParameterType)) + { + _parameterType = TaskParameterType.ITaskItem; + _wrappedParameter = new TaskParameterTaskItem((ITaskItem)wrappedParameter); + } + else if (wrappedParameterType.IsValueType) + { + _parameterType = TaskParameterType.ValueType; + _wrappedParameter = wrappedParameter; + } + else + { + ErrorUtilities.ThrowInternalErrorUnreachable(); + } + } + } + + /// + /// Constructor for deserialization. + /// + private TaskParameter() + { + } + + /// + /// Gets the of the wrapped parameter. + /// + public TaskParameterType ParameterType => _parameterType; + + /// + /// Gets the of the wrapper parameter if it's a primitive or array of primitives. + /// + public TypeCode ParameterTypeCode => _parameterTypeCode; + + /// + /// Gets the actual task parameter that we're wrapping. + /// + public object? WrappedParameter => _wrappedParameter; + + /// + /// TaskParameter's ToString should just pass through to whatever it's wrapping. + /// + public override string ToString() + => _wrappedParameter is null ? string.Empty : _wrappedParameter.ToString(); + + /// + /// Serialize / deserialize this item. + /// + public void Translate(ITranslator translator) + { + translator.TranslateEnum(ref _parameterType, (int)_parameterType); + + switch (_parameterType) + { + case TaskParameterType.Null: + _wrappedParameter = null; + break; + case TaskParameterType.PrimitiveType: + TranslatePrimitiveType(translator); + break; + case TaskParameterType.PrimitiveTypeArray: + TranslatePrimitiveTypeArray(translator); + break; + case TaskParameterType.ValueType: + TranslateValueType(translator); + break; + case TaskParameterType.ValueTypeArray: + TranslateValueTypeArray(translator); + break; + case TaskParameterType.ITaskItem: + TranslateITaskItem(translator); + break; + case TaskParameterType.ITaskItemArray: + TranslateITaskItemArray(translator); + break; + case TaskParameterType.Invalid: + var exceptionParam = (Exception?)_wrappedParameter; + translator.TranslateException(ref exceptionParam); + _wrappedParameter = exceptionParam; + break; + default: + ErrorUtilities.ThrowInternalErrorUnreachable(); + break; + } + } + + /// + /// Overridden to give this class infinite lease time. Otherwise we end up with a limited + /// lease (5 minutes I think) and instances can expire if they take long time processing. + /// + [SecurityCritical] + public override object? InitializeLifetimeService() => null; + + /// + /// Factory for deserialization. + /// + internal static TaskParameter FactoryForDeserialization(ITranslator translator) + { + TaskParameter taskParameter = new(); + taskParameter.Translate(translator); + return taskParameter; + } + + /// + /// Serialize / deserialize this item. + /// + private void TranslateITaskItemArray(ITranslator translator) + { + var wrappedItems = (ITaskItem[]?)_wrappedParameter; + int length = wrappedItems?.Length ?? 0; + translator.Translate(ref length); + wrappedItems ??= new ITaskItem[length]; + + for (int i = 0; i < wrappedItems.Length; i++) + { + TaskParameterTaskItem taskItem = (TaskParameterTaskItem)wrappedItems[i]; + translator.Translate(ref taskItem, TaskParameterTaskItem.FactoryForDeserialization); + wrappedItems[i] = taskItem; + } + + _wrappedParameter = wrappedItems; + } + + /// + /// Serialize / deserialize this item. + /// + private void TranslateITaskItem(ITranslator translator) + { + TaskParameterTaskItem taskItem = (TaskParameterTaskItem)_wrappedParameter!; + translator.Translate(ref taskItem, TaskParameterTaskItem.FactoryForDeserialization); + _wrappedParameter = taskItem; + } + + /// + /// Serializes or deserializes a primitive type value wrapped by this . + /// + private void TranslatePrimitiveType(ITranslator translator) + { + translator.TranslateEnum(ref _parameterTypeCode, (int)_parameterTypeCode); + + switch (_parameterTypeCode) + { + case TypeCode.Boolean: + bool boolParam = _wrappedParameter is bool wrappedBool ? wrappedBool : default; + translator.Translate(ref boolParam); + _wrappedParameter = boolParam; + break; + + case TypeCode.Byte: + byte byteParam = _wrappedParameter is byte wrappedByte ? wrappedByte : default; + translator.Translate(ref byteParam); + _wrappedParameter = byteParam; + break; + + case TypeCode.Int16: + short shortParam = _wrappedParameter is short wrappedShort ? wrappedShort : default; + translator.Translate(ref shortParam); + _wrappedParameter = shortParam; + break; + + case TypeCode.UInt16: + ushort ushortParam = _wrappedParameter is ushort wrappedUShort ? wrappedUShort : default; + translator.Translate(ref ushortParam); + _wrappedParameter = ushortParam; + break; + + case TypeCode.Int64: + long longParam = _wrappedParameter is long wrappedLong ? wrappedLong : default; + translator.Translate(ref longParam); + _wrappedParameter = longParam; + break; + + case TypeCode.Double: + double doubleParam = _wrappedParameter is double wrappedDouble ? wrappedDouble : default; + translator.Translate(ref doubleParam); + _wrappedParameter = doubleParam; + break; + + case TypeCode.String: + string? stringParam = (string?)_wrappedParameter; + translator.Translate(ref stringParam); + _wrappedParameter = stringParam; + break; + + case TypeCode.DateTime: + DateTime dateTimeParam = _wrappedParameter is DateTime wrappedDateTime ? wrappedDateTime : default; + translator.Translate(ref dateTimeParam); + _wrappedParameter = dateTimeParam; + break; + + default: + // Fall back to converting to/from string for types that don't have ITranslator support. + string? stringValue = null; + + if (translator.Mode == TranslationDirection.WriteToStream) { - _parameterType = TaskParameterType.PrimitiveType; - _parameterTypeCode = typeCode; - _wrappedParameter = wrappedParameter; + stringValue = (string)Convert.ChangeType(_wrappedParameter, typeof(string), CultureInfo.InvariantCulture); } - else if (typeof(ITaskItem).IsAssignableFrom(wrappedParameterType)) + + translator.Translate(ref stringValue); + + if (translator.Mode == TranslationDirection.ReadFromStream) { - _parameterType = TaskParameterType.ITaskItem; - _wrappedParameter = new TaskParameterTaskItem((ITaskItem)wrappedParameter); + _wrappedParameter = Convert.ChangeType(stringValue, _parameterTypeCode, CultureInfo.InvariantCulture); } - else if (wrappedParameterType.IsValueType) + + break; + } + } + + /// + /// Serializes or deserializes an array of primitive type values wrapped by this . + /// + private void TranslatePrimitiveTypeArray(ITranslator translator) + { + translator.TranslateEnum(ref _parameterTypeCode, (int)_parameterTypeCode); + + switch (_parameterTypeCode) + { + case TypeCode.Boolean: + bool[]? boolArrayParam = (bool[]?)_wrappedParameter; + translator.Translate(ref boolArrayParam); + _wrappedParameter = boolArrayParam; + break; + + case TypeCode.Int32: + int[]? intArrayParam = (int[]?)_wrappedParameter; + translator.Translate(ref intArrayParam); + _wrappedParameter = intArrayParam; + break; + + case TypeCode.String: + string[]? stringArrayParam = (string[]?)_wrappedParameter; + translator.Translate(ref stringArrayParam); + _wrappedParameter = stringArrayParam; + break; + + default: + // Fall back to converting to/from string for types that don't have ITranslator support. + if (translator.Mode == TranslationDirection.WriteToStream) { - _parameterType = TaskParameterType.ValueType; - _wrappedParameter = wrappedParameter; + Array array = (Array)_wrappedParameter!; + int length = array.Length; + + translator.Translate(ref length); + + for (int i = 0; i < length; i++) + { + string? valueString = Convert.ToString(array.GetValue(i), CultureInfo.InvariantCulture); + translator.Translate(ref valueString); + } } else { - ErrorUtilities.ThrowInternalErrorUnreachable(); + Type elementType = _parameterTypeCode switch + { + TypeCode.Char => typeof(char), + TypeCode.SByte => typeof(sbyte), + TypeCode.Byte => typeof(byte), + TypeCode.Int16 => typeof(short), + TypeCode.UInt16 => typeof(ushort), + TypeCode.UInt32 => typeof(uint), + TypeCode.Int64 => typeof(long), + TypeCode.UInt64 => typeof(ulong), + TypeCode.Single => typeof(float), + TypeCode.Double => typeof(double), + TypeCode.Decimal => typeof(decimal), + TypeCode.DateTime => typeof(DateTime), + _ => throw new NotImplementedException(), + }; + + int length = 0; + translator.Translate(ref length); + + Array array = Array.CreateInstance(elementType, length); + for (int i = 0; i < length; i++) + { + string? valueString = null; + translator.Translate(ref valueString); + array.SetValue(Convert.ChangeType(valueString, _parameterTypeCode, CultureInfo.InvariantCulture), i); + } + + _wrappedParameter = array; } - } + + break; } + } - /// - /// Constructor for deserialization. - /// - private TaskParameter() + /// + /// Serializes or deserializes the value type instance wrapped by this . + /// + /// + /// The value type is converted to/from string using the class. Note that we require + /// task parameter types to be so this conversion is guaranteed to work for parameters + /// that have made it this far. + /// + private void TranslateValueType(ITranslator translator) + { + string? valueString = null; + + if (translator.Mode == TranslationDirection.WriteToStream) { + valueString = (string)Convert.ChangeType(_wrappedParameter, typeof(string), CultureInfo.InvariantCulture); } - /// - /// The TaskParameterType of the wrapped parameter. - /// - public TaskParameterType ParameterType => _parameterType; + translator.Translate(ref valueString); - /// - /// The of the wrapper parameter if it's a primitive or array of primitives. - /// - public TypeCode ParameterTypeCode => _parameterTypeCode; + if (translator.Mode == TranslationDirection.ReadFromStream) + { + // We don't know how to convert the string back to the original value type. This is fine because output + // task parameters are anyway converted to strings by the engine (see TaskExecutionHost.GetValueOutputs) + // and input task parameters of custom value types are not supported. + _wrappedParameter = valueString; + } + } - /// - /// The actual task parameter that we're wrapping. - /// - public object WrappedParameter => _wrappedParameter; + /// + /// Serializes or deserializes the value type array instance wrapped by this . + /// + /// + /// The array is assumed to be non-null. + /// + private void TranslateValueTypeArray(ITranslator translator) + { + if (translator.Mode == TranslationDirection.WriteToStream) + { + var array = (Array)_wrappedParameter!; + int length = array.Length; - /// - /// TaskParameter's ToString should just pass through to whatever it's wrapping. - /// - public override string ToString() + translator.Translate(ref length); + + for (int i = 0; i < length; i++) + { + string? valueString = Convert.ToString(array.GetValue(i), CultureInfo.InvariantCulture); + translator.Translate(ref valueString); + } + } + else { - return (WrappedParameter == null) ? String.Empty : WrappedParameter.ToString(); + int length = 0; + translator.Translate(ref length); + + string?[] stringArray = new string[length]; + for (int i = 0; i < length; i++) + { + translator.Translate(ref stringArray[i]); + } + + // We don't know how to convert the string array back to the original value type array. + // This is fine because the engine would eventually convert it to strings anyway. + _wrappedParameter = stringArray; } + } - /// - /// Serialize / deserialize this item. - /// - public void Translate(ITranslator translator) - { - translator.TranslateEnum(ref _parameterType, (int)_parameterType); + /// + /// Super simple ITaskItem derivative that we can use as a container for read items. + /// + private class TaskParameterTaskItem : MarshalByRefObject, ITaskItem, ITranslatable + { + private string? _escapedItemSpec; + private string? _escapedDefiningProject; + private Dictionary? _customEscapedMetadata; + private string? _fullPath; - switch (_parameterType) + internal TaskParameterTaskItem(ITaskItem copyFrom) + { + if (copyFrom is TaskParameterTaskItem taskParameterTaskItem) + { + _escapedItemSpec = taskParameterTaskItem._escapedItemSpec; + _escapedDefiningProject = taskParameterTaskItem.GetMetadataValueEscaped(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath); + _customEscapedMetadata = new(taskParameterTaskItem._customEscapedMetadata); + } + else { - case TaskParameterType.Null: - _wrappedParameter = null; - break; - case TaskParameterType.PrimitiveType: - TranslatePrimitiveType(translator); - break; - case TaskParameterType.PrimitiveTypeArray: - TranslatePrimitiveTypeArray(translator); - break; - case TaskParameterType.ValueType: - TranslateValueType(translator); - break; - case TaskParameterType.ValueTypeArray: - TranslateValueTypeArray(translator); - break; - case TaskParameterType.ITaskItem: - TranslateITaskItem(translator); - break; - case TaskParameterType.ITaskItemArray: - TranslateITaskItemArray(translator); - break; - case TaskParameterType.Invalid: - Exception exceptionParam = (Exception)_wrappedParameter; - translator.TranslateException(ref exceptionParam); - _wrappedParameter = exceptionParam; - break; - default: - ErrorUtilities.ThrowInternalErrorUnreachable(); - break; + // If we don't have ITaskItem2 to fall back on, we have to make do with the fact that + // CloneCustomMetadata, GetMetadata, & ItemSpec returns unescaped values, and + // TaskParameterTaskItem's constructor expects escaped values, so escaping them all + // is the closest approximation to correct we can get. + _escapedItemSpec = EscapingUtilities.Escape(copyFrom.ItemSpec); + _escapedDefiningProject = EscapingUtilities.EscapeWithCaching(copyFrom.GetMetadata(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath)); + + IDictionary customMetadata = copyFrom.CloneCustomMetadata(); + _customEscapedMetadata = new(StringComparer.OrdinalIgnoreCase); + + if (customMetadata?.Count > 0) + { + foreach (DictionaryEntry entry in customMetadata) + { + _customEscapedMetadata[(string)entry.Key] = EscapingUtilities.Escape((string)entry.Value) ?? string.Empty; + } + } } + + ErrorUtilities.VerifyThrowInternalNull(_escapedItemSpec); + } + + private TaskParameterTaskItem() + { } /// @@ -257,543 +565,201 @@ public void Translate(ITranslator translator) /// lease (5 minutes I think) and instances can expire if they take long time processing. /// [SecurityCritical] - public override object InitializeLifetimeService() - { - // null means infinite lease time - return null; - } + public override object? InitializeLifetimeService() => null; /// - /// Factory for deserialization. + /// Gets or sets the item "specification" e.g. for disk-based items this would be the file path. /// - internal static TaskParameter FactoryForDeserialization(ITranslator translator) + /// + /// This should be named "EvaluatedInclude" but that would be a breaking change to this interface. + /// + /// The item-spec string. + public string ItemSpec { - TaskParameter taskParameter = new(); - taskParameter.Translate(translator); - return taskParameter; + get => _escapedItemSpec is null ? string.Empty : EscapingUtilities.UnescapeAll(_escapedItemSpec); + set => _escapedItemSpec = value; } /// - /// Serialize / deserialize this item. + /// Gets the names of all the metadata on the item. + /// Includes the built-in metadata like "FullPath". /// - private void TranslateITaskItemArray(ITranslator translator) + /// The list of metadata names. + public ICollection MetadataNames { - ITaskItem[] wrappedItems = (ITaskItem[])_wrappedParameter; - int length = wrappedItems?.Length ?? 0; - translator.Translate(ref length); - wrappedItems ??= new ITaskItem[length]; - - for (int i = 0; i < wrappedItems.Length; i++) + get { - TaskParameterTaskItem taskItem = (TaskParameterTaskItem)wrappedItems[i]; - translator.Translate(ref taskItem, TaskParameterTaskItem.FactoryForDeserialization); - wrappedItems[i] = taskItem; - } + List result = _customEscapedMetadata is not null + ? [.. _customEscapedMetadata.Keys] + : []; - _wrappedParameter = wrappedItems; + result.AddRange(FileUtilities.ItemSpecModifiers.All); + + return result; + } } /// - /// Serialize / deserialize this item. + /// Gets the number of pieces of metadata on the item. Includes + /// both custom and built-in metadata. Used only for unit testing. /// - private void TranslateITaskItem(ITranslator translator) + /// Count of pieces of metadata. + public int MetadataCount { - TaskParameterTaskItem taskItem = (TaskParameterTaskItem)_wrappedParameter; - translator.Translate(ref taskItem, TaskParameterTaskItem.FactoryForDeserialization); - _wrappedParameter = taskItem; + get + { + int count = _customEscapedMetadata?.Count ?? 0; + return count + FileUtilities.ItemSpecModifiers.All.Length; + } } /// - /// Serializes or deserializes a primitive type value wrapped by this . + /// Allows the values of metadata on the item to be queried. /// - private void TranslatePrimitiveType(ITranslator translator) + /// The name of the metadata to retrieve. + /// The value of the specified metadata. + public string GetMetadata(string metadataName) { - translator.TranslateEnum(ref _parameterTypeCode, (int)_parameterTypeCode); - - switch (_parameterTypeCode) - { - case TypeCode.Boolean: - bool boolParam = _wrappedParameter is bool wrappedBool ? wrappedBool : default; - translator.Translate(ref boolParam); - _wrappedParameter = boolParam; - break; - - case TypeCode.Byte: - byte byteParam = _wrappedParameter is byte wrappedByte ? wrappedByte : default; - translator.Translate(ref byteParam); - _wrappedParameter = byteParam; - break; - - case TypeCode.Int16: - short shortParam = _wrappedParameter is short wrappedShort ? wrappedShort : default; - translator.Translate(ref shortParam); - _wrappedParameter = shortParam; - break; - - case TypeCode.UInt16: - ushort ushortParam = _wrappedParameter is ushort wrappedUShort ? wrappedUShort : default; - translator.Translate(ref ushortParam); - _wrappedParameter = ushortParam; - break; - - case TypeCode.Int64: - long longParam = _wrappedParameter is long wrappedLong ? wrappedLong : default; - translator.Translate(ref longParam); - _wrappedParameter = longParam; - break; - - case TypeCode.Double: - double doubleParam = _wrappedParameter is double wrappedDouble ? wrappedDouble : default; - translator.Translate(ref doubleParam); - _wrappedParameter = doubleParam; - break; - - case TypeCode.String: - string stringParam = (string)_wrappedParameter; - translator.Translate(ref stringParam); - _wrappedParameter = stringParam; - break; - - case TypeCode.DateTime: - DateTime dateTimeParam = _wrappedParameter is DateTime wrappedDateTime ? wrappedDateTime : default; - translator.Translate(ref dateTimeParam); - _wrappedParameter = dateTimeParam; - break; - - default: - // Fall back to converting to/from string for types that don't have ITranslator support. - string stringValue = null; - if (translator.Mode == TranslationDirection.WriteToStream) - { - stringValue = (string)Convert.ChangeType(_wrappedParameter, typeof(string), CultureInfo.InvariantCulture); - } - - translator.Translate(ref stringValue); - - if (translator.Mode == TranslationDirection.ReadFromStream) - { - _wrappedParameter = Convert.ChangeType(stringValue, _parameterTypeCode, CultureInfo.InvariantCulture); - } - break; - } + string metadataValue = GetMetadataValueEscaped(metadataName); + return EscapingUtilities.UnescapeAll(metadataValue); } /// - /// Serializes or deserializes an array of primitive type values wrapped by this . + /// Allows a piece of custom metadata to be set on the item. /// - private void TranslatePrimitiveTypeArray(ITranslator translator) + /// The name of the metadata to set. + /// The metadata value. + public void SetMetadata(string metadataName, string metadataValue) { - translator.TranslateEnum(ref _parameterTypeCode, (int)_parameterTypeCode); - - switch (_parameterTypeCode) - { - case TypeCode.Boolean: - bool[] boolArrayParam = (bool[])_wrappedParameter; - translator.Translate(ref boolArrayParam); - _wrappedParameter = boolArrayParam; - break; - - case TypeCode.Int32: - int[] intArrayParam = (int[])_wrappedParameter; - translator.Translate(ref intArrayParam); - _wrappedParameter = intArrayParam; - break; - - case TypeCode.String: - string[] stringArrayParam = (string[])_wrappedParameter; - translator.Translate(ref stringArrayParam); - _wrappedParameter = stringArrayParam; - break; - - default: - // Fall back to converting to/from string for types that don't have ITranslator support. - if (translator.Mode == TranslationDirection.WriteToStream) - { - Array array = (Array)_wrappedParameter; - int length = array.Length; + ErrorUtilities.VerifyThrowArgumentLength(metadataName); - translator.Translate(ref length); + // Non-derivable metadata can only be set at construction time. + // That's why this is IsItemSpecModifier and not IsDerivableItemSpecModifier. + ErrorUtilities.VerifyThrowArgument( + !FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(metadataName), + SR.Shared_CannotChangeItemSpecModifiers, + metadataName); - for (int i = 0; i < length; i++) - { - string valueString = Convert.ToString(array.GetValue(i), CultureInfo.InvariantCulture); - translator.Translate(ref valueString); - } - } - else - { - Type elementType = _parameterTypeCode switch - { - TypeCode.Char => typeof(char), - TypeCode.SByte => typeof(sbyte), - TypeCode.Byte => typeof(byte), - TypeCode.Int16 => typeof(short), - TypeCode.UInt16 => typeof(ushort), - TypeCode.UInt32 => typeof(uint), - TypeCode.Int64 => typeof(long), - TypeCode.UInt64 => typeof(ulong), - TypeCode.Single => typeof(float), - TypeCode.Double => typeof(double), - TypeCode.Decimal => typeof(decimal), - TypeCode.DateTime => typeof(DateTime), - _ => throw new NotImplementedException(), - }; - - int length = 0; - translator.Translate(ref length); - - Array array = Array.CreateInstance(elementType, length); - for (int i = 0; i < length; i++) - { - string valueString = null; - translator.Translate(ref valueString); - array.SetValue(Convert.ChangeType(valueString, _parameterTypeCode, CultureInfo.InvariantCulture), i); - } - _wrappedParameter = array; - } - break; - } + _customEscapedMetadata ??= new(StringComparer.OrdinalIgnoreCase); + _customEscapedMetadata[metadataName] = metadataValue ?? string.Empty; } /// - /// Serializes or deserializes the value type instance wrapped by this . + /// Allows the removal of custom metadata set on the item. /// - /// - /// The value type is converted to/from string using the class. Note that we require - /// task parameter types to be so this conversion is guaranteed to work for parameters - /// that have made it this far. - /// - private void TranslateValueType(ITranslator translator) + /// The name of the metadata to remove. + public void RemoveMetadata(string metadataName) { - string valueString = null; - if (translator.Mode == TranslationDirection.WriteToStream) - { - valueString = (string)Convert.ChangeType(_wrappedParameter, typeof(string), CultureInfo.InvariantCulture); - } + ErrorUtilities.VerifyThrowArgumentNull(metadataName); + ErrorUtilities.VerifyThrowArgument( + !FileUtilities.ItemSpecModifiers.IsItemSpecModifier(metadataName), + SR.Shared_CannotChangeItemSpecModifiers, + metadataName); - translator.Translate(ref valueString); - - if (translator.Mode == TranslationDirection.ReadFromStream) + if (_customEscapedMetadata == null) { - // We don't know how to convert the string back to the original value type. This is fine because output - // task parameters are anyway converted to strings by the engine (see TaskExecutionHost.GetValueOutputs) - // and input task parameters of custom value types are not supported. - _wrappedParameter = valueString; + return; } + + _customEscapedMetadata.Remove(metadataName); } /// - /// Serializes or deserializes the value type array instance wrapped by this . + /// Allows custom metadata on the item to be copied to another item. /// /// - /// The array is assumed to be non-null. + /// RECOMMENDED GUIDELINES FOR METHOD IMPLEMENTATIONS: + /// 1) this method should NOT copy over the item-spec. + /// 2) if a particular piece of metadata already exists on the destination item, it should NOT be overwritten. + /// 3) if there are pieces of metadata on the item that make no semantic sense on the destination item, they should NOT be copied. /// - private void TranslateValueTypeArray(ITranslator translator) + /// The item to copy metadata to. + public void CopyMetadataTo(ITaskItem destinationItem) { - if (translator.Mode == TranslationDirection.WriteToStream) - { - Array array = (Array)_wrappedParameter; - int length = array.Length; - - translator.Translate(ref length); - - for (int i = 0; i < length; i++) - { - string valueString = Convert.ToString(array.GetValue(i), CultureInfo.InvariantCulture); - translator.Translate(ref valueString); - } - } - else - { - int length = 0; - translator.Translate(ref length); + ErrorUtilities.VerifyThrowArgumentNull(destinationItem); - string[] stringArray = new string[length]; - for (int i = 0; i < length; i++) - { - translator.Translate(ref stringArray[i]); - } + // also copy the original item-spec under a "magic" metadata -- this is useful for tasks that forward metadata + // between items, and need to know the source item where the metadata came from + string originalItemSpec = destinationItem.GetMetadata("OriginalItemSpec"); - // We don't know how to convert the string array back to the original value type array. - // This is fine because the engine would eventually convert it to strings anyway. - _wrappedParameter = stringArray; - } - } - - /// - /// Super simple ITaskItem derivative that we can use as a container for read items. - /// - private class TaskParameterTaskItem : MarshalByRefObject, ITaskItem, ITranslatable - { - /// - /// The item spec - /// - private string _escapedItemSpec = null; - - /// - /// The full path to the project that originally defined this item. - /// - private string _escapedDefiningProject = null; - - /// - /// The custom metadata - /// - private Dictionary _customEscapedMetadata = null; - - /// - /// Cache for fullpath metadata - /// - private string _fullPath; - - /// - /// Constructor for serialization - /// - internal TaskParameterTaskItem(ITaskItem copyFrom) + if (_customEscapedMetadata != null) { - if (copyFrom is TaskParameterTaskItem taskParameterTaskItem) + foreach (KeyValuePair entry in _customEscapedMetadata) { - _escapedItemSpec = taskParameterTaskItem._escapedItemSpec; - _escapedDefiningProject = taskParameterTaskItem.GetMetadataValueEscaped(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath); - _customEscapedMetadata = new(taskParameterTaskItem._customEscapedMetadata); - } - else - { - // If we don't have ITaskItem2 to fall back on, we have to make do with the fact that - // CloneCustomMetadata, GetMetadata, & ItemSpec returns unescaped values, and - // TaskParameterTaskItem's constructor expects escaped values, so escaping them all - // is the closest approximation to correct we can get. - _escapedItemSpec = EscapingUtilities.Escape(copyFrom.ItemSpec); - _escapedDefiningProject = EscapingUtilities.EscapeWithCaching(copyFrom.GetMetadata(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath)); + string value = destinationItem.GetMetadata(entry.Key); - IDictionary customMetadata = copyFrom.CloneCustomMetadata(); - _customEscapedMetadata = new Dictionary(StringComparer.OrdinalIgnoreCase); - - if (customMetadata?.Count > 0) + if (string.IsNullOrEmpty(value)) { - foreach (DictionaryEntry entry in customMetadata) - { - _customEscapedMetadata[(string)entry.Key] = EscapingUtilities.Escape((string)entry.Value) ?? string.Empty; - } + destinationItem.SetMetadata(entry.Key, entry.Value); } } - - ErrorUtilities.VerifyThrowInternalNull(_escapedItemSpec); - } - - private TaskParameterTaskItem() - { - } - - /// - /// Gets or sets the item "specification" e.g. for disk-based items this would be the file path. - /// - /// - /// This should be named "EvaluatedInclude" but that would be a breaking change to this interface. - /// - /// The item-spec string. - public string ItemSpec - { - get - { - return (_escapedItemSpec == null) ? String.Empty : EscapingUtilities.UnescapeAll(_escapedItemSpec); - } - - set - { - _escapedItemSpec = value; - } - } - - /// - /// Gets the names of all the metadata on the item. - /// Includes the built-in metadata like "FullPath". - /// - /// The list of metadata names. - public ICollection MetadataNames - { - get - { - List metadataNames = (_customEscapedMetadata == null) ? new List() : new List(_customEscapedMetadata.Keys); - metadataNames.AddRange(FileUtilities.ItemSpecModifiers.All); - - return metadataNames; - } } - /// - /// Gets the number of pieces of metadata on the item. Includes - /// both custom and built-in metadata. Used only for unit testing. - /// - /// Count of pieces of metadata. - public int MetadataCount + if (string.IsNullOrEmpty(originalItemSpec)) { - get - { - int count = (_customEscapedMetadata == null) ? 0 : _customEscapedMetadata.Count; - return count + FileUtilities.ItemSpecModifiers.All.Length; - } - } - - /// - /// Allows the values of metadata on the item to be queried. - /// - /// The name of the metadata to retrieve. - /// The value of the specified metadata. - public string GetMetadata(string metadataName) - { - string metadataValue = GetMetadataValueEscaped(metadataName); - return EscapingUtilities.UnescapeAll(metadataValue); + destinationItem.SetMetadata("OriginalItemSpec", EscapingUtilities.Escape(ItemSpec)); } + } - /// - /// Allows a piece of custom metadata to be set on the item. - /// - /// The name of the metadata to set. - /// The metadata value. - public void SetMetadata(string metadataName, string metadataValue) - { - ErrorUtilities.VerifyThrowArgumentLength(metadataName); - - // Non-derivable metadata can only be set at construction time. - // That's why this is IsItemSpecModifier and not IsDerivableItemSpecModifier. - ErrorUtilities.VerifyThrowArgument( - !FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(metadataName), - SR.Shared_CannotChangeItemSpecModifiers, - metadataName); - - _customEscapedMetadata ??= new Dictionary(StringComparer.OrdinalIgnoreCase); - - _customEscapedMetadata[metadataName] = metadataValue ?? String.Empty; - } + /// + /// Get the collection of custom metadata. This does not include built-in metadata. + /// + /// + /// RECOMMENDED GUIDELINES FOR METHOD IMPLEMENTATIONS: + /// 1) this method should return a clone of the metadata. + /// 2) writing to this dictionary should not be reflected in the underlying item. + /// + /// Dictionary of cloned metadata. + public IDictionary CloneCustomMetadata() + { + Dictionary clonedMetadata = new(StringComparer.OrdinalIgnoreCase); - /// - /// Allows the removal of custom metadata set on the item. - /// - /// The name of the metadata to remove. - public void RemoveMetadata(string metadataName) + if (_customEscapedMetadata != null) { - ErrorUtilities.VerifyThrowArgumentNull(metadataName); - ErrorUtilities.VerifyThrowArgument( - !FileUtilities.ItemSpecModifiers.IsItemSpecModifier(metadataName), - SR.Shared_CannotChangeItemSpecModifiers, - metadataName); - - if (_customEscapedMetadata == null) + foreach (KeyValuePair metadatum in _customEscapedMetadata) { - return; + clonedMetadata.Add(metadatum.Key, EscapingUtilities.UnescapeAll(metadatum.Value)); } - - _customEscapedMetadata.Remove(metadataName); } - /// - /// Allows custom metadata on the item to be copied to another item. - /// - /// - /// RECOMMENDED GUIDELINES FOR METHOD IMPLEMENTATIONS: - /// 1) this method should NOT copy over the item-spec - /// 2) if a particular piece of metadata already exists on the destination item, it should NOT be overwritten - /// 3) if there are pieces of metadata on the item that make no semantic sense on the destination item, they should NOT be copied - /// - /// The item to copy metadata to. - public void CopyMetadataTo(ITaskItem destinationItem) - { - ErrorUtilities.VerifyThrowArgumentNull(destinationItem); - - // also copy the original item-spec under a "magic" metadata -- this is useful for tasks that forward metadata - // between items, and need to know the source item where the metadata came from - string originalItemSpec = destinationItem.GetMetadata("OriginalItemSpec"); - - if (_customEscapedMetadata != null) - { - foreach (KeyValuePair entry in _customEscapedMetadata) - { - string value = destinationItem.GetMetadata(entry.Key); + return clonedMetadata; + } - if (string.IsNullOrEmpty(value)) - { - destinationItem.SetMetadata(entry.Key, entry.Value); - } - } - } + private string GetMetadataValueEscaped(string metadataName) + { + ErrorUtilities.VerifyThrowArgumentNull(metadataName); - if (string.IsNullOrEmpty(originalItemSpec)) - { - destinationItem.SetMetadata("OriginalItemSpec", EscapingUtilities.Escape(ItemSpec)); - } - } + string? metadataValue = null; - /// - /// Get the collection of custom metadata. This does not include built-in metadata. - /// - /// - /// RECOMMENDED GUIDELINES FOR METHOD IMPLEMENTATIONS: - /// 1) this method should return a clone of the metadata - /// 2) writing to this dictionary should not be reflected in the underlying item. - /// - /// Dictionary of cloned metadata - public IDictionary CloneCustomMetadata() + if (FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(metadataName)) { - IDictionary clonedMetadata = new Dictionary(StringComparer.OrdinalIgnoreCase); - - if (_customEscapedMetadata != null) - { - foreach (KeyValuePair metadatum in _customEscapedMetadata) - { - clonedMetadata.Add(metadatum.Key, EscapingUtilities.UnescapeAll(metadatum.Value)); - } - } - - return (IDictionary)clonedMetadata; + // FileUtilities.GetItemSpecModifier is expecting escaped data, which we assume we already are. + // Passing in a null for currentDirectory indicates we are already in the correct current directory + metadataValue = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(null, _escapedItemSpec, _escapedDefiningProject, metadataName, ref _fullPath); } - - /// - /// Overridden to give this class infinite lease time. Otherwise we end up with a limited - /// lease (5 minutes I think) and instances can expire if they take long time processing. - /// - [SecurityCritical] - public override object InitializeLifetimeService() + else if (_customEscapedMetadata != null) { - // null means infinite lease time - return null; + _customEscapedMetadata.TryGetValue(metadataName, out metadataValue); } - private string GetMetadataValueEscaped(string metadataName) - { - ErrorUtilities.VerifyThrowArgumentNull(metadataName); - - string metadataValue = null; - - if (FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(metadataName)) - { - // FileUtilities.GetItemSpecModifier is expecting escaped data, which we assume we already are. - // Passing in a null for currentDirectory indicates we are already in the correct current directory - metadataValue = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(null, _escapedItemSpec, _escapedDefiningProject, metadataName, ref _fullPath); - } - else if (_customEscapedMetadata != null) - { - _customEscapedMetadata.TryGetValue(metadataName, out metadataValue); - } - - return metadataValue ?? String.Empty; - } + return metadataValue ?? string.Empty; + } - public void Translate(ITranslator translator) - { - translator.Translate(ref _escapedItemSpec); - translator.Translate(ref _escapedDefiningProject); - translator.TranslateDictionary(ref _customEscapedMetadata, StringComparer.OrdinalIgnoreCase); + public void Translate(ITranslator translator) + { + translator.Translate(ref _escapedItemSpec); + translator.Translate(ref _escapedDefiningProject); + translator.TranslateDictionary(ref _customEscapedMetadata, StringComparer.OrdinalIgnoreCase); - ErrorUtilities.VerifyThrowInternalNull(_escapedItemSpec); - ErrorUtilities.VerifyThrowInternalNull(_customEscapedMetadata); - } + ErrorUtilities.VerifyThrowInternalNull(_escapedItemSpec); + ErrorUtilities.VerifyThrowInternalNull(_customEscapedMetadata); + } - internal static TaskParameterTaskItem FactoryForDeserialization(ITranslator translator) - { - TaskParameterTaskItem taskItem = new(); - taskItem.Translate(translator); - return taskItem; - } + internal static TaskParameterTaskItem FactoryForDeserialization(ITranslator translator) + { + var taskItem = new TaskParameterTaskItem(); + taskItem.Translate(translator); + return taskItem; } } } From f64ad9d39b4073dd62acc8ff0c736d1ae585138b Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 5 Feb 2026 15:39:50 -0800 Subject: [PATCH 108/136] MSBuildTaskHost: Clean up TaskParameterTypeVerifier - File-scoped namespace - Expression-bodied members - Enable nullability --- .../BackEnd/TaskParameterTypeVerifier.cs | 94 ++++++++----------- 1 file changed, 39 insertions(+), 55 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/TaskParameterTypeVerifier.cs b/src/MSBuildTaskHost/BackEnd/TaskParameterTypeVerifier.cs index e95cc309118..2c5c9989f09 100644 --- a/src/MSBuildTaskHost/BackEnd/TaskParameterTypeVerifier.cs +++ b/src/MSBuildTaskHost/BackEnd/TaskParameterTypeVerifier.cs @@ -4,68 +4,52 @@ using System; using Microsoft.Build.Framework; -#nullable disable +namespace Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.TaskHost.BackEnd +/// +/// Provide a class which can verify the correct type for both input and output parameters. +/// +internal static class TaskParameterTypeVerifier { /// - /// Provide a class which can verify the correct type for both input and output parameters. + /// Is the parameter type a valid scalar input value. /// - internal static class TaskParameterTypeVerifier - { - /// - /// Is the parameter type a valid scalar input value - /// - internal static bool IsValidScalarInputParameter(Type parameterType) => - parameterType.IsValueType || parameterType == typeof(string) || parameterType == typeof(ITaskItem); + internal static bool IsValidScalarInputParameter(Type parameterType) + => parameterType.IsValueType || parameterType == typeof(string) || parameterType == typeof(ITaskItem); - /// - /// Is the passed in parameterType a valid vector input parameter - /// - internal static bool IsValidVectorInputParameter(Type parameterType) - { - bool result = (parameterType.IsArray && parameterType.GetElementType().IsValueType) || - parameterType == typeof(string[]) || - parameterType == typeof(ITaskItem[]); - return result; - } + /// + /// Is the passed in parameterType a valid vector input parameter. + /// + internal static bool IsValidVectorInputParameter(Type parameterType) + => (parameterType.IsArray && parameterType.GetElementType().IsValueType) || + parameterType == typeof(string[]) || + parameterType == typeof(ITaskItem[]); - /// - /// Is the passed in value type assignable to an ITask or Itask[] object - /// - internal static bool IsAssignableToITask(Type parameterType) - { - bool result = typeof(ITaskItem[]).IsAssignableFrom(parameterType) || /* ITaskItem array or derived type, or */ - typeof(ITaskItem).IsAssignableFrom(parameterType); /* ITaskItem or derived type */ - return result; - } + /// + /// Is the passed in value type assignable to an ITask or ITask[] object. + /// + internal static bool IsAssignableToITask(Type parameterType) + => typeof(ITaskItem[]).IsAssignableFrom(parameterType) || // ITaskItem array or derived type, or + typeof(ITaskItem).IsAssignableFrom(parameterType); // ITaskItem or derived type - /// - /// Is the passed parameter a valid value type output parameter - /// - internal static bool IsValueTypeOutputParameter(Type parameterType) - { - bool result = (parameterType.IsArray && parameterType.GetElementType().IsValueType) || /* array of value types, or */ - parameterType == typeof(string[]) || /* string array, or */ - parameterType.IsValueType || /* value type, or */ - parameterType == typeof(string); /* string */ - return result; - } + /// + /// Is the passed parameter a valid value type output parameter. + /// + internal static bool IsValueTypeOutputParameter(Type parameterType) + => (parameterType.IsArray && parameterType.GetElementType().IsValueType) || // array of value types, or + parameterType == typeof(string[]) || // string array, or + parameterType.IsValueType || // value type, or + parameterType == typeof(string); // string - /// - /// Is the parameter type a valid scalar or value type input parameter - /// - internal static bool IsValidInputParameter(Type parameterType) - { - return IsValidScalarInputParameter(parameterType) || IsValidVectorInputParameter(parameterType); - } + /// + /// Is the parameter type a valid scalar or value type input parameter. + /// + internal static bool IsValidInputParameter(Type parameterType) + => IsValidScalarInputParameter(parameterType) || IsValidVectorInputParameter(parameterType); - /// - /// Is the parameter type a valid scalar or value type output parameter - /// - internal static bool IsValidOutputParameter(Type parameterType) - { - return IsValueTypeOutputParameter(parameterType) || IsAssignableToITask(parameterType); - } - } + /// + /// Is the parameter type a valid scalar or value type output parameter. + /// + internal static bool IsValidOutputParameter(Type parameterType) + => IsValueTypeOutputParameter(parameterType) || IsAssignableToITask(parameterType); } From 7cfbb7d9b22174ecead2a35f0c9f27b443bdf133 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 5 Feb 2026 15:44:24 -0800 Subject: [PATCH 109/136] MSBuildTaskHost: Clean up NodePacketFactory - File-scoped namespace - Expression-bodied members - Enable nullability --- .../BackEnd/NodePacketFactory.cs | 160 +++++++----------- 1 file changed, 64 insertions(+), 96 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/NodePacketFactory.cs b/src/MSBuildTaskHost/BackEnd/NodePacketFactory.cs index 5aa2ec6c265..e763efe3d85 100644 --- a/src/MSBuildTaskHost/BackEnd/NodePacketFactory.cs +++ b/src/MSBuildTaskHost/BackEnd/NodePacketFactory.cs @@ -4,124 +4,92 @@ using System.Collections.Generic; using Microsoft.Build.TaskHost.Utilities; -#nullable disable +namespace Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.TaskHost.BackEnd +/// +/// Implementation of INodePacketFactory as a helper class for classes which expose this interface publicly. +/// +internal sealed class NodePacketFactory : INodePacketFactory { /// - /// Implementation of INodePacketFactory as a helper class for classes which expose this interface publicly. + /// Mapping of packet types to factory information. /// - internal class NodePacketFactory : INodePacketFactory + private readonly Dictionary _packetFactories; + + public NodePacketFactory() { - /// - /// Mapping of packet types to factory information. - /// - private Dictionary _packetFactories; + _packetFactories = new Dictionary(); + } - /// - /// Constructor - /// - public NodePacketFactory() - { - _packetFactories = new Dictionary(); - } + /// + /// Registers a packet handler. + /// + public void RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory, INodePacketHandler handler) + => _packetFactories[packetType] = new PacketFactoryRecord(handler, factory); - #region INodePacketFactory Members + /// + /// Unregisters a packet handler. + /// + public void UnregisterPacketHandler(NodePacketType packetType) + => _packetFactories.Remove(packetType); - /// - /// Registers a packet handler - /// - public void RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory, INodePacketHandler handler) + /// + /// Creates and routes a packet with data from a binary stream. + /// + public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITranslator translator) + { + if (!_packetFactories.TryGetValue(packetType, out PacketFactoryRecord? record)) { - _packetFactories[packetType] = new PacketFactoryRecord(handler, factory); + ErrorUtilities.ThrowInternalError($"No packet handler for type {packetType}"); } - /// - /// Unregisters a packet handler. - /// - public void UnregisterPacketHandler(NodePacketType packetType) - { - _packetFactories.Remove(packetType); - } + INodePacket packet = record.DeserializePacket(translator); + record.RoutePacket(nodeId, packet); + } - /// - /// Creates and routes a packet with data from a binary stream. - /// - public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITranslator translator) + /// + /// Creates a packet with data from a binary stream. + /// + public INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator) + { + if (!_packetFactories.TryGetValue(packetType, out PacketFactoryRecord? record)) { - // PERF: Not using VerifyThrow to avoid boxing of packetType in the non-error case - if (!_packetFactories.TryGetValue(packetType, out PacketFactoryRecord record)) - { - ErrorUtilities.ThrowInternalError($"No packet handler for type {packetType}"); - } - - INodePacket packet = record.DeserializePacket(translator); - record.RoutePacket(nodeId, packet); + ErrorUtilities.ThrowInternalError($"No packet handler for type {packetType}"); } - /// - /// Creates a packet with data from a binary stream. - /// - public INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator) - { - // PERF: Not using VerifyThrow to avoid boxing of packetType in the non-error case - if (!_packetFactories.TryGetValue(packetType, out PacketFactoryRecord record)) - { - ErrorUtilities.ThrowInternalError($"No packet handler for type {packetType}"); - } - - return record.DeserializePacket(translator); - } + return record.DeserializePacket(translator); + } - /// - /// Routes the specified packet. - /// - public void RoutePacket(int nodeId, INodePacket packet) + /// + /// Routes the specified packet. + /// + public void RoutePacket(int nodeId, INodePacket packet) + { + if (!_packetFactories.TryGetValue(packet.Type, out PacketFactoryRecord record)) { - // PERF: Not using VerifyThrow to avoid boxing of packetType in the non-error case - if (!_packetFactories.TryGetValue(packet.Type, out PacketFactoryRecord record)) - { - ErrorUtilities.ThrowInternalError($"No packet handler for type {packet.Type}"); - } - - record.RoutePacket(nodeId, packet); + ErrorUtilities.ThrowInternalError($"No packet handler for type {packet.Type}"); } - #endregion + record.RoutePacket(nodeId, packet); + } + /// + /// A record for a packet factory. + /// + /// The handler to invoke when the packet is deserialized. + /// The method used to construct a packet from a translator stream. + private sealed class PacketFactoryRecord(INodePacketHandler handler, NodePacketFactoryMethod factoryMethod) + { /// - /// A record for a packet factory + /// Creates a packet from a binary stream. /// - private class PacketFactoryRecord - { - /// - /// The handler to invoke when the packet is deserialized. - /// - private readonly INodePacketHandler _handler; - - /// - /// The method used to construct a packet from a translator stream. - /// - private readonly NodePacketFactoryMethod _factoryMethod; + public INodePacket DeserializePacket(ITranslator translator) + => factoryMethod(translator); - /// - /// Constructor. - /// - public PacketFactoryRecord(INodePacketHandler handler, NodePacketFactoryMethod factoryMethod) - { - _handler = handler; - _factoryMethod = factoryMethod; - } - - /// - /// Creates a packet from a binary stream. - /// - public INodePacket DeserializePacket(ITranslator translator) => _factoryMethod(translator); - - /// - /// Routes the packet to the correct destination. - /// - public void RoutePacket(int nodeId, INodePacket packet) => _handler.PacketReceived(nodeId, packet); - } + /// + /// Routes the packet to the correct destination. + /// + public void RoutePacket(int nodeId, INodePacket packet) + => handler.PacketReceived(nodeId, packet); } } From e2d0b7f85d74553ed3f94a4be25e611f6f1f6918 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 5 Feb 2026 15:45:39 -0800 Subject: [PATCH 110/136] MSBuildTaskHost: Clean up ITranslatable - File-scoped namespace - Enable nullability --- src/MSBuildTaskHost/BackEnd/ITranslatable.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/ITranslatable.cs b/src/MSBuildTaskHost/BackEnd/ITranslatable.cs index fdf48d25a7a..455728022d2 100644 --- a/src/MSBuildTaskHost/BackEnd/ITranslatable.cs +++ b/src/MSBuildTaskHost/BackEnd/ITranslatable.cs @@ -1,18 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable +namespace Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.TaskHost.BackEnd +/// +/// An interface representing an object which may be serialized by the node packet serializer. +/// +internal interface ITranslatable { /// - /// An interface representing an object which may be serialized by the node packet serializer. + /// Reads or writes the packet to the serializer. /// - internal interface ITranslatable - { - /// - /// Reads or writes the packet to the serializer. - /// - void Translate(ITranslator translator); - } + void Translate(ITranslator translator); } From 6686d218763bf3bd97011c3b2afdf9594e1d308e Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 6 Feb 2026 12:05:22 -0800 Subject: [PATCH 111/136] MSBuildTaskHost: Clean up NodeEndpointOutOfProcTaskHost - File-scoped namespace - Expression-bodied members - Enable nullability - Remove unneeded members --- .../BackEnd/NodeEndpointOutOfProcTaskHost.cs | 993 +++++++++--------- 1 file changed, 468 insertions(+), 525 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs b/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs index 7dc24c37609..213205ac853 100644 --- a/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs +++ b/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs @@ -12,645 +12,588 @@ using Microsoft.Build.TaskHost.Collections; using Microsoft.Build.TaskHost.Utilities; -#nullable disable +namespace Microsoft.Build.TaskHost.BackEnd; -namespace Microsoft.Build.TaskHost.BackEnd +/// +/// This is an implementation of INodeEndpoint for the out-of-proc nodes. It acts only as a client. +/// +[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "It is expected to keep the stream open for the process lifetime")] +internal sealed class NodeEndpointOutOfProcTaskHost : INodeEndpoint { /// - /// This is an implementation of INodeEndpoint for the out-of-proc nodes. It acts only as a client. + /// The size of the buffers to use for named pipes. /// - [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "It is expected to keep the stream open for the process lifetime")] - internal sealed class NodeEndpointOutOfProcTaskHost : INodeEndpoint - { - #region Private Data - - /// - /// The size of the buffers to use for named pipes - /// - private const int PipeBufferSize = 131072; - - /// - /// The current communication status of the node. - /// - private LinkStatus _status; - - /// - /// The pipe client used by the nodes. - /// - private NamedPipeServerStream _pipeServer; - - // The following private data fields are used only when the endpoint is in ASYNCHRONOUS mode. - - /// - /// Object used as a lock source for the async data - /// - private object _asyncDataMonitor; - - /// - /// Set when a packet is available in the packet queue - /// - private AutoResetEvent _packetAvailable; - - /// - /// Set when the asynchronous packet pump should terminate - /// - private AutoResetEvent _terminatePacketPump; - - /// - /// True if this side is gracefully disconnecting. - /// In such case we have sent last packet to client side and we expect - /// client will soon broke pipe connection - unless server do it first. - /// - private bool _isClientDisconnecting; - - /// - /// The thread which runs the asynchronous packet pump - /// - private Thread _packetPump; - - /// - /// The factory used to create and route packets. - /// - private INodePacketFactory _packetFactory; - - /// - /// The asynchronous packet queue. - /// - /// - /// Operations on this queue must be synchronized since it is accessible by multiple threads. - /// Use a lock on the packetQueue itself. - /// - private ConcurrentQueue _packetQueue; - - /// - /// Per-node shared read buffer. - /// - private BinaryReaderFactory _sharedReadBuffer; - - /// - /// A way to cache a byte array when writing out packets - /// - private MemoryStream _packetStream; - - /// - /// A binary writer to help write into - /// - private BinaryWriter _binaryWriter; - - /// - /// Represents the version of the parent packet associated with the node instantiation. - /// - private byte _parentPacketVersion; - - #endregion - - public NodeEndpointOutOfProcTaskHost(byte parentPacketVersion) - { - _status = LinkStatus.Inactive; - _asyncDataMonitor = new object(); - _sharedReadBuffer = InterningBinaryReader.CreateSharedBuffer(); - - _packetStream = new MemoryStream(); - _binaryWriter = new BinaryWriter(_packetStream); - _parentPacketVersion = parentPacketVersion; - - string pipeName = $"MSBuild{EnvironmentUtilities.CurrentProcessId}"; - - SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner; - PipeSecurity security = new PipeSecurity(); - - // Restrict access to just this account. We set the owner specifically here, and on the - // pipe client side they will check the owner against this one - they must have identical - // SIDs or the client will reject this server. This is used to avoid attacks where a - // hacked server creates a less restricted pipe in an attempt to lure us into using it and - // then sending build requests to the real pipe client (which is the MSBuild Build Manager.) - PipeAccessRule rule = new PipeAccessRule(identifier, PipeAccessRights.ReadWrite, AccessControlType.Allow); - security.AddAccessRule(rule); - security.SetOwner(identifier); - - _pipeServer = new NamedPipeServerStream( - pipeName, - PipeDirection.InOut, - 1, // Only allow one connection at a time. - PipeTransmissionMode.Byte, - PipeOptions.Asynchronous | PipeOptions.WriteThrough, - PipeBufferSize, // Default input buffer - PipeBufferSize, // Default output buffer - security, - HandleInheritability.None); - } + private const int PipeBufferSize = 131072; - #region INodeEndpoint Events + /// + /// The pipe client used by the nodes. + /// + private readonly NamedPipeServerStream _pipeServer; - /// - /// Raised when the link status has changed. - /// - public event LinkStatusChangedDelegate OnLinkStatusChanged; + /// + /// Per-node shared read buffer. + /// + private readonly BinaryReaderFactory _sharedReadBuffer; - #endregion + /// + /// A way to cache a byte array when writing out packets. + /// + private readonly MemoryStream _packetStream; - #region INodeEndpoint Properties + /// + /// A binary writer to help write into . + /// + private readonly BinaryWriter _binaryWriter; - /// - /// Returns the link status of this node. - /// - public LinkStatus LinkStatus - { - get { return _status; } - } + /// + /// Represents the version of the parent packet associated with the node instantiation. + /// + private readonly byte _parentPacketVersion; - #endregion + /// + /// The current communication status of the node. + /// + private LinkStatus _status; - #region INodeEndpoint Methods + /// + /// Set when a packet is available in the packet queue. + /// + private AutoResetEvent? _packetAvailable; - /// - /// Causes this endpoint to wait for the remote endpoint to connect - /// - /// The factory used to create packets. - public void Listen(INodePacketFactory factory) - { - ErrorUtilities.VerifyThrow(_status == LinkStatus.Inactive, "Link not inactive. Status is {0}", _status); - ErrorUtilities.VerifyThrowArgumentNull(factory); - _packetFactory = factory; + /// + /// Set when the asynchronous packet pump should terminate. + /// + private AutoResetEvent? _terminatePacketPump; - InitializeAsyncPacketThread(); - } + /// + /// True if this side is gracefully disconnecting. + /// In such case we have sent last packet to client side and we expect + /// client will soon broke pipe connection - unless server do it first. + /// + private bool _isClientDisconnecting; - /// - /// Causes this node to connect to the matched endpoint. - /// - /// The factory used to create packets. - public void Connect(INodePacketFactory factory) - { - ErrorUtilities.ThrowInternalError("Connect() not valid on the out of proc endpoint."); - } + /// + /// The thread which runs the asynchronous packet pump. + /// + private Thread? _packetPump; - /// - /// Shuts down the link - /// - public void Disconnect() - { - InternalDisconnect(); - } + /// + /// The factory used to create and route packets. + /// + private INodePacketFactory? _packetFactory; - /// - /// Sends data to the peer endpoint. - /// - /// The packet to send. - public void SendData(INodePacket packet) - { - // PERF: Set up a priority system so logging packets are sent only when all other packet types have been sent. - if (_status == LinkStatus.Active) - { - EnqueuePacket(packet); - } - } + /// + /// The asynchronous packet queue. + /// + /// + /// Operations on this queue must be synchronized since it is accessible by multiple threads. + /// Use a lock on the packetQueue itself. + /// + private ConcurrentQueue? _packetQueue; - /// - /// Called when we are about to send last packet to finalize graceful disconnection with client. - /// - public void ClientWillDisconnect() - { - _isClientDisconnecting = true; - } + public NodeEndpointOutOfProcTaskHost(byte parentPacketVersion) + { + _status = LinkStatus.Inactive; + _sharedReadBuffer = InterningBinaryReader.CreateSharedBuffer(); + + _packetStream = new MemoryStream(); + _binaryWriter = new BinaryWriter(_packetStream); + _parentPacketVersion = parentPacketVersion; + + string pipeName = $"MSBuild{EnvironmentUtilities.CurrentProcessId}"; + + SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner; + var security = new PipeSecurity(); + + // Restrict access to just this account. We set the owner specifically here, and on the + // pipe client side they will check the owner against this one - they must have identical + // SIDs or the client will reject this server. This is used to avoid attacks where a + // hacked server creates a less restricted pipe in an attempt to lure us into using it and + // then sending build requests to the real pipe client (which is the MSBuild Build Manager.) + var rule = new PipeAccessRule(identifier, PipeAccessRights.ReadWrite, AccessControlType.Allow); + security.AddAccessRule(rule); + security.SetOwner(identifier); + + _pipeServer = new NamedPipeServerStream( + pipeName, + PipeDirection.InOut, + maxNumberOfServerInstances: 1, + PipeTransmissionMode.Byte, + PipeOptions.Asynchronous | PipeOptions.WriteThrough, + inBufferSize: PipeBufferSize, + outBufferSize: PipeBufferSize, + security, + HandleInheritability.None); + } - #endregion + /// + /// Raised when the link status has changed. + /// + public event LinkStatusChangedDelegate? OnLinkStatusChanged; - /// - /// Updates the current link status if it has changed and notifies any registered delegates. - /// - /// The status the node should now be in. - private void ChangeLinkStatus(LinkStatus newStatus) - { - ErrorUtilities.VerifyThrow(_status != newStatus, "Attempting to change status to existing status {0}.", _status); - CommunicationsUtilities.Trace("Changing link status from {0} to {1}", _status.ToString(), newStatus.ToString()); - _status = newStatus; - RaiseLinkStatusChanged(_status); - } + /// + /// Returns the link status of this node. + /// + public LinkStatus LinkStatus => _status; - /// - /// Invokes the OnLinkStatusChanged event in a thread-safe manner. - /// - /// The new status of the endpoint link. - private void RaiseLinkStatusChanged(LinkStatus newStatus) - { - OnLinkStatusChanged?.Invoke(this, newStatus); - } + /// + /// Causes this endpoint to wait for the remote endpoint to connect. + /// + /// The factory used to create packets. + public void Listen(INodePacketFactory factory) + { + ErrorUtilities.VerifyThrow(_status == LinkStatus.Inactive, "Link not inactive. Status is {0}", _status); + ErrorUtilities.VerifyThrowArgumentNull(factory); - #region Private Methods + _packetFactory = factory; - /// - /// This does the actual work of changing the status and shutting down any threads we may have for - /// disconnection. - /// - private void InternalDisconnect() + _isClientDisconnecting = false; + _packetPump = new Thread(PacketPumpProc) { - ErrorUtilities.VerifyThrow(_packetPump.ManagedThreadId != Thread.CurrentThread.ManagedThreadId, "Can't join on the same thread."); - _terminatePacketPump.Set(); - _packetPump.Join(); - _terminatePacketPump.Close(); - _pipeServer.Dispose(); - _packetPump = null; - ChangeLinkStatus(LinkStatus.Inactive); - } + IsBackground = true, + Name = "OutOfProc Endpoint Packet Pump" + }; + + _packetAvailable = new AutoResetEvent(false); + _terminatePacketPump = new AutoResetEvent(false); + _packetQueue = new ConcurrentQueue(); + _packetPump.Start(); + } - #region Asynchronous Mode Methods + /// + /// Causes this node to connect to the matched endpoint. + /// + /// The factory used to create packets. + public void Connect(INodePacketFactory factory) + => ErrorUtilities.ThrowInternalError("Connect() not valid on the out of proc endpoint."); + + /// + /// Shuts down the link. + /// + public void Disconnect() + { + ErrorUtilities.VerifyThrow(_packetPump != null, $"{nameof(_packetPump)} is null."); + ErrorUtilities.VerifyThrow(_packetPump.ManagedThreadId != Thread.CurrentThread.ManagedThreadId, "Can't join on the same thread."); + ErrorUtilities.VerifyThrow(_terminatePacketPump != null, $"{nameof(_terminatePacketPump)} is null."); + + _terminatePacketPump.Set(); + _packetPump.Join(); + _terminatePacketPump.Close(); + _pipeServer.Dispose(); + _packetPump = null; + ChangeLinkStatus(LinkStatus.Inactive); + } - /// - /// Adds a packet to the packet queue when asynchronous mode is enabled. - /// - /// The packet to be transmitted. - private void EnqueuePacket(INodePacket packet) + /// + /// Sends data to the peer endpoint. + /// + /// The packet to send. + public void SendData(INodePacket packet) + { + ErrorUtilities.VerifyThrowArgumentNull(packet); + + // PERF: Set up a priority system so logging packets are sent only when all other packet types have been sent. + if (_status == LinkStatus.Active) { - ErrorUtilities.VerifyThrowArgumentNull(packet); - ErrorUtilities.VerifyThrow(_packetQueue != null, "packetQueue is null"); - ErrorUtilities.VerifyThrow(_packetAvailable != null, "packetAvailable is null"); + ErrorUtilities.VerifyThrow(_packetQueue != null, $"{nameof(_packetQueue)} is null"); + ErrorUtilities.VerifyThrow(_packetAvailable != null, $"{nameof(_packetAvailable)} is null"); + _packetQueue.Enqueue(packet); _packetAvailable.Set(); } + } - /// - /// Initializes the packet pump thread and the supporting events as well as the packet queue. - /// - private void InitializeAsyncPacketThread() - { - lock (_asyncDataMonitor) - { - _isClientDisconnecting = false; - _packetPump = new Thread(PacketPumpProc); - _packetPump.IsBackground = true; - _packetPump.Name = "OutOfProc Endpoint Packet Pump"; - _packetAvailable = new AutoResetEvent(false); - _terminatePacketPump = new AutoResetEvent(false); - _packetQueue = new ConcurrentQueue(); - _packetPump.Start(); - } - } + /// + /// Called when we are about to send last packet to finalize graceful disconnection with client. + /// + public void ClientWillDisconnect() + { + _isClientDisconnecting = true; + } + + /// + /// Updates the current link status if it has changed and notifies any registered delegates. + /// + /// The status the node should now be in. + private void ChangeLinkStatus(LinkStatus newStatus) + { + ErrorUtilities.VerifyThrow(_status != newStatus, "Attempting to change status to existing status {0}.", _status); + CommunicationsUtilities.Trace("Changing link status from {0} to {1}", _status.ToString(), newStatus.ToString()); + _status = newStatus; + OnLinkStatusChanged?.Invoke(this, newStatus); + } + + /// + /// This method handles the asynchronous message pump. It waits for messages to show up on the queue + /// and calls FireDataAvailable for each such packet. It will terminate when the terminate event is + /// set. + /// + private void PacketPumpProc() + { + ErrorUtilities.VerifyThrow(_packetQueue != null, $"{nameof(_packetQueue)} is null"); + ErrorUtilities.VerifyThrow(_terminatePacketPump != null, $"{nameof(_terminatePacketPump)} is null"); + ErrorUtilities.VerifyThrow(_packetAvailable != null, $"{nameof(_packetAvailable)} is null"); + + NamedPipeServerStream localPipeServer = _pipeServer; + + AutoResetEvent localPacketAvailable = _packetAvailable; + AutoResetEvent localTerminatePacketPump = _terminatePacketPump; + ConcurrentQueue localPacketQueue = _packetQueue; - /// - /// This method handles the asynchronous message pump. It waits for messages to show up on the queue - /// and calls FireDataAvailable for each such packet. It will terminate when the terminate event is - /// set. - /// - private void PacketPumpProc() + DateTime originalWaitStartTime = DateTime.UtcNow; + bool gotValidConnection = false; + while (!gotValidConnection) { - NamedPipeServerStream localPipeServer = _pipeServer; + gotValidConnection = true; + DateTime restartWaitTime = DateTime.UtcNow; - AutoResetEvent localPacketAvailable = _packetAvailable; - AutoResetEvent localTerminatePacketPump = _terminatePacketPump; - ConcurrentQueue localPacketQueue = _packetQueue; + // We only wait to wait the difference between now and the last original start time, in case we have multiple hosts attempting + // to attach. This prevents each attempt from resetting the timer. + TimeSpan usedWaitTime = restartWaitTime - originalWaitStartTime; + int waitTimeRemaining = Math.Max(0, CommunicationsUtilities.NodeConnectionTimeout - (int)usedWaitTime.TotalMilliseconds); - DateTime originalWaitStartTime = DateTime.UtcNow; - bool gotValidConnection = false; - while (!gotValidConnection) + try { - gotValidConnection = true; - DateTime restartWaitTime = DateTime.UtcNow; + // Wait for a connection + IAsyncResult resultForConnection = localPipeServer.BeginWaitForConnection(null, null); + CommunicationsUtilities.Trace("Waiting for connection {0} ms...", waitTimeRemaining); + bool connected = resultForConnection.AsyncWaitHandle.WaitOne(waitTimeRemaining, false); + if (!connected) + { + CommunicationsUtilities.Trace("Connection timed out waiting a host to contact us. Exiting comm thread."); + ChangeLinkStatus(LinkStatus.ConnectionFailed); + return; + } - // We only wait to wait the difference between now and the last original start time, in case we have multiple hosts attempting - // to attach. This prevents each attempt from resetting the timer. - TimeSpan usedWaitTime = restartWaitTime - originalWaitStartTime; - int waitTimeRemaining = Math.Max(0, CommunicationsUtilities.NodeConnectionTimeout - (int)usedWaitTime.TotalMilliseconds); + CommunicationsUtilities.Trace("Parent started connecting. Reading handshake from parent"); + localPipeServer.EndWaitForConnection(resultForConnection); + // The handshake protocol is a series of int exchanges. The host sends us a each component, and we + // verify it. Afterwards, the host sends an "End of Handshake" signal, to which we respond in kind. + // Once the handshake is complete, both sides can be assured the other is ready to accept data. + Handshake handshake = new(CommunicationsUtilities.GetHandshakeOptions()); try { - // Wait for a connection - IAsyncResult resultForConnection = localPipeServer.BeginWaitForConnection(null, null); - CommunicationsUtilities.Trace("Waiting for connection {0} ms...", waitTimeRemaining); - bool connected = resultForConnection.AsyncWaitHandle.WaitOne(waitTimeRemaining, false); - if (!connected) - { - CommunicationsUtilities.Trace("Connection timed out waiting a host to contact us. Exiting comm thread."); - ChangeLinkStatus(LinkStatus.ConnectionFailed); - return; - } - - CommunicationsUtilities.Trace("Parent started connecting. Reading handshake from parent"); - localPipeServer.EndWaitForConnection(resultForConnection); + HandshakeComponents handshakeComponents = handshake.RetrieveHandshakeComponents(); - // The handshake protocol is a series of int exchanges. The host sends us a each component, and we - // verify it. Afterwards, the host sends an "End of Handshake" signal, to which we respond in kind. - // Once the handshake is complete, both sides can be assured the other is ready to accept data. - Handshake handshake = new(CommunicationsUtilities.GetHandshakeOptions()); - try + int index = 0; + foreach (var component in handshakeComponents.EnumerateComponents()) { - HandshakeComponents handshakeComponents = handshake.RetrieveHandshakeComponents(); + byte? byteToAccept = index == 0 ? CommunicationsUtilities.HandshakeVersion : null; - int index = 0; - foreach (var component in handshakeComponents.EnumerateComponents()) + if (!_pipeServer.TryReadIntForHandshake( + byteToAccept, /* this will disconnect a < 16.8 host; it expects leading 00 or F5 or 06. 0x00 is a wildcard */ + out HandshakeResult result)) { + CommunicationsUtilities.Trace($"Handshake failed with error: {result.ErrorMessage}"); + } - if (!_pipeServer.TryReadIntForHandshake( - byteToAccept: index == 0 ? (byte?)CommunicationsUtilities.HandshakeVersion : null, /* this will disconnect a < 16.8 host; it expects leading 00 or F5 or 06. 0x00 is a wildcard */ - out HandshakeResult result)) - { - CommunicationsUtilities.Trace($"Handshake failed with error: {result.ErrorMessage}"); - } - - if (!IsHandshakePartValid(component, result.Value, index)) - { - CommunicationsUtilities.Trace( - "Handshake failed. Received {0} from host for {1} but expected {2}. Probably the host is a different MSBuild build.", - result.Value, - component.Key, - component.Value); - _pipeServer.WriteIntForHandshake(index + 1); - gotValidConnection = false; - break; - } - - index++; + if (!IsHandshakePartValid(component, result.Value)) + { + CommunicationsUtilities.Trace( + "Handshake failed. Received {0} from host for {1} but expected {2}. Probably the host is a different MSBuild build.", + result.Value, + component.Key, + component.Value); + _pipeServer.WriteIntForHandshake(index + 1); + gotValidConnection = false; + break; } - if (gotValidConnection) + index++; + } + + if (gotValidConnection) + { + // To ensure that our handshake and theirs have the same number of bytes, receive and send a magic number indicating EOS. + if (_pipeServer.TryReadEndOfHandshakeSignal(false, out _)) { - // To ensure that our handshake and theirs have the same number of bytes, receive and send a magic number indicating EOS. - if (_pipeServer.TryReadEndOfHandshakeSignal(false, out HandshakeResult _)) + // Send supported PacketVersion after EndOfHandshakeSignal + // Based on this parent node decides how to communicate with the child. + if (_parentPacketVersion >= 2) { - // Send supported PacketVersion after EndOfHandshakeSignal - // Based on this parent node decides how to communicate with the child. - if (_parentPacketVersion >= 2) - { - _pipeServer.WriteIntForHandshake(Handshake.PacketVersionFromChildMarker); // Marker: PacketVersion follows - _pipeServer.WriteIntForHandshake(NodePacketTypeExtensions.PacketVersion); - CommunicationsUtilities.Trace("Sent PacketVersion: {0}", NodePacketTypeExtensions.PacketVersion); - } + _pipeServer.WriteIntForHandshake(Handshake.PacketVersionFromChildMarker); // Marker: PacketVersion follows + _pipeServer.WriteIntForHandshake(NodePacketTypeExtensions.PacketVersion); + CommunicationsUtilities.Trace("Sent PacketVersion: {0}", NodePacketTypeExtensions.PacketVersion); + } - CommunicationsUtilities.Trace("Successfully connected to parent."); - _pipeServer.WriteEndOfHandshakeSignal(); + CommunicationsUtilities.Trace("Successfully connected to parent."); + _pipeServer.WriteEndOfHandshakeSignal(); - // We will only talk to a host that was started by the same user as us. Even though the pipe access is set to only allow this user, we want to ensure they - // haven't attempted to change those permissions out from under us. This ensures that the only way they can truly gain access is to be impersonating the - // user we were started by. - WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent(); - WindowsIdentity clientIdentity = null; - localPipeServer.RunAsClient(delegate () { clientIdentity = WindowsIdentity.GetCurrent(true); }); + // We will only talk to a host that was started by the same user as us. Even though the pipe access is set to only allow this user, we want to ensure they + // haven't attempted to change those permissions out from under us. This ensures that the only way they can truly gain access is to be impersonating the + // user we were started by. + WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent(); + WindowsIdentity? clientIdentity = null; + localPipeServer.RunAsClient(() => { clientIdentity = WindowsIdentity.GetCurrent(true); }); - if (clientIdentity == null || !String.Equals(clientIdentity.Name, currentIdentity.Name, StringComparison.OrdinalIgnoreCase)) - { - CommunicationsUtilities.Trace("Handshake failed. Host user is {0} but we were created by {1}.", (clientIdentity == null) ? "" : clientIdentity.Name, currentIdentity.Name); - gotValidConnection = false; - continue; - } + if (clientIdentity == null || !string.Equals(clientIdentity.Name, currentIdentity.Name, StringComparison.OrdinalIgnoreCase)) + { + CommunicationsUtilities.Trace("Handshake failed. Host user is {0} but we were created by {1}.", (clientIdentity == null) ? "" : clientIdentity.Name, currentIdentity.Name); + gotValidConnection = false; + continue; } } } - catch (IOException e) - { - // We will get here when: - // 1. The host (OOP main node) connects to us, it immediately checks for user privileges - // and if they don't match it disconnects immediately leaving us still trying to read the blank handshake - // 2. The host is too old sending us bits we automatically reject in the handshake - // 3. We expected to read the EndOfHandshake signal, but we received something else - CommunicationsUtilities.Trace("Client connection failed but we will wait for another connection. Exception: {0}", e.Message); - - gotValidConnection = false; - } - catch (InvalidOperationException) - { - gotValidConnection = false; - } - - if (!gotValidConnection) - { - if (localPipeServer.IsConnected) - { - localPipeServer.Disconnect(); - } - continue; - } - - ChangeLinkStatus(LinkStatus.Active); } - catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + catch (IOException e) + { + // We will get here when: + // 1. The host (OOP main node) connects to us, it immediately checks for user privileges + // and if they don't match it disconnects immediately leaving us still trying to read the blank handshake + // 2. The host is too old sending us bits we automatically reject in the handshake + // 3. We expected to read the EndOfHandshake signal, but we received something else + CommunicationsUtilities.Trace("Client connection failed but we will wait for another connection. Exception: {0}", e.Message); + + gotValidConnection = false; + } + catch (InvalidOperationException) + { + gotValidConnection = false; + } + + if (!gotValidConnection) { - CommunicationsUtilities.Trace("Client connection failed. Exiting comm thread. {0}", e); if (localPipeServer.IsConnected) { localPipeServer.Disconnect(); } - ExceptionHandling.DumpExceptionToFile(e); - ChangeLinkStatus(LinkStatus.Failed); - return; + continue; } - } - RunReadLoop( - new BufferedReadStream(_pipeServer), - _pipeServer, - localPacketQueue, localPacketAvailable, localTerminatePacketPump); - - CommunicationsUtilities.Trace("Ending read loop"); - - try + ChangeLinkStatus(LinkStatus.Active); + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) { + CommunicationsUtilities.Trace("Client connection failed. Exiting comm thread. {0}", e); if (localPipeServer.IsConnected) { - localPipeServer.WaitForPipeDrain(); localPipeServer.Disconnect(); } - } - catch (Exception) - { - // We don't really care if Disconnect somehow fails, but it gives us a chance to do the right thing. + + ExceptionHandling.DumpExceptionToFile(e); + ChangeLinkStatus(LinkStatus.Failed); + return; } } - /// - /// Method to verify that the handshake part received from the host matches the expected values. - /// - private bool IsHandshakePartValid(KeyValuePair component, int handshakePart, int index) + RunReadLoop( + new BufferedReadStream(_pipeServer), + _pipeServer, + localPacketQueue, + localPacketAvailable, + localTerminatePacketPump); + + CommunicationsUtilities.Trace("Ending read loop"); + + try { - if (handshakePart == component.Value) + if (localPipeServer.IsConnected) { - return true; + localPipeServer.WaitForPipeDrain(); + localPipeServer.Disconnect(); } - - CommunicationsUtilities.Trace( - "Handshake failed. Received {0} from host for {1} but expected {2}. Probably the host is a different MSBuild build.", - handshakePart, - component.Key, - component.Value); - - return false; } + catch (Exception) + { + // We don't really care if Disconnect somehow fails, but it gives us a chance to do the right thing. + } + } - private void RunReadLoop( - BufferedReadStream localReadPipe, - NamedPipeServerStream localWritePipe, - ConcurrentQueue localPacketQueue, - AutoResetEvent localPacketAvailable, - AutoResetEvent localTerminatePacketPump) + /// + /// Method to verify that the handshake part received from the host matches the expected values. + /// + private static bool IsHandshakePartValid(KeyValuePair component, int handshakePart) + { + if (handshakePart == component.Value) { - // Ordering of the wait handles is important. The first signaled wait handle in the array - // will be returned by WaitAny if multiple wait handles are signaled. We prefer to have the - // terminate event triggered so that we cannot get into a situation where packets are being - // spammed to the endpoint and it never gets an opportunity to shutdown. - CommunicationsUtilities.Trace("Entering read loop."); - byte[] headerByte = new byte[5]; - ITranslator writeTranslator = null; - IAsyncResult result = localReadPipe.BeginRead(headerByte, 0, headerByte.Length, null, null); - - // Ordering is important. We want packetAvailable to supercede terminate otherwise we will not properly wait for all - // packets to be sent by other threads which are shutting down, such as the logging thread. - WaitHandle[] handles = new WaitHandle[] - { - result.AsyncWaitHandle, - localPacketAvailable, - localTerminatePacketPump, - }; + return true; + } + + CommunicationsUtilities.Trace( + "Handshake failed. Received {0} from host for {1} but expected {2}. Probably the host is a different MSBuild build.", + handshakePart, + component.Key, + component.Value); + + return false; + } - bool exitLoop = false; - do + private void RunReadLoop( + BufferedReadStream localReadPipe, + NamedPipeServerStream localWritePipe, + ConcurrentQueue localPacketQueue, + AutoResetEvent localPacketAvailable, + AutoResetEvent localTerminatePacketPump) + { + ErrorUtilities.VerifyThrow(_packetFactory != null, $"{nameof(_packetFactory)} is null"); + + INodePacketFactory packetFactory = _packetFactory; + + // Ordering of the wait handles is important. The first signaled wait handle in the array + // will be returned by WaitAny if multiple wait handles are signaled. We prefer to have the + // terminate event triggered so that we cannot get into a situation where packets are being + // spammed to the endpoint and it never gets an opportunity to shutdown. + CommunicationsUtilities.Trace("Entering read loop."); + byte[] headerByte = new byte[5]; + ITranslator? writeTranslator = null; + IAsyncResult result = localReadPipe.BeginRead(headerByte, offset: 0, headerByte.Length, callback: null, state: null); + + // Ordering is important. We want packetAvailable to supercede terminate otherwise we will not properly wait for all + // packets to be sent by other threads which are shutting down, such as the logging thread. + WaitHandle[] handles = + [ + result.AsyncWaitHandle, + localPacketAvailable, + localTerminatePacketPump, + ]; + + bool exitLoop = false; + do + { + int waitId = WaitHandle.WaitAny(handles); + switch (waitId) { - int waitId = WaitHandle.WaitAny(handles); - switch (waitId) - { - case 0: + case 0: + { + int bytesRead; + try { - int bytesRead = 0; - try - { - bytesRead = localReadPipe.EndRead(result); - } - catch (Exception e) - { - // Lost communications. Abort (but allow node reuse) - CommunicationsUtilities.Trace("Exception reading from server. {0}", e); - ExceptionHandling.DumpExceptionToFile(e); - ChangeLinkStatus(LinkStatus.Inactive); - exitLoop = true; - break; - } + bytesRead = localReadPipe.EndRead(result); + } + catch (Exception e) + { + // Lost communications. Abort (but allow node reuse) + CommunicationsUtilities.Trace("Exception reading from server. {0}", e); + ExceptionHandling.DumpExceptionToFile(e); + ChangeLinkStatus(LinkStatus.Inactive); + exitLoop = true; + break; + } - if (bytesRead != headerByte.Length) + if (bytesRead != headerByte.Length) + { + // Incomplete read. Abort. + if (bytesRead == 0) { - // Incomplete read. Abort. - if (bytesRead == 0) + if (_isClientDisconnecting) { - if (_isClientDisconnecting) - { - CommunicationsUtilities.Trace("Parent disconnected gracefully."); - // Do not change link status to failed as this could make node think connection has failed - // and recycle node, while this is perfectly expected and handled race condition - // (both client and node is about to close pipe and client can be faster). - } - else - { - CommunicationsUtilities.Trace("Parent disconnected abruptly."); - ChangeLinkStatus(LinkStatus.Failed); - } + CommunicationsUtilities.Trace("Parent disconnected gracefully."); + + // Do not change link status to failed as this could make node think connection has failed + // and recycle node, while this is perfectly expected and handled race condition + // (both client and node is about to close pipe and client can be faster). } else { - CommunicationsUtilities.Trace("Incomplete header read from server. {0} of {1} bytes read", bytesRead, headerByte.Length); + CommunicationsUtilities.Trace("Parent disconnected abruptly."); ChangeLinkStatus(LinkStatus.Failed); } - - exitLoop = true; - break; - } - - // Check if this packet has an extended header that includes a version part. - byte rawType = headerByte[0]; - bool hasExtendedHeader = NodePacketTypeExtensions.HasExtendedHeader(rawType); - NodePacketType packetType = hasExtendedHeader - ? NodePacketTypeExtensions.GetNodePacketType(rawType) - : (NodePacketType)rawType; - - byte parentVersion = 0; - if (hasExtendedHeader) - { - parentVersion = NodePacketTypeExtensions.ReadVersion(localReadPipe); - } - - try - { - ITranslator readTranslator = BinaryTranslator.GetReadTranslator(localReadPipe, _sharedReadBuffer); - - // parent sends a packet version that is already negotiated during handshake. - readTranslator.NegotiatedPacketVersion = parentVersion; - _packetFactory.DeserializeAndRoutePacket(0, packetType, readTranslator); } - catch (Exception e) + else { - // Error while deserializing or handling packet. Abort. - CommunicationsUtilities.Trace("Exception while deserializing packet {0}: {1}", packetType, e); - ExceptionHandling.DumpExceptionToFile(e); + CommunicationsUtilities.Trace("Incomplete header read from server. {0} of {1} bytes read", bytesRead, headerByte.Length); ChangeLinkStatus(LinkStatus.Failed); - exitLoop = true; - break; } - result = localReadPipe.BeginRead(headerByte, 0, headerByte.Length, null, null); - - handles[0] = result.AsyncWaitHandle; + exitLoop = true; + break; } - break; + // Check if this packet has an extended header that includes a version part. + byte rawType = headerByte[0]; + bool hasExtendedHeader = NodePacketTypeExtensions.HasExtendedHeader(rawType); + NodePacketType packetType = hasExtendedHeader + ? NodePacketTypeExtensions.GetNodePacketType(rawType) + : (NodePacketType)rawType; - case 1: - case 2: - try + byte parentVersion = 0; + if (hasExtendedHeader) { - // Write out all the queued packets. - INodePacket packet; - while (localPacketQueue.TryDequeue(out packet)) - { - var packetStream = _packetStream; - packetStream.SetLength(0); - - // Re-use writeTranslator; we clear _packetStream but never replace it. - // If _packetStream is ever reassigned, set writeTranslator = null first. - writeTranslator ??= BinaryTranslator.GetWriteTranslator(packetStream); - - packetStream.WriteByte((byte)packet.Type); - - // Pad for packet length - _binaryWriter.Write(0); - - // Reset the position in the write buffer. - packet.Translate(writeTranslator); - - int packetStreamLength = (int)packetStream.Position; + parentVersion = NodePacketTypeExtensions.ReadVersion(localReadPipe); + } - // Now write in the actual packet length - packetStream.Position = 1; - _binaryWriter.Write(packetStreamLength - 5); + try + { + ITranslator readTranslator = BinaryTranslator.GetReadTranslator(localReadPipe, _sharedReadBuffer); - localWritePipe.Write(packetStream.GetBuffer(), 0, packetStreamLength); - } + // parent sends a packet version that is already negotiated during handshake. + readTranslator.NegotiatedPacketVersion = parentVersion; + packetFactory.DeserializeAndRoutePacket(0, packetType, readTranslator); } catch (Exception e) { // Error while deserializing or handling packet. Abort. - CommunicationsUtilities.Trace("Exception while serializing packets: {0}", e); + CommunicationsUtilities.Trace("Exception while deserializing packet {0}: {1}", packetType, e); ExceptionHandling.DumpExceptionToFile(e); ChangeLinkStatus(LinkStatus.Failed); exitLoop = true; break; } - if (waitId == 2) + result = localReadPipe.BeginRead(headerByte, 0, headerByte.Length, null, null); + + handles[0] = result.AsyncWaitHandle; + } + + break; + + case 1: + case 2: + try + { + // Write out all the queued packets. + while (localPacketQueue.TryDequeue(out INodePacket? packet)) { - CommunicationsUtilities.Trace("Disconnecting voluntarily"); - ChangeLinkStatus(LinkStatus.Failed); - exitLoop = true; - } + var packetStream = _packetStream; + packetStream.SetLength(0); - break; + // Re-use writeTranslator; we clear _packetStream but never replace it. + // If _packetStream is ever reassigned, set writeTranslator = null first. + writeTranslator ??= BinaryTranslator.GetWriteTranslator(packetStream); + + packetStream.WriteByte((byte)packet.Type); + + // Pad for packet length + _binaryWriter.Write(0); + + // Reset the position in the write buffer. + packet.Translate(writeTranslator); - default: - ErrorUtilities.ThrowInternalError($"waitId {waitId} out of range."); + int packetStreamLength = (int)packetStream.Position; + + // Now write in the actual packet length + packetStream.Position = 1; + _binaryWriter.Write(packetStreamLength - 5); + + localWritePipe.Write(packetStream.GetBuffer(), 0, packetStreamLength); + } + } + catch (Exception e) + { + // Error while deserializing or handling packet. Abort. + CommunicationsUtilities.Trace("Exception while serializing packets: {0}", e); + ExceptionHandling.DumpExceptionToFile(e); + ChangeLinkStatus(LinkStatus.Failed); + exitLoop = true; break; - } - } - while (!exitLoop); - } + } - #endregion + if (waitId == 2) + { + CommunicationsUtilities.Trace("Disconnecting voluntarily"); + ChangeLinkStatus(LinkStatus.Failed); + exitLoop = true; + } - #endregion + break; + + default: + ErrorUtilities.ThrowInternalError($"waitId {waitId} out of range."); + break; + } + } + while (!exitLoop); } } From 98c1053b79c8d19359b9adf13bf53e35c25fe285 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 6 Feb 2026 12:08:31 -0800 Subject: [PATCH 112/136] MSBuildTaskHost: Clean up CollectionHelpers - File-scoped namespace - Expression-bodied members - Enable nullability - Rename to CollectionExtensions --- .../Collections/CollectionExtensions.cs | 30 +++++++++++++++++++ .../Collections/CollectionHelpers.cs | 30 ------------------- src/MSBuildTaskHost/OutOfProcTaskHostNode.cs | 6 ++-- 3 files changed, 33 insertions(+), 33 deletions(-) create mode 100644 src/MSBuildTaskHost/Collections/CollectionExtensions.cs delete mode 100644 src/MSBuildTaskHost/Collections/CollectionHelpers.cs diff --git a/src/MSBuildTaskHost/Collections/CollectionExtensions.cs b/src/MSBuildTaskHost/Collections/CollectionExtensions.cs new file mode 100644 index 00000000000..3869475f23d --- /dev/null +++ b/src/MSBuildTaskHost/Collections/CollectionExtensions.cs @@ -0,0 +1,30 @@ +// 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; + +namespace Microsoft.Build.TaskHost.Collections; + +internal static class CollectionExtensions +{ + /// + /// Checks whether the dictionary contains the specified key with a value that matches the given + /// value using the specified string comparison. + /// + /// The dictionary to check. + /// The key to locate in the dictionary. + /// The value to compare against the dictionary's value. + /// The string comparison method to use. + /// + /// if the dictionary contains the key and its associated value equals + /// the specified value; otherwise, . + /// + internal static bool HasValue( + this Dictionary dictionary, + string key, + string value, + StringComparison comparison) + => dictionary.TryGetValue(key, out string existingValue) + && string.Equals(value, existingValue, comparison); +} diff --git a/src/MSBuildTaskHost/Collections/CollectionHelpers.cs b/src/MSBuildTaskHost/Collections/CollectionHelpers.cs deleted file mode 100644 index 05a4a55e982..00000000000 --- a/src/MSBuildTaskHost/Collections/CollectionHelpers.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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; - -#nullable disable - -namespace Microsoft.Build.TaskHost.Collections -{ - /// - /// Utilities for collections - /// - internal static class CollectionHelpers - { - /// - /// Extension method -- combines a TryGet with a check to see that the value is equal. - /// - internal static bool ContainsValueAndIsEqual(this Dictionary dictionary, string key, string value, StringComparison comparison) - { - string valueFromDictionary; - if (dictionary.TryGetValue(key, out valueFromDictionary)) - { - return String.Equals(value, valueFromDictionary, comparison); - } - - return false; - } - } -} diff --git a/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs index 42c56662df3..a1c471a450d 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs @@ -639,9 +639,9 @@ private void RunTask(object state) // We only really know the values of these variables for sure once we see what we received from our parent // environment -- otherwise if this was a completely new build, we could lose out on expected environment // variables. - _debugCommunications = taskConfiguration.BuildProcessEnvironment.ContainsValueAndIsEqual("MSBUILDDEBUGCOMM", "1", StringComparison.OrdinalIgnoreCase); - _updateEnvironment = !taskConfiguration.BuildProcessEnvironment.ContainsValueAndIsEqual("MSBuildTaskHostDoNotUpdateEnvironment", "1", StringComparison.OrdinalIgnoreCase); - _updateEnvironmentAndLog = taskConfiguration.BuildProcessEnvironment.ContainsValueAndIsEqual("MSBuildTaskHostUpdateEnvironmentAndLog", "1", StringComparison.OrdinalIgnoreCase); + _debugCommunications = taskConfiguration.BuildProcessEnvironment.HasValue("MSBUILDDEBUGCOMM", "1", StringComparison.OrdinalIgnoreCase); + _updateEnvironment = !taskConfiguration.BuildProcessEnvironment.HasValue("MSBuildTaskHostDoNotUpdateEnvironment", "1", StringComparison.OrdinalIgnoreCase); + _updateEnvironmentAndLog = taskConfiguration.BuildProcessEnvironment.HasValue("MSBuildTaskHostUpdateEnvironmentAndLog", "1", StringComparison.OrdinalIgnoreCase); try { From ca756e386f0ec13333f11e1900c97a287afe5fde Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 6 Feb 2026 12:18:34 -0800 Subject: [PATCH 113/136] MSBuildTaskHost: Clean up ConcurrentDictionary - File-scoped namespace - Expression-bodied members - Enable nullability --- .../Collections/ConcurrentDictionary.cs | 811 +++++++++--------- 1 file changed, 402 insertions(+), 409 deletions(-) diff --git a/src/MSBuildTaskHost/Collections/ConcurrentDictionary.cs b/src/MSBuildTaskHost/Collections/ConcurrentDictionary.cs index 644c1c13c6e..4f589978edc 100644 --- a/src/MSBuildTaskHost/Collections/ConcurrentDictionary.cs +++ b/src/MSBuildTaskHost/Collections/ConcurrentDictionary.cs @@ -4,531 +4,524 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Threading; using Microsoft.Build.TaskHost.Utilities; -#nullable disable +namespace Microsoft.Build.TaskHost.Collections; -namespace Microsoft.Build.TaskHost.Collections +// The following class is back-ported from .NET 4.X CoreFX library because +// MSBuildTaskHost requires 3.5 .NET Framework. Only GetOrAdd method kept. +internal class ConcurrentDictionary + where TKey : notnull { - // The following class is back-ported from .NET 4.X CoreFX library because - // MSBuildTaskHost requires 3.5 .NET Framework. Only GetOrAdd method kept. - internal class ConcurrentDictionary + /// + /// Tables that hold the internal state of the ConcurrentDictionary + /// + /// Wrapping the three tables in a single object allows us to atomically + /// replace all tables at once. + /// + private sealed class Tables { - /// - /// Tables that hold the internal state of the ConcurrentDictionary - /// - /// Wrapping the three tables in a single object allows us to atomically - /// replace all tables at once. - /// - private sealed class Tables - { - internal readonly Node[] _buckets; // A singly-linked list for each bucket. - internal readonly object[] _locks; // A set of locks, each guarding a section of the table. - internal volatile int[] _countPerLock; // The number of elements guarded by each lock. + internal readonly Node[] _buckets; // A singly-linked list for each bucket. + internal readonly object[] _locks; // A set of locks, each guarding a section of the table. + internal volatile int[] _countPerLock; // The number of elements guarded by each lock. - internal Tables(Node[] buckets, object[] locks, int[] countPerLock) - { - _buckets = buckets; - _locks = locks; - _countPerLock = countPerLock; - } + internal Tables(Node[] buckets, object[] locks, int[] countPerLock) + { + _buckets = buckets; + _locks = locks; + _countPerLock = countPerLock; } + } - private volatile Tables _tables; // Internal tables of the dictionary - private IEqualityComparer _comparer; // Key equality comparer - private readonly bool _growLockArray; // Whether to dynamically increase the size of the striped lock - private int _budget; // The maximum number of elements per lock before a resize operation is triggered + private volatile Tables _tables; // Internal tables of the dictionary + private IEqualityComparer _comparer; // Key equality comparer + private readonly bool _growLockArray; // Whether to dynamically increase the size of the striped lock + private int _budget; // The maximum number of elements per lock before a resize operation is triggered - // The default capacity, i.e. the initial # of buckets. When choosing this value, we are making - // a trade-off between the size of a very small dictionary, and the number of resizes when - // constructing a large dictionary. Also, the capacity should not be divisible by a small prime. - private const int DefaultCapacity = 31; + // The default capacity, i.e. the initial # of buckets. When choosing this value, we are making + // a trade-off between the size of a very small dictionary, and the number of resizes when + // constructing a large dictionary. Also, the capacity should not be divisible by a small prime. + private const int DefaultCapacity = 31; - // The maximum size of the striped lock that will not be exceeded when locks are automatically - // added as the dictionary grows. However, the user is allowed to exceed this limit by passing - // a concurrency level larger than MaxLockNumber into the constructor. - private const int MaxLockNumber = 1024; + // The maximum size of the striped lock that will not be exceeded when locks are automatically + // added as the dictionary grows. However, the user is allowed to exceed this limit by passing + // a concurrency level larger than MaxLockNumber into the constructor. + private const int MaxLockNumber = 1024; - // Whether TValue is a type that can be written atomically (i.e., with no danger of torn reads) - private static readonly bool s_isValueWriteAtomic = IsValueWriteAtomic(); + // Whether TValue is a type that can be written atomically (i.e., with no danger of torn reads) + private static readonly bool s_isValueWriteAtomic = IsValueWriteAtomic(); - /// - /// Determines whether type TValue can be written atomically - /// - private static bool IsValueWriteAtomic() + /// + /// Determines whether type TValue can be written atomically + /// + private static bool IsValueWriteAtomic() + { + // Section 12.6.6 of ECMA CLI explains which types can be read and written atomically without + // the risk of tearing. + // + // See http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf + Type valueType = typeof(TValue); + if (!valueType.IsValueType) { - // - // Section 12.6.6 of ECMA CLI explains which types can be read and written atomically without - // the risk of tearing. - // - // See http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf - // - Type valueType = typeof(TValue); - if (!valueType.IsValueType) - { - return true; - } - - switch (Type.GetTypeCode(valueType)) - { - case TypeCode.Boolean: - case TypeCode.Byte: - case TypeCode.Char: - case TypeCode.Int16: - case TypeCode.Int32: - case TypeCode.SByte: - case TypeCode.Single: - case TypeCode.UInt16: - case TypeCode.UInt32: - return true; - case TypeCode.Int64: - case TypeCode.Double: - case TypeCode.UInt64: - return IntPtr.Size == 8; - default: - return false; - } + return true; } - /// - /// Initializes a new instance of the - /// class that is empty, has the default concurrency level, has the default initial capacity, and - /// uses the default comparer for the key type. - /// - public ConcurrentDictionary(IEqualityComparer comparer = null) + switch (Type.GetTypeCode(valueType)) { - int concurrencyLevel = NativeMethods.GetLogicalCoreCount(); - int capacity = DefaultCapacity; - - // The capacity should be at least as large as the concurrency level. Otherwise, we would have locks that don't guard - // any buckets. - if (capacity < concurrencyLevel) - { - capacity = concurrencyLevel; - } - - object[] locks = new object[concurrencyLevel]; - for (int i = 0; i < locks.Length; i++) - { - locks[i] = new object(); - } + case TypeCode.Boolean: + case TypeCode.Byte: + case TypeCode.Char: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.SByte: + case TypeCode.Single: + case TypeCode.UInt16: + case TypeCode.UInt32: + return true; + case TypeCode.Int64: + case TypeCode.Double: + case TypeCode.UInt64: + return IntPtr.Size == 8; + default: + return false; + } + } - int[] countPerLock = new int[locks.Length]; - Node[] buckets = new Node[capacity]; - _tables = new Tables(buckets, locks, countPerLock); + /// + /// Initializes a new instance of the + /// class that is empty, has the default concurrency level, has the default initial capacity, and + /// uses the default comparer for the key type. + /// + public ConcurrentDictionary(IEqualityComparer? comparer = null) + { + int concurrencyLevel = NativeMethods.GetLogicalCoreCount(); + int capacity = DefaultCapacity; - _comparer = comparer ?? EqualityComparer.Default; - _growLockArray = true; - _budget = buckets.Length / locks.Length; + // The capacity should be at least as large as the concurrency level. Otherwise, we would have locks that don't guard + // any buckets. + if (capacity < concurrencyLevel) + { + capacity = concurrencyLevel; } - private bool TryGetValueInternal(TKey key, int hashcode, out TValue value) + object[] locks = new object[concurrencyLevel]; + for (int i = 0; i < locks.Length; i++) { - Debug.Assert(_comparer.GetHashCode(key) == hashcode); + locks[i] = new object(); + } - // We must capture the _buckets field in a local variable. It is set to a new table on each table resize. - Tables tables = _tables; + int[] countPerLock = new int[locks.Length]; + Node[] buckets = new Node[capacity]; + _tables = new Tables(buckets, locks, countPerLock); + + _comparer = comparer ?? EqualityComparer.Default; + _growLockArray = true; + _budget = buckets.Length / locks.Length; + } - int bucketNo = GetBucket(hashcode, tables._buckets.Length); + private bool TryGetValueInternal(TKey key, int hashcode, [MaybeNullWhen(false)] out TValue value) + { + Debug.Assert(_comparer.GetHashCode(key) == hashcode); + + // We must capture the _buckets field in a local variable. It is set to a new table on each table resize. + Tables tables = _tables; + + int bucketNo = GetBucket(hashcode, tables._buckets.Length); - // We can get away w/out a lock here. - // The Volatile.Read ensures that we have a copy of the reference to tables._buckets[bucketNo]. - // This protects us from reading fields ('_hashcode', '_key', '_value' and '_next') of different instances. - Thread.MemoryBarrier(); - Node n = tables._buckets[bucketNo]; + // We can get away w/out a lock here. + // The Volatile.Read ensures that we have a copy of the reference to tables._buckets[bucketNo]. + // This protects us from reading fields ('_hashcode', '_key', '_value' and '_next') of different instances. + Thread.MemoryBarrier(); + Node? n = tables._buckets[bucketNo]; - while (n != null) + while (n != null) + { + if (hashcode == n._hashcode && _comparer.Equals(n._key, key)) { - if (hashcode == n._hashcode && _comparer.Equals(n._key, key)) - { - value = n._value; - return true; - } - n = n._next; + value = n._value; + return true; } - value = default(TValue); - return false; + n = n._next; } - /// - /// Shared internal implementation for inserts and updates. - /// If key exists, we always return false; and if updateIfExists == true we force update with value; - /// If key doesn't exist, we always add value and return true; - /// - private bool TryAddInternal(TKey key, int hashcode, TValue value, bool updateIfExists, bool acquireLock, out TValue resultingValue) - { - Debug.Assert(_comparer.GetHashCode(key) == hashcode); + value = default; + return false; + } - while (true) - { - int bucketNo, lockNo; + /// + /// Shared internal implementation for inserts and updates. + /// If key exists, we always return false; and if updateIfExists == true we force update with value; + /// If key doesn't exist, we always add value and return true; + /// + private bool TryAddInternal(TKey key, int hashcode, TValue value, bool updateIfExists, bool acquireLock, out TValue resultingValue) + { + Debug.Assert(_comparer.GetHashCode(key) == hashcode); - Tables tables = _tables; - GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables._buckets.Length, tables._locks.Length); + while (true) + { + int bucketNo, lockNo; - bool resizeDesired = false; - bool lockTaken = false; - try + Tables tables = _tables; + GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables._buckets.Length, tables._locks.Length); + + bool resizeDesired = false; + bool lockTaken = false; + try + { + if (acquireLock) { - if (acquireLock) - { - lockTaken = Monitor.TryEnter(tables._locks[lockNo]); - } + lockTaken = Monitor.TryEnter(tables._locks[lockNo]); + } - // If the table just got resized, we may not be holding the right lock, and must retry. - // This should be a rare occurrence. - if (tables != _tables) - { - continue; - } + // If the table just got resized, we may not be holding the right lock, and must retry. + // This should be a rare occurrence. + if (tables != _tables) + { + continue; + } - // Try to find this key in the bucket - Node prev = null; - for (Node node = tables._buckets[bucketNo]; node != null; node = node._next) + // Try to find this key in the bucket + Node? prev = null; + for (Node? node = tables._buckets[bucketNo]; node != null; node = node._next) + { + Debug.Assert((prev == null && node == tables._buckets[bucketNo]) || prev!._next == node); + if (hashcode == node._hashcode && _comparer.Equals(node._key, key)) { - Debug.Assert((prev == null && node == tables._buckets[bucketNo]) || prev._next == node); - if (hashcode == node._hashcode && _comparer.Equals(node._key, key)) + // The key was found in the dictionary. If updates are allowed, update the value for that key. + // We need to create a new node for the update, in order to support TValue types that cannot + // be written atomically, since lock-free reads may be happening concurrently. + if (updateIfExists) { - // The key was found in the dictionary. If updates are allowed, update the value for that key. - // We need to create a new node for the update, in order to support TValue types that cannot - // be written atomically, since lock-free reads may be happening concurrently. - if (updateIfExists) + if (s_isValueWriteAtomic) + { + node._value = value; + } + else { - if (s_isValueWriteAtomic) + Node newNode = new Node(node._key, value, hashcode, node._next); + if (prev == null) { - node._value = value; + Interlocked.Exchange(ref tables._buckets[bucketNo], newNode); } else { - Node newNode = new Node(node._key, value, hashcode, node._next); - if (prev == null) - { - Interlocked.Exchange(ref tables._buckets[bucketNo], newNode); - } - else - { - prev._next = newNode; - } + prev._next = newNode; } - resultingValue = value; } - else - { - resultingValue = node._value; - } - return false; + resultingValue = value; } - prev = node; - } - - // The key was not found in the bucket. Insert the key-value pair. - Interlocked.Exchange(ref tables._buckets[bucketNo], new Node(key, value, hashcode, tables._buckets[bucketNo])); - checked - { - tables._countPerLock[lockNo]++; - } - - // - // If the number of elements guarded by this lock has exceeded the budget, resize the bucket table. - // It is also possible that GrowTable will increase the budget but won't resize the bucket table. - // That happens if the bucket table is found to be poorly utilized due to a bad hash function. - // - if (tables._countPerLock[lockNo] > _budget) - { - resizeDesired = true; + else + { + resultingValue = node._value; + } + return false; } + prev = node; } - finally + + // The key was not found in the bucket. Insert the key-value pair. + Interlocked.Exchange(ref tables._buckets[bucketNo], new Node(key, value, hashcode, tables._buckets[bucketNo])); + checked { - if (lockTaken) - { - Monitor.Exit(tables._locks[lockNo]); - } + tables._countPerLock[lockNo]++; } - // - // The fact that we got here means that we just performed an insertion. If necessary, we will grow the table. - // - // Concurrency notes: - // - Notice that we are not holding any locks at when calling GrowTable. This is necessary to prevent deadlocks. - // - As a result, it is possible that GrowTable will be called unnecessarily. But, GrowTable will obtain lock 0 - // and then verify that the table we passed to it as the argument is still the current table. - // - if (resizeDesired) + // If the number of elements guarded by this lock has exceeded the budget, resize the bucket table. + // It is also possible that GrowTable will increase the budget but won't resize the bucket table. + // That happens if the bucket table is found to be poorly utilized due to a bad hash function. + if (tables._countPerLock[lockNo] > _budget) { - GrowTable(tables); + resizeDesired = true; } + } + finally + { + if (lockTaken) + { + Monitor.Exit(tables._locks[lockNo]); + } + } - resultingValue = value; - return true; + // The fact that we got here means that we just performed an insertion. If necessary, we will grow the table. + // + // Concurrency notes: + // - Notice that we are not holding any locks at when calling GrowTable. This is necessary to prevent deadlocks. + // - As a result, it is possible that GrowTable will be called unnecessarily. But, GrowTable will obtain lock 0 + // and then verify that the table we passed to it as the argument is still the current table. + if (resizeDesired) + { + GrowTable(tables); } + + resultingValue = value; + return true; } + } - private static void ThrowKeyNullException() + [DoesNotReturn] + private static void ThrowKeyNullException() + => throw new ArgumentNullException("key"); + + /// + /// Adds a key/value pair to the + /// if the key does not already exist. + /// + /// The key of the element to add. + /// The function used to generate a value for the key. + /// is a null reference + /// (Nothing in Visual Basic). + /// is a null reference + /// (Nothing in Visual Basic). + /// The dictionary contains too many + /// elements. + /// The value for the key. This will be either the existing value for the key if the + /// key is already in the dictionary, or the new value for the key as returned by valueFactory + /// if the key was not in the dictionary. + public TValue GetOrAdd(TKey key, Func valueFactory) + { + if (key == null) { - throw new ArgumentNullException("key"); + ThrowKeyNullException(); } - /// - /// Adds a key/value pair to the - /// if the key does not already exist. - /// - /// The key of the element to add. - /// The function used to generate a value for the key. - /// is a null reference - /// (Nothing in Visual Basic). - /// is a null reference - /// (Nothing in Visual Basic). - /// The dictionary contains too many - /// elements. - /// The value for the key. This will be either the existing value for the key if the - /// key is already in the dictionary, or the new value for the key as returned by valueFactory - /// if the key was not in the dictionary. - public TValue GetOrAdd(TKey key, Func valueFactory) + if (valueFactory == null) { - if (key == null) - { - ThrowKeyNullException(); - } + throw new ArgumentNullException(nameof(valueFactory)); + } - if (valueFactory == null) + int hashcode = _comparer.GetHashCode(key); + + if (!TryGetValueInternal(key, hashcode, out TValue? resultingValue)) + { + TryAddInternal(key, hashcode, valueFactory(key), updateIfExists: false, acquireLock: true, out resultingValue); + } + + return resultingValue; + } + + /// + /// Replaces the bucket table with a larger one. To prevent multiple threads from resizing the + /// table as a result of races, the Tables instance that holds the table of buckets deemed too + /// small is passed in as an argument to GrowTable(). GrowTable() obtains a lock, and then checks + /// the Tables instance has been replaced in the meantime or not. + /// + private void GrowTable(Tables tables) + { + const int MaxArrayLength = 0X7FEFFFFF; + int locksAcquired = 0; + try + { + // The thread that first obtains _locks[0] will be the one doing the resize operation + AcquireLocks(0, 1, ref locksAcquired); + + // Make sure nobody resized the table while we were waiting for lock 0: + if (tables != _tables) { - throw new ArgumentNullException(nameof(valueFactory)); + // We assume that since the table reference is different, it was already resized (or the budget + // was adjusted). If we ever decide to do table shrinking, or replace the table for other reasons, + // we will have to revisit this logic. + return; } - int hashcode = _comparer.GetHashCode(key); - - TValue resultingValue; - if (!TryGetValueInternal(key, hashcode, out resultingValue)) + // Compute the (approx.) total size. Use an Int64 accumulation variable to avoid an overflow. + long approxCount = 0; + for (int i = 0; i < tables._countPerLock.Length; i++) { - TryAddInternal(key, hashcode, valueFactory(key), false, true, out resultingValue); + approxCount += tables._countPerLock[i]; } - return resultingValue; - } - /// - /// Replaces the bucket table with a larger one. To prevent multiple threads from resizing the - /// table as a result of races, the Tables instance that holds the table of buckets deemed too - /// small is passed in as an argument to GrowTable(). GrowTable() obtains a lock, and then checks - /// the Tables instance has been replaced in the meantime or not. - /// - private void GrowTable(Tables tables) - { - const int MaxArrayLength = 0X7FEFFFFF; - int locksAcquired = 0; - try + // If the bucket array is too empty, double the budget instead of resizing the table + if (approxCount < tables._buckets.Length / 4) { - // The thread that first obtains _locks[0] will be the one doing the resize operation - AcquireLocks(0, 1, ref locksAcquired); + _budget = 2 * _budget; - // Make sure nobody resized the table while we were waiting for lock 0: - if (tables != _tables) + if (_budget < 0) { - // We assume that since the table reference is different, it was already resized (or the budget - // was adjusted). If we ever decide to do table shrinking, or replace the table for other reasons, - // we will have to revisit this logic. - return; + _budget = int.MaxValue; } - // Compute the (approx.) total size. Use an Int64 accumulation variable to avoid an overflow. - long approxCount = 0; - for (int i = 0; i < tables._countPerLock.Length; i++) - { - approxCount += tables._countPerLock[i]; - } + return; + } - // - // If the bucket array is too empty, double the budget instead of resizing the table - // - if (approxCount < tables._buckets.Length / 4) + // Compute the new table size. We find the smallest integer larger than twice the previous table size, and not divisible by + // 2,3,5 or 7. We can consider a different table-sizing policy in the future. + int newLength = 0; + bool maximizeTableSize = false; + try + { + checked { - _budget = 2 * _budget; - if (_budget < 0) + // Double the size of the buckets table and add one, so that we have an odd integer. + newLength = (tables._buckets.Length * 2) + 1; + + // Now, we only need to check odd integers, and find the first that is not divisible + // by 3, 5 or 7. + while (newLength % 3 == 0 || newLength % 5 == 0 || newLength % 7 == 0) { - _budget = int.MaxValue; + newLength += 2; } - return; - } + Debug.Assert(newLength % 2 != 0); - // Compute the new table size. We find the smallest integer larger than twice the previous table size, and not divisible by - // 2,3,5 or 7. We can consider a different table-sizing policy in the future. - int newLength = 0; - bool maximizeTableSize = false; - try - { - checked + if (newLength > MaxArrayLength) { - // Double the size of the buckets table and add one, so that we have an odd integer. - newLength = (tables._buckets.Length * 2) + 1; - - // Now, we only need to check odd integers, and find the first that is not divisible - // by 3, 5 or 7. - while (newLength % 3 == 0 || newLength % 5 == 0 || newLength % 7 == 0) - { - newLength += 2; - } - - Debug.Assert(newLength % 2 != 0); - - if (newLength > MaxArrayLength) - { - maximizeTableSize = true; - } + maximizeTableSize = true; } } - catch (OverflowException) - { - maximizeTableSize = true; - } + } + catch (OverflowException) + { + maximizeTableSize = true; + } - if (maximizeTableSize) - { - newLength = MaxArrayLength; + if (maximizeTableSize) + { + newLength = MaxArrayLength; - // We want to make sure that GrowTable will not be called again, since table is at the maximum size. - // To achieve that, we set the budget to int.MaxValue. - // - // (There is one special case that would allow GrowTable() to be called in the future: - // calling Clear() on the ConcurrentDictionary will shrink the table and lower the budget.) - _budget = int.MaxValue; - } + // We want to make sure that GrowTable will not be called again, since table is at the maximum size. + // To achieve that, we set the budget to int.MaxValue. + // + // (There is one special case that would allow GrowTable() to be called in the future: + // calling Clear() on the ConcurrentDictionary will shrink the table and lower the budget.) + _budget = int.MaxValue; + } - // Now acquire all other locks for the table - AcquireLocks(1, tables._locks.Length, ref locksAcquired); + // Now acquire all other locks for the table + AcquireLocks(1, tables._locks.Length, ref locksAcquired); - object[] newLocks = tables._locks; + object[] newLocks = tables._locks; - // Add more locks - if (_growLockArray && tables._locks.Length < MaxLockNumber) + // Add more locks + if (_growLockArray && tables._locks.Length < MaxLockNumber) + { + newLocks = new object[tables._locks.Length * 2]; + Array.Copy(tables._locks, 0, newLocks, 0, tables._locks.Length); + for (int i = tables._locks.Length; i < newLocks.Length; i++) { - newLocks = new object[tables._locks.Length * 2]; - Array.Copy(tables._locks, 0, newLocks, 0, tables._locks.Length); - for (int i = tables._locks.Length; i < newLocks.Length; i++) - { - newLocks[i] = new object(); - } + newLocks[i] = new object(); } + } - Node[] newBuckets = new Node[newLength]; - int[] newCountPerLock = new int[newLocks.Length]; + Node[] newBuckets = new Node[newLength]; + int[] newCountPerLock = new int[newLocks.Length]; - // Copy all data into a new table, creating new nodes for all elements - for (int i = 0; i < tables._buckets.Length; i++) + // Copy all data into a new table, creating new nodes for all elements + for (int i = 0; i < tables._buckets.Length; i++) + { + Node? current = tables._buckets[i]; + while (current != null) { - Node current = tables._buckets[i]; - while (current != null) - { - Node next = current._next; - int newBucketNo, newLockNo; - GetBucketAndLockNo(current._hashcode, out newBucketNo, out newLockNo, newBuckets.Length, newLocks.Length); - - newBuckets[newBucketNo] = new Node(current._key, current._value, current._hashcode, newBuckets[newBucketNo]); + Node? next = current._next; + int newBucketNo, newLockNo; + GetBucketAndLockNo(current._hashcode, out newBucketNo, out newLockNo, newBuckets.Length, newLocks.Length); - checked - { - newCountPerLock[newLockNo]++; - } + newBuckets[newBucketNo] = new Node(current._key, current._value, current._hashcode, newBuckets[newBucketNo]); - current = next; + checked + { + newCountPerLock[newLockNo]++; } + + current = next; } + } - // Adjust the budget - _budget = Math.Max(1, newBuckets.Length / newLocks.Length); + // Adjust the budget + _budget = Math.Max(1, newBuckets.Length / newLocks.Length); - // Replace tables with the new versions - _tables = new Tables(newBuckets, newLocks, newCountPerLock); - } - finally - { - // Release all locks that we took earlier - ReleaseLocks(0, locksAcquired); - } + // Replace tables with the new versions + _tables = new Tables(newBuckets, newLocks, newCountPerLock); } - - /// - /// Computes the bucket for a particular key. - /// - private static int GetBucket(int hashcode, int bucketCount) + finally { - int bucketNo = (hashcode & 0x7fffffff) % bucketCount; - Debug.Assert(bucketNo >= 0 && bucketNo < bucketCount); - return bucketNo; + // Release all locks that we took earlier + ReleaseLocks(0, locksAcquired); } + } - /// - /// Computes the bucket and lock number for a particular key. - /// - private static void GetBucketAndLockNo(int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount) - { - bucketNo = (hashcode & 0x7fffffff) % bucketCount; - lockNo = bucketNo % lockCount; + /// + /// Computes the bucket for a particular key. + /// + private static int GetBucket(int hashcode, int bucketCount) + { + int bucketNo = (hashcode & 0x7fffffff) % bucketCount; + Debug.Assert(bucketNo >= 0 && bucketNo < bucketCount); - Debug.Assert(bucketNo >= 0 && bucketNo < bucketCount); - Debug.Assert(lockNo >= 0 && lockNo < lockCount); - } + return bucketNo; + } - /// - /// Acquires a contiguous range of locks for this hash table, and increments locksAcquired - /// by the number of locks that were successfully acquired. The locks are acquired in an - /// increasing order. - /// - private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired) - { - Debug.Assert(fromInclusive <= toExclusive); - object[] locks = _tables._locks; + /// + /// Computes the bucket and lock number for a particular key. + /// + private static void GetBucketAndLockNo(int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount) + { + bucketNo = (hashcode & 0x7fffffff) % bucketCount; + lockNo = bucketNo % lockCount; + + Debug.Assert(bucketNo >= 0 && bucketNo < bucketCount); + Debug.Assert(lockNo >= 0 && lockNo < lockCount); + } - for (int i = fromInclusive; i < toExclusive; i++) + /// + /// Acquires a contiguous range of locks for this hash table, and increments locksAcquired + /// by the number of locks that were successfully acquired. The locks are acquired in an + /// increasing order. + /// + private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired) + { + Debug.Assert(fromInclusive <= toExclusive); + object[] locks = _tables._locks; + + for (int i = fromInclusive; i < toExclusive; i++) + { + bool lockTaken = false; + try { - bool lockTaken = false; - try - { - lockTaken = Monitor.TryEnter(locks[i]); - } - finally + lockTaken = Monitor.TryEnter(locks[i]); + } + finally + { + if (lockTaken) { - if (lockTaken) - { - locksAcquired++; - } + locksAcquired++; } } } + } - /// - /// Releases a contiguous range of locks. - /// - private void ReleaseLocks(int fromInclusive, int toExclusive) - { - Debug.Assert(fromInclusive <= toExclusive); + /// + /// Releases a contiguous range of locks. + /// + private void ReleaseLocks(int fromInclusive, int toExclusive) + { + Debug.Assert(fromInclusive <= toExclusive); - for (int i = fromInclusive; i < toExclusive; i++) - { - Monitor.Exit(_tables._locks[i]); - } + for (int i = fromInclusive; i < toExclusive; i++) + { + Monitor.Exit(_tables._locks[i]); } + } - /// - /// A node in a singly-linked list representing a particular hash table bucket. - /// - private sealed class Node - { - internal readonly TKey _key; - internal TValue _value; - internal volatile Node _next; - internal readonly int _hashcode; + /// + /// A node in a singly-linked list representing a particular hash table bucket. + /// + private sealed class Node + { + internal readonly TKey _key; + internal TValue _value; + internal volatile Node? _next; + internal readonly int _hashcode; - internal Node(TKey key, TValue value, int hashcode, Node next) - { - _key = key; - _value = value; - _next = next; - _hashcode = hashcode; - } + internal Node(TKey key, TValue value, int hashcode, Node? next) + { + _key = key; + _value = value; + _next = next; + _hashcode = hashcode; } } } From 4f48280a2a7f4955ca17a406b55ff3d5d3a96980 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 6 Feb 2026 12:25:20 -0800 Subject: [PATCH 114/136] MSBuildTaskHost: Clean up ConcurrentQueue - File-scoped namespace - Expression-bodied members - Enable nullability --- .../Collections/ConcurrentQueue.cs | 950 +++++++++--------- 1 file changed, 477 insertions(+), 473 deletions(-) diff --git a/src/MSBuildTaskHost/Collections/ConcurrentQueue.cs b/src/MSBuildTaskHost/Collections/ConcurrentQueue.cs index dab75693e91..5310a53fd46 100644 --- a/src/MSBuildTaskHost/Collections/ConcurrentQueue.cs +++ b/src/MSBuildTaskHost/Collections/ConcurrentQueue.cs @@ -3,565 +3,569 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Threading; -#nullable disable +namespace Microsoft.Build.TaskHost.Collections; -namespace Microsoft.Build.TaskHost.Collections +// The following class is back-ported from .NET 4.X CoreFX library because +// MSBuildTaskHost requires 3.5 .NET Framework. Only important methods (Enqueue, TryDequeue) are kept. +internal class ConcurrentQueue { - // The following class is back-ported from .NET 4.X CoreFX library because - // MSBuildTaskHost requires 3.5 .NET Framework. Only important methods (Enqueue, TryDequeue) are kept. - internal class ConcurrentQueue + // This implementation provides an unbounded, multi-producer multi-consumer queue + // that supports the standard Enqueue/TryDequeue operations, as well as support for + // snapshot enumeration (GetEnumerator, ToArray, CopyTo), peeking, and Count/IsEmpty. + // It is composed of a linked list of bounded ring buffers, each of which has a head + // and a tail index, isolated from each other to minimize false sharing. As long as + // the number of elements in the queue remains less than the size of the current + // buffer (Segment), no additional allocations are required for enqueued items. When + // the number of items exceeds the size of the current segment, the current segment is + // "frozen" to prevent further enqueues, and a new segment is linked from it and set + // as the new tail segment for subsequent enqueues. As old segments are consumed by + // dequeues, the head reference is updated to point to the segment that dequeuers should + // try next. To support snapshot enumeration, segments also support the notion of + // preserving for observation, whereby they avoid overwriting state as part of dequeues. + // Any operation that requires a snapshot results in all current segments being + // both frozen for enqueues and preserved for observation: any new enqueues will go + // to new segments, and dequeuers will consume from the existing segments but without + // overwriting the existing data. + + /// Initial length of the segments used in the queue. + private const int InitialSegmentLength = 32; + + /// + /// Maximum length of the segments used in the queue. This is a somewhat arbitrary limit: + /// larger means that as long as we don't exceed the size, we avoid allocating more segments, + /// but if we do exceed it, then the segment becomes garbage. + /// + private const int MaxSegmentLength = 1024 * 1024; + + /// + /// Lock used to protect cross-segment operations, including any updates to or + /// and any operations that need to get a consistent view of them. + /// + private object _crossSegmentLock; + + /// The current tail segment. + private volatile Segment _tail; + + /// The current head segment. + private volatile Segment _head; + + internal static object VolatileReader(ref object o) => Thread.VolatileRead(ref o); + + /// + /// Initializes a new instance of the class. + /// + public ConcurrentQueue() { - // This implementation provides an unbounded, multi-producer multi-consumer queue - // that supports the standard Enqueue/TryDequeue operations, as well as support for - // snapshot enumeration (GetEnumerator, ToArray, CopyTo), peeking, and Count/IsEmpty. - // It is composed of a linked list of bounded ring buffers, each of which has a head - // and a tail index, isolated from each other to minimize false sharing. As long as - // the number of elements in the queue remains less than the size of the current - // buffer (Segment), no additional allocations are required for enqueued items. When - // the number of items exceeds the size of the current segment, the current segment is - // "frozen" to prevent further enqueues, and a new segment is linked from it and set - // as the new tail segment for subsequent enqueues. As old segments are consumed by - // dequeues, the head reference is updated to point to the segment that dequeuers should - // try next. To support snapshot enumeration, segments also support the notion of - // preserving for observation, whereby they avoid overwriting state as part of dequeues. - // Any operation that requires a snapshot results in all current segments being - // both frozen for enqueues and preserved for observation: any new enqueues will go - // to new segments, and dequeuers will consume from the existing segments but without - // overwriting the existing data. - - /// Initial length of the segments used in the queue. - private const int InitialSegmentLength = 32; - /// - /// Maximum length of the segments used in the queue. This is a somewhat arbitrary limit: - /// larger means that as long as we don't exceed the size, we avoid allocating more segments, - /// but if we do exceed it, then the segment becomes garbage. - /// - private const int MaxSegmentLength = 1024 * 1024; - - /// - /// Lock used to protect cross-segment operations, including any updates to or - /// and any operations that need to get a consistent view of them. - /// - private object _crossSegmentLock; - /// The current tail segment. - private volatile Segment _tail; - /// The current head segment. - private volatile Segment _head; + _crossSegmentLock = new object(); + _tail = _head = new Segment(InitialSegmentLength); + } - internal static object VolatileReader(ref object o) => Thread.VolatileRead(ref o); - /// - /// Initializes a new instance of the class. - /// - public ConcurrentQueue() + /// Adds an object to the end of the . + /// + /// The object to add to the end of the . + /// The value can be a null reference (Nothing in Visual Basic) for reference types. + /// + public void Enqueue(T item) + { + // Try to enqueue to the current tail. + if (!_tail.TryEnqueue(item)) { - _crossSegmentLock = new object(); - _tail = _head = new Segment(InitialSegmentLength); + // If we're unable to, we need to take a slow path that will + // try to add a new tail segment. + EnqueueSlow(item); } + } - /// Adds an object to the end of the . - /// - /// The object to add to the end of the . - /// The value can be a null reference (Nothing in Visual Basic) for reference types. - /// - public void Enqueue(T item) + /// Adds to the end of the queue, adding a new segment if necessary. + private void EnqueueSlow(T item) + { + while (true) { - // Try to enqueue to the current tail. - if (!_tail.TryEnqueue(item)) + Segment tail = _tail; + + // Try to append to the existing tail. + if (tail.TryEnqueue(item)) + { + return; + } + + // If we were unsuccessful, take the lock so that we can compare and manipulate + // the tail. Assuming another enqueuer hasn't already added a new segment, + // do so, then loop around to try enqueueing again. + lock (_crossSegmentLock) { - // If we're unable to, we need to take a slow path that will - // try to add a new tail segment. - EnqueueSlow(item); + if (tail == _tail) + { + // Make sure no one else can enqueue to this segment. + tail.EnsureFrozenForEnqueues(); + + // We determine the new segment's length based on the old length. + // In general, we double the size of the segment, to make it less likely + // that we'll need to grow again. However, if the tail segment is marked + // as preserved for observation, something caused us to avoid reusing this + // segment, and if that happens a lot and we grow, we'll end up allocating + // lots of wasted space. As such, in such situations we reset back to the + // initial segment length; if these observations are happening frequently, + // this will help to avoid wasted memory, and if they're not, we'll + // relatively quickly grow again to a larger size. + int nextSize = tail._preservedForObservation != 0 ? InitialSegmentLength : Math.Min(tail.Capacity * 2, MaxSegmentLength); + var newTail = new Segment(nextSize); + + // Hook up the new tail. + tail._nextSegment = newTail; + _tail = newTail; + } } } + } - /// Adds to the end of the queue, adding a new segment if necessary. - private void EnqueueSlow(T item) + /// + /// Attempts to remove and return the object at the beginning of the . + /// + /// + /// When this method returns, if the operation was successful, contains the + /// object removed. If no object was available to be removed, the value is unspecified. + /// + /// + /// true if an element was removed and returned from the beginning of the + /// successfully; otherwise, false. + /// + public bool TryDequeue([MaybeNullWhen(false)] out T result) => + _head.TryDequeue(out result) || // fast-path that operates just on the head segment + TryDequeueSlow(out result); // slow path that needs to fix up segments + + /// Tries to dequeue an item, removing empty segments as needed. + private bool TryDequeueSlow([MaybeNullWhen(false)] out T item) + { + while (true) { - while (true) + // Get the current head + Segment head = _head; + + // Try to take. If we're successful, we're done. + if (head.TryDequeue(out item)) { - Segment tail = _tail; + return true; + } - // Try to append to the existing tail. - if (tail.TryEnqueue(item)) - { - return; - } + // Check to see whether this segment is the last. If it is, we can consider + // this to be a moment-in-time empty condition (even though between the TryDequeue + // check and this check, another item could have arrived). + if (head._nextSegment == null) + { + item = default; + return false; + } - // If we were unsuccessful, take the lock so that we can compare and manipulate - // the tail. Assuming another enqueuer hasn't already added a new segment, - // do so, then loop around to try enqueueing again. - lock (_crossSegmentLock) + // At this point we know that head.Next != null, which means + // this segment has been frozen for additional enqueues. But between + // the time that we ran TryDequeue and checked for a next segment, + // another item could have been added. Try to dequeue one more time + // to confirm that the segment is indeed empty. + Debug.Assert(head._frozenForEnqueues); + if (head.TryDequeue(out item)) + { + return true; + } + + // This segment is frozen (nothing more can be added) and empty (nothing is in it). + // Update head to point to the next segment in the list, assuming no one's beat us to it. + lock (_crossSegmentLock) + { + if (head == _head) { - if (tail == _tail) - { - // Make sure no one else can enqueue to this segment. - tail.EnsureFrozenForEnqueues(); - - // We determine the new segment's length based on the old length. - // In general, we double the size of the segment, to make it less likely - // that we'll need to grow again. However, if the tail segment is marked - // as preserved for observation, something caused us to avoid reusing this - // segment, and if that happens a lot and we grow, we'll end up allocating - // lots of wasted space. As such, in such situations we reset back to the - // initial segment length; if these observations are happening frequently, - // this will help to avoid wasted memory, and if they're not, we'll - // relatively quickly grow again to a larger size. - int nextSize = tail._preservedForObservation != 0 ? InitialSegmentLength : Math.Min(tail.Capacity * 2, MaxSegmentLength); - var newTail = new Segment(nextSize); - - // Hook up the new tail. - tail._nextSegment = newTail; - _tail = newTail; - } + _head = head._nextSegment; } } } + } - /// - /// Attempts to remove and return the object at the beginning of the . - /// - /// - /// When this method returns, if the operation was successful, contains the - /// object removed. If no object was available to be removed, the value is unspecified. - /// - /// - /// true if an element was removed and returned from the beginning of the - /// successfully; otherwise, false. - /// - public bool TryDequeue(out T result) => - _head.TryDequeue(out result) || // fast-path that operates just on the head segment - TryDequeueSlow(out result); // slow path that needs to fix up segments - - /// Tries to dequeue an item, removing empty segments as needed. - private bool TryDequeueSlow(out T item) + /// + /// Attempts to return an object from the beginning of the + /// without removing it. + /// + /// + /// When this method returns, contains an object from + /// the beginning of the or default(T) + /// if the operation failed. + /// + /// true if and object was returned successfully; otherwise, false. + /// + /// For determining whether the collection contains any items, use of the + /// property is recommended rather than peeking. + /// + public bool TryPeek([MaybeNullWhen(false)] out T result) => TryPeek(out result, resultUsed: true); + + /// Attempts to retrieve the value for the first element in the queue. + /// The value of the first element, if found. + /// true if the result is neede; otherwise false if only the true/false outcome is needed. + /// true if an element was found; otherwise, false. + private bool TryPeek([MaybeNullWhen(false)] out T result, bool resultUsed) + { + // Starting with the head segment, look through all of the segments + // for the first one we can find that's not empty. + Segment s = _head; + while (true) { - while (true) + // Grab the next segment from this one, before we peek. + // This is to be able to see whether the value has changed + // during the peek operation. + Thread.MemoryBarrier(); + Segment? next = s._nextSegment; + + // Peek at the segment. If we find an element, we're done. + if (s.TryPeek(out result, resultUsed)) { - // Get the current head - Segment head = _head; + return true; + } - // Try to take. If we're successful, we're done. - if (head.TryDequeue(out item)) - { - return true; - } + // The current segment was empty at the moment we checked. - // Check to see whether this segment is the last. If it is, we can consider - // this to be a moment-in-time empty condition (even though between the TryDequeue - // check and this check, another item could have arrived). - if (head._nextSegment == null) + if (next != null) + { + // If prior to the peek there was already a next segment, then + // during the peek no additional items could have been enqueued + // to it and we can just move on to check the next segment. + Debug.Assert(next == s._nextSegment); + s = next; + } + else + { + Thread.MemoryBarrier(); + if (s._nextSegment == null) { - item = default(T); - return false; + // The next segment is null. Nothing more to peek at. + break; } + } - // At this point we know that head.Next != null, which means - // this segment has been frozen for additional enqueues. But between - // the time that we ran TryDequeue and checked for a next segment, - // another item could have been added. Try to dequeue one more time - // to confirm that the segment is indeed empty. - Debug.Assert(head._frozenForEnqueues); - if (head.TryDequeue(out item)) - { - return true; - } + // The next segment was null before we peeked but non-null after. + // That means either when we peeked the first segment had + // already been frozen but the new segment not yet added, + // or that the first segment was empty and between the time + // that we peeked and then checked _nextSegment, so many items + // were enqueued that we filled the first segment and went + // into the next. Since we need to peek in order, we simply + // loop around again to peek on the same segment. The next + // time around on this segment we'll then either successfully + // peek or we'll find that next was non-null before peeking, + // and we'll traverse to that segment. + } - // This segment is frozen (nothing more can be added) and empty (nothing is in it). - // Update head to point to the next segment in the list, assuming no one's beat us to it. - lock (_crossSegmentLock) - { - if (head == _head) - { - _head = head._nextSegment; - } - } + result = default; + return false; + } + + /// + /// Provides a multi-producer, multi-consumer thread-safe bounded segment. When the queue is full, + /// enqueues fail and return false. When the queue is empty, dequeues fail and return null. + /// These segments are linked together to form the unbounded . + /// + [DebuggerDisplay("Capacity = {Capacity}")] + private sealed class Segment + { + // Segment design is inspired by the algorithm outlined at: + // http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue + + /// The array of items in this queue. Each slot contains the item in that slot and its "sequence number". + internal readonly Slot[] _slots; + /// Mask for quickly accessing a position within the queue's array. + internal readonly int _slotsMask; + /// The head and tail positions, with padding to help avoid false sharing contention. + /// Dequeuing happens from the head, enqueuing happens at the tail. + internal PaddedHeadAndTail _headAndTail; // mutable struct: do not make this readonly + + /// Indicates whether the segment has been marked such that dequeues don't overwrite the removed data. + internal byte _preservedForObservation; + /// Indicates whether the segment has been marked such that no additional items may be enqueued. + internal bool _frozenForEnqueues; + /// The segment following this one in the queue, or null if this segment is the last in the queue. + internal Segment? _nextSegment; + + /// Creates the segment. + /// + /// The maximum number of elements the segment can contain. Must be a power of 2. + /// + public Segment(int boundedLength) + { + // Validate the length + Debug.Assert(boundedLength >= 2, $"Must be >= 2, got {boundedLength}"); + Debug.Assert((boundedLength & (boundedLength - 1)) == 0, $"Must be a power of 2, got {boundedLength}"); + + // Initialize the slots and the mask. The mask is used as a way of quickly doing "% _slots.Length", + // instead letting us do "& _slotsMask". + _slots = new Slot[boundedLength]; + _slotsMask = boundedLength - 1; + + // Initialize the sequence number for each slot. The sequence number provides a ticket that + // allows dequeuers to know whether they can dequeue and enqueuers to know whether they can + // enqueue. An enqueuer at position N can enqueue when the sequence number is N, and a dequeuer + // for position N can dequeue when the sequence number is N + 1. When an enqueuer is done writing + // at position N, it sets the sequence number to N + 1 so that a dequeuer will be able to dequeue, + // and when a dequeuer is done dequeueing at position N, it sets the sequence number to N + _slots.Length, + // so that when an enqueuer loops around the slots, it'll find that the sequence number at + // position N is N. This also means that when an enqueuer finds that at position N the sequence + // number is < N, there is still a value in that slot, i.e. the segment is full, and when a + // dequeuer finds that the value in a slot is < N + 1, there is nothing currently available to + // dequeue. (It is possible for multiple enqueuers to enqueue concurrently, writing into + // subsequent slots, and to have the first enqueuer take longer, so that the slots for 1, 2, 3, etc. + // may have values, but the 0th slot may still be being filled... in that case, TryDequeue will + // return false.) + for (int i = 0; i < _slots.Length; i++) + { + _slots[i].SequenceNumber = i; } } + /// Gets the number of elements this segment can store. + internal int Capacity => _slots.Length; + + /// Gets the "freeze offset" for this segment. + internal int FreezeOffset => _slots.Length * 2; + /// - /// Attempts to return an object from the beginning of the - /// without removing it. + /// Ensures that the segment will not accept any subsequent enqueues that aren't already underway. /// - /// - /// When this method returns, contains an object from - /// the beginning of the or default(T) - /// if the operation failed. - /// - /// true if and object was returned successfully; otherwise, false. /// - /// For determining whether the collection contains any items, use of the - /// property is recommended rather than peeking. + /// When we mark a segment as being frozen for additional enqueues, + /// we set the bool, but that's mostly + /// as a small helper to avoid marking it twice. The real marking comes + /// by modifying the Tail for the segment, increasing it by this + /// . This effectively knocks it off the + /// sequence expected by future enqueuers, such that any additional enqueuer + /// will be unable to enqueue due to it not lining up with the expected + /// sequence numbers. This value is chosen specially so that Tail will grow + /// to a value that maps to the same slot but that won't be confused with + /// any other enqueue/dequeue sequence number. /// - public bool TryPeek(out T result) => TryPeek(out result, resultUsed: true); - - /// Attempts to retrieve the value for the first element in the queue. - /// The value of the first element, if found. - /// true if the result is neede; otherwise false if only the true/false outcome is needed. - /// true if an element was found; otherwise, false. - private bool TryPeek(out T result, bool resultUsed) + internal void EnsureFrozenForEnqueues() // must only be called while queue's segment lock is held { - // Starting with the head segment, look through all of the segments - // for the first one we can find that's not empty. - Segment s = _head; - while (true) + if (!_frozenForEnqueues) // flag used to ensure we don't increase the Tail more than once if frozen more than once { - // Grab the next segment from this one, before we peek. - // This is to be able to see whether the value has changed - // during the peek operation. - Thread.MemoryBarrier(); - Segment next = s._nextSegment; - - // Peek at the segment. If we find an element, we're done. - if (s.TryPeek(out result, resultUsed)) - { - return true; - } - - // The current segment was empty at the moment we checked. + _frozenForEnqueues = true; - if (next != null) - { - // If prior to the peek there was already a next segment, then - // during the peek no additional items could have been enqueued - // to it and we can just move on to check the next segment. - Debug.Assert(next == s._nextSegment); - s = next; - } - else + // Increase the tail by FreezeOffset, spinning until we're successful in doing so. + while (true) { - Thread.MemoryBarrier(); - if (s._nextSegment == null) + int tail = Thread.VolatileRead(ref _headAndTail.Tail); + if (Interlocked.CompareExchange(ref _headAndTail.Tail, tail + FreezeOffset, tail) == tail) { - // The next segment is null. Nothing more to peek at. break; } + Thread.SpinWait(1); } - - // The next segment was null before we peeked but non-null after. - // That means either when we peeked the first segment had - // already been frozen but the new segment not yet added, - // or that the first segment was empty and between the time - // that we peeked and then checked _nextSegment, so many items - // were enqueued that we filled the first segment and went - // into the next. Since we need to peek in order, we simply - // loop around again to peek on the same segment. The next - // time around on this segment we'll then either successfully - // peek or we'll find that next was non-null before peeking, - // and we'll traverse to that segment. } - - result = default(T); - return false; } - /// - /// Provides a multi-producer, multi-consumer thread-safe bounded segment. When the queue is full, - /// enqueues fail and return false. When the queue is empty, dequeues fail and return null. - /// These segments are linked together to form the unbounded . - /// - [DebuggerDisplay("Capacity = {Capacity}")] - private sealed class Segment + /// Tries to dequeue an element from the queue. + public bool TryDequeue([MaybeNullWhen(false)] out T item) { - // Segment design is inspired by the algorithm outlined at: - // http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue - - /// The array of items in this queue. Each slot contains the item in that slot and its "sequence number". - internal readonly Slot[] _slots; - /// Mask for quickly accessing a position within the queue's array. - internal readonly int _slotsMask; - /// The head and tail positions, with padding to help avoid false sharing contention. - /// Dequeuing happens from the head, enqueuing happens at the tail. - internal PaddedHeadAndTail _headAndTail; // mutable struct: do not make this readonly - - /// Indicates whether the segment has been marked such that dequeues don't overwrite the removed data. - internal byte _preservedForObservation; - /// Indicates whether the segment has been marked such that no additional items may be enqueued. - internal bool _frozenForEnqueues; - /// The segment following this one in the queue, or null if this segment is the last in the queue. - internal Segment _nextSegment; - - /// Creates the segment. - /// - /// The maximum number of elements the segment can contain. Must be a power of 2. - /// - public Segment(int boundedLength) + // Loop in case of contention... + while (true) { - // Validate the length - Debug.Assert(boundedLength >= 2, $"Must be >= 2, got {boundedLength}"); - Debug.Assert((boundedLength & (boundedLength - 1)) == 0, $"Must be a power of 2, got {boundedLength}"); - - // Initialize the slots and the mask. The mask is used as a way of quickly doing "% _slots.Length", - // instead letting us do "& _slotsMask". - _slots = new Slot[boundedLength]; - _slotsMask = boundedLength - 1; - - // Initialize the sequence number for each slot. The sequence number provides a ticket that - // allows dequeuers to know whether they can dequeue and enqueuers to know whether they can - // enqueue. An enqueuer at position N can enqueue when the sequence number is N, and a dequeuer - // for position N can dequeue when the sequence number is N + 1. When an enqueuer is done writing - // at position N, it sets the sequence number to N + 1 so that a dequeuer will be able to dequeue, - // and when a dequeuer is done dequeueing at position N, it sets the sequence number to N + _slots.Length, - // so that when an enqueuer loops around the slots, it'll find that the sequence number at - // position N is N. This also means that when an enqueuer finds that at position N the sequence - // number is < N, there is still a value in that slot, i.e. the segment is full, and when a - // dequeuer finds that the value in a slot is < N + 1, there is nothing currently available to - // dequeue. (It is possible for multiple enqueuers to enqueue concurrently, writing into - // subsequent slots, and to have the first enqueuer take longer, so that the slots for 1, 2, 3, etc. - // may have values, but the 0th slot may still be being filled... in that case, TryDequeue will - // return false.) - for (int i = 0; i < _slots.Length; i++) - { - _slots[i].SequenceNumber = i; - } - } + // Get the head at which to try to dequeue. + int currentHead = Thread.VolatileRead(ref _headAndTail.Head); + int slotsIndex = currentHead & _slotsMask; - /// Gets the number of elements this segment can store. - internal int Capacity => _slots.Length; - - /// Gets the "freeze offset" for this segment. - internal int FreezeOffset => _slots.Length * 2; - - /// - /// Ensures that the segment will not accept any subsequent enqueues that aren't already underway. - /// - /// - /// When we mark a segment as being frozen for additional enqueues, - /// we set the bool, but that's mostly - /// as a small helper to avoid marking it twice. The real marking comes - /// by modifying the Tail for the segment, increasing it by this - /// . This effectively knocks it off the - /// sequence expected by future enqueuers, such that any additional enqueuer - /// will be unable to enqueue due to it not lining up with the expected - /// sequence numbers. This value is chosen specially so that Tail will grow - /// to a value that maps to the same slot but that won't be confused with - /// any other enqueue/dequeue sequence number. - /// - internal void EnsureFrozenForEnqueues() // must only be called while queue's segment lock is held - { - if (!_frozenForEnqueues) // flag used to ensure we don't increase the Tail more than once if frozen more than once - { - _frozenForEnqueues = true; + // Read the sequence number for the head position. + int sequenceNumber = Thread.VolatileRead(ref _slots[slotsIndex].SequenceNumber); - // Increase the tail by FreezeOffset, spinning until we're successful in doing so. - while (true) + // We can dequeue from this slot if it's been filled by an enqueuer, which + // would have left the sequence number at pos+1. + int diff = sequenceNumber - (currentHead + 1); + if (diff == 0) + { + // We may be racing with other dequeuers. Try to reserve the slot by incrementing + // the head. Once we've done that, no one else will be able to read from this slot, + // and no enqueuer will be able to read from this slot until we've written the new + // sequence number. WARNING: The next few lines are not reliable on a runtime that + // supports thread aborts. If a thread abort were to sneak in after the CompareExchange + // but before the Volatile.Write, enqueuers trying to enqueue into this slot would + // spin indefinitely. If this implementation is ever used on such a platform, this + // if block should be wrapped in a finally / prepared region. + if (Interlocked.CompareExchange(ref _headAndTail.Head, currentHead + 1, currentHead) == currentHead) { - int tail = Thread.VolatileRead(ref _headAndTail.Tail); - if (Interlocked.CompareExchange(ref _headAndTail.Tail, tail + FreezeOffset, tail) == tail) + // Successfully reserved the slot. Note that after the above CompareExchange, other threads + // trying to dequeue from this slot will end up spinning until we do the subsequent Write. + item = _slots[slotsIndex].Item!; + if (Thread.VolatileRead(ref _preservedForObservation) == 0) { - break; + // If we're preserving, though, we don't zero out the slot, as we need it for + // enumerations, peeking, ToArray, etc. And we don't update the sequence number, + // so that an enqueuer will see it as full and be forced to move to a new segment. + _slots[slotsIndex].Item = default; + Thread.VolatileWrite(ref _slots[slotsIndex].SequenceNumber, currentHead + _slots.Length); } - Thread.SpinWait(1); + + return true; } } - } - - /// Tries to dequeue an element from the queue. - public bool TryDequeue(out T item) - { - // Loop in case of contention... - while (true) + else if (diff < 0) { - // Get the head at which to try to dequeue. - int currentHead = Thread.VolatileRead(ref _headAndTail.Head); - int slotsIndex = currentHead & _slotsMask; - - // Read the sequence number for the head position. - int sequenceNumber = Thread.VolatileRead(ref _slots[slotsIndex].SequenceNumber); - - // We can dequeue from this slot if it's been filled by an enqueuer, which - // would have left the sequence number at pos+1. - int diff = sequenceNumber - (currentHead + 1); - if (diff == 0) - { - // We may be racing with other dequeuers. Try to reserve the slot by incrementing - // the head. Once we've done that, no one else will be able to read from this slot, - // and no enqueuer will be able to read from this slot until we've written the new - // sequence number. WARNING: The next few lines are not reliable on a runtime that - // supports thread aborts. If a thread abort were to sneak in after the CompareExchange - // but before the Volatile.Write, enqueuers trying to enqueue into this slot would - // spin indefinitely. If this implementation is ever used on such a platform, this - // if block should be wrapped in a finally / prepared region. - if (Interlocked.CompareExchange(ref _headAndTail.Head, currentHead + 1, currentHead) == currentHead) - { - // Successfully reserved the slot. Note that after the above CompareExchange, other threads - // trying to dequeue from this slot will end up spinning until we do the subsequent Write. - item = _slots[slotsIndex].Item; - if (Thread.VolatileRead(ref _preservedForObservation) == 0) - { - // If we're preserving, though, we don't zero out the slot, as we need it for - // enumerations, peeking, ToArray, etc. And we don't update the sequence number, - // so that an enqueuer will see it as full and be forced to move to a new segment. - _slots[slotsIndex].Item = default(T); - Thread.VolatileWrite(ref _slots[slotsIndex].SequenceNumber, currentHead + _slots.Length); - } - return true; - } - } - else if (diff < 0) + // The sequence number was less than what we needed, which means this slot doesn't + // yet contain a value we can dequeue, i.e. the segment is empty. Technically it's + // possible that multiple enqueuers could have written concurrently, with those + // getting later slots actually finishing first, so there could be elements after + // this one that are available, but we need to dequeue in order. So before declaring + // failure and that the segment is empty, we check the tail to see if we're actually + // empty or if we're just waiting for items in flight or after this one to become available. + bool frozen = _frozenForEnqueues; + int currentTail = Thread.VolatileRead(ref _headAndTail.Tail); + if (currentTail - currentHead <= 0 || (frozen && (currentTail - FreezeOffset - currentHead <= 0))) { - // The sequence number was less than what we needed, which means this slot doesn't - // yet contain a value we can dequeue, i.e. the segment is empty. Technically it's - // possible that multiple enqueuers could have written concurrently, with those - // getting later slots actually finishing first, so there could be elements after - // this one that are available, but we need to dequeue in order. So before declaring - // failure and that the segment is empty, we check the tail to see if we're actually - // empty or if we're just waiting for items in flight or after this one to become available. - bool frozen = _frozenForEnqueues; - int currentTail = Thread.VolatileRead(ref _headAndTail.Tail); - if (currentTail - currentHead <= 0 || (frozen && (currentTail - FreezeOffset - currentHead <= 0))) - { - item = default(T); - return false; - } - - // It's possible it could have become frozen after we checked _frozenForEnqueues - // and before reading the tail. That's ok: in that rare race condition, we just - // loop around again. + item = default; + return false; } - // Lost a race. Spin a bit, then try again. - Thread.SpinWait(1); + // It's possible it could have become frozen after we checked _frozenForEnqueues + // and before reading the tail. That's ok: in that rare race condition, we just + // loop around again. } + + // Lost a race. Spin a bit, then try again. + Thread.SpinWait(1); } + } - /// Tries to peek at an element from the queue, without removing it. - public bool TryPeek(out T result, bool resultUsed) + /// Tries to peek at an element from the queue, without removing it. + public bool TryPeek([MaybeNullWhen(false)] out T result, bool resultUsed) + { + if (resultUsed) { - if (resultUsed) - { - // In order to ensure we don't get a torn read on the value, we mark the segment - // as preserving for observation. Additional items can still be enqueued to this - // segment, but no space will be freed during dequeues, such that the segment will - // no longer be reusable. - _preservedForObservation = 1; - Thread.MemoryBarrier(); - } + // In order to ensure we don't get a torn read on the value, we mark the segment + // as preserving for observation. Additional items can still be enqueued to this + // segment, but no space will be freed during dequeues, such that the segment will + // no longer be reusable. + _preservedForObservation = 1; + Thread.MemoryBarrier(); + } - // Loop in case of contention... - while (true) - { - // Get the head at which to try to peek. - int currentHead = Thread.VolatileRead(ref _headAndTail.Head); - int slotsIndex = currentHead & _slotsMask; + // Loop in case of contention... + while (true) + { + // Get the head at which to try to peek. + int currentHead = Thread.VolatileRead(ref _headAndTail.Head); + int slotsIndex = currentHead & _slotsMask; - // Read the sequence number for the head position. - int sequenceNumber = Thread.VolatileRead(ref _slots[slotsIndex].SequenceNumber); + // Read the sequence number for the head position. + int sequenceNumber = Thread.VolatileRead(ref _slots[slotsIndex].SequenceNumber); - // We can peek from this slot if it's been filled by an enqueuer, which - // would have left the sequence number at pos+1. - int diff = sequenceNumber - (currentHead + 1); - if (diff == 0) - { - result = resultUsed ? _slots[slotsIndex].Item : default(T); - return true; - } - else if (diff < 0) + // We can peek from this slot if it's been filled by an enqueuer, which + // would have left the sequence number at pos+1. + int diff = sequenceNumber - (currentHead + 1); + if (diff == 0) + { + result = resultUsed ? _slots[slotsIndex].Item! : default!; + return true; + } + else if (diff < 0) + { + // The sequence number was less than what we needed, which means this slot doesn't + // yet contain a value we can peek, i.e. the segment is empty. Technically it's + // possible that multiple enqueuers could have written concurrently, with those + // getting later slots actually finishing first, so there could be elements after + // this one that are available, but we need to peek in order. So before declaring + // failure and that the segment is empty, we check the tail to see if we're actually + // empty or if we're just waiting for items in flight or after this one to become available. + bool frozen = _frozenForEnqueues; + int currentTail = Thread.VolatileRead(ref _headAndTail.Tail); + if (currentTail - currentHead <= 0 || (frozen && (currentTail - FreezeOffset - currentHead <= 0))) { - // The sequence number was less than what we needed, which means this slot doesn't - // yet contain a value we can peek, i.e. the segment is empty. Technically it's - // possible that multiple enqueuers could have written concurrently, with those - // getting later slots actually finishing first, so there could be elements after - // this one that are available, but we need to peek in order. So before declaring - // failure and that the segment is empty, we check the tail to see if we're actually - // empty or if we're just waiting for items in flight or after this one to become available. - bool frozen = _frozenForEnqueues; - int currentTail = Thread.VolatileRead(ref _headAndTail.Tail); - if (currentTail - currentHead <= 0 || (frozen && (currentTail - FreezeOffset - currentHead <= 0))) - { - result = default(T); - return false; - } - - // It's possible it could have become frozen after we checked _frozenForEnqueues - // and before reading the tail. That's ok: in that rare race condition, we just - // loop around again. + result = default; + return false; } - // Lost a race. Spin a bit, then try again. - Thread.SpinWait(1); + // It's possible it could have become frozen after we checked _frozenForEnqueues + // and before reading the tail. That's ok: in that rare race condition, we just + // loop around again. } + + // Lost a race. Spin a bit, then try again. + Thread.SpinWait(1); } + } - /// - /// Attempts to enqueue the item. If successful, the item will be stored - /// in the queue and true will be returned; otherwise, the item won't be stored, and false - /// will be returned. - /// - public bool TryEnqueue(T item) + /// + /// Attempts to enqueue the item. If successful, the item will be stored + /// in the queue and true will be returned; otherwise, the item won't be stored, and false + /// will be returned. + /// + public bool TryEnqueue(T item) + { + // Loop in case of contention... + while (true) { - // Loop in case of contention... - while (true) - { - // Get the tail at which to try to return. - int currentTail = Thread.VolatileRead(ref _headAndTail.Tail); - int slotsIndex = currentTail & _slotsMask; + // Get the tail at which to try to return. + int currentTail = Thread.VolatileRead(ref _headAndTail.Tail); + int slotsIndex = currentTail & _slotsMask; - // Read the sequence number for the tail position. - int sequenceNumber = Thread.VolatileRead(ref _slots[slotsIndex].SequenceNumber); + // Read the sequence number for the tail position. + int sequenceNumber = Thread.VolatileRead(ref _slots[slotsIndex].SequenceNumber); - // The slot is empty and ready for us to enqueue into it if its sequence - // number matches the slot. - int diff = sequenceNumber - currentTail; - if (diff == 0) - { - // We may be racing with other enqueuers. Try to reserve the slot by incrementing - // the tail. Once we've done that, no one else will be able to write to this slot, - // and no dequeuer will be able to read from this slot until we've written the new - // sequence number. WARNING: The next few lines are not reliable on a runtime that - // supports thread aborts. If a thread abort were to sneak in after the CompareExchange - // but before the Volatile.Write, other threads will spin trying to access this slot. - // If this implementation is ever used on such a platform, this if block should be - // wrapped in a finally / prepared region. - if (Interlocked.CompareExchange(ref _headAndTail.Tail, currentTail + 1, currentTail) == currentTail) - { - // Successfully reserved the slot. Note that after the above CompareExchange, other threads - // trying to return will end up spinning until we do the subsequent Write. - _slots[slotsIndex].Item = item; - Thread.VolatileWrite(ref _slots[slotsIndex].SequenceNumber, currentTail + 1); - return true; - } - } - else if (diff < 0) + // The slot is empty and ready for us to enqueue into it if its sequence + // number matches the slot. + int diff = sequenceNumber - currentTail; + if (diff == 0) + { + // We may be racing with other enqueuers. Try to reserve the slot by incrementing + // the tail. Once we've done that, no one else will be able to write to this slot, + // and no dequeuer will be able to read from this slot until we've written the new + // sequence number. WARNING: The next few lines are not reliable on a runtime that + // supports thread aborts. If a thread abort were to sneak in after the CompareExchange + // but before the Volatile.Write, other threads will spin trying to access this slot. + // If this implementation is ever used on such a platform, this if block should be + // wrapped in a finally / prepared region. + if (Interlocked.CompareExchange(ref _headAndTail.Tail, currentTail + 1, currentTail) == currentTail) { - // The sequence number was less than what we needed, which means this slot still - // contains a value, i.e. the segment is full. Technically it's possible that multiple - // dequeuers could have read concurrently, with those getting later slots actually - // finishing first, so there could be spaces after this one that are available, but - // we need to enqueue in order. - return false; + // Successfully reserved the slot. Note that after the above CompareExchange, other threads + // trying to return will end up spinning until we do the subsequent Write. + _slots[slotsIndex].Item = item; + Thread.VolatileWrite(ref _slots[slotsIndex].SequenceNumber, currentTail + 1); + return true; } - - // Lost a race. Spin a bit, then try again. - Thread.SpinWait(1); } - } + else if (diff < 0) + { + // The sequence number was less than what we needed, which means this slot still + // contains a value, i.e. the segment is full. Technically it's possible that multiple + // dequeuers could have read concurrently, with those getting later slots actually + // finishing first, so there could be spaces after this one that are available, but + // we need to enqueue in order. + return false; + } - /// Represents a slot in the queue. - [StructLayout(LayoutKind.Auto)] - [DebuggerDisplay("Item = {Item}, SequenceNumber = {SequenceNumber}")] - internal struct Slot - { - /// The item. - public T Item; - /// The sequence number for this slot, used to synchronize between enqueuers and dequeuers. - public int SequenceNumber; + // Lost a race. Spin a bit, then try again. + Thread.SpinWait(1); } } - } - /// Padded head and tail indices, to avoid false sharing between producers and consumers. - [DebuggerDisplay("Head = {Head}, Tail = {Tail}")] - [StructLayout(LayoutKind.Explicit, Size = 192)] // padding before/between/after fields based on typical cache line size of 64 - internal struct PaddedHeadAndTail - { - [FieldOffset(64)] - public int Head; + /// Represents a slot in the queue. + [StructLayout(LayoutKind.Auto)] + [DebuggerDisplay("Item = {Item}, SequenceNumber = {SequenceNumber}")] + internal struct Slot + { + /// The item. + public T? Item; - [FieldOffset(128)] - public int Tail; + /// The sequence number for this slot, used to synchronize between enqueuers and dequeuers. + public int SequenceNumber; + } } } + +/// Padded head and tail indices, to avoid false sharing between producers and consumers. +[DebuggerDisplay("Head = {Head}, Tail = {Tail}")] +[StructLayout(LayoutKind.Explicit, Size = 192)] // padding before/between/after fields based on typical cache line size of 64 +internal struct PaddedHeadAndTail +{ + [FieldOffset(64)] + public int Head; + + [FieldOffset(128)] + public int Tail; +} From ae8b9ae793de2d2650e0594cfd2fd067200626e7 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 6 Feb 2026 12:40:23 -0800 Subject: [PATCH 115/136] MSBuildTaskHost: Clean up InternalErrorException - File-scoped namespace - Expression-bodied members - Enable nullability --- .../BuildExceptionSerializationHelper.cs | 39 ++++++++------- .../Exceptions/InternalErrorException.cs | 48 ++++++------------- 2 files changed, 34 insertions(+), 53 deletions(-) diff --git a/src/MSBuildTaskHost/Exceptions/BuildExceptionSerializationHelper.cs b/src/MSBuildTaskHost/Exceptions/BuildExceptionSerializationHelper.cs index 1c1ddbb4103..1f5603b0fee 100644 --- a/src/MSBuildTaskHost/Exceptions/BuildExceptionSerializationHelper.cs +++ b/src/MSBuildTaskHost/Exceptions/BuildExceptionSerializationHelper.cs @@ -4,33 +4,32 @@ using System; using System.Collections.Generic; -namespace Microsoft.Build.TaskHost.Exceptions +namespace Microsoft.Build.TaskHost.Exceptions; + +internal static class BuildExceptionSerializationHelper { - internal static class BuildExceptionSerializationHelper + private static Dictionary> s_exceptionFactories = new() { - private static Dictionary> s_exceptionFactories = new() - { - { GetSerializationKey(), InternalErrorException.CreateFromRemote } - }; + { GetSerializationKey(), InternalErrorException.CreateFromRemote } + }; - private static readonly Func s_defaultFactory = - (message, innerException) => new GeneralBuildTransferredException(message, innerException); + private static readonly Func s_defaultFactory = + (message, innerException) => new GeneralBuildTransferredException(message, innerException); - public static string GetSerializationKey() - where T : BuildExceptionBase - => GetSerializationKey(typeof(T)); + public static string GetSerializationKey() + where T : BuildExceptionBase + => GetSerializationKey(typeof(T)); - public static string GetSerializationKey(Type exceptionType) - => exceptionType.FullName ?? exceptionType.ToString(); + public static string GetSerializationKey(Type exceptionType) + => exceptionType.FullName ?? exceptionType.ToString(); - public static BuildExceptionBase DeserializeException(string serializationType, string message, Exception? innerException) + public static BuildExceptionBase DeserializeException(string serializationType, string message, Exception? innerException) + { + if (s_exceptionFactories.TryGetValue(serializationType, out var factory)) { - if (s_exceptionFactories.TryGetValue(serializationType, out var factory)) - { - factory = s_defaultFactory; - } - - return factory(message, innerException); + factory = s_defaultFactory; } + + return factory(message, innerException); } } diff --git a/src/MSBuildTaskHost/Exceptions/InternalErrorException.cs b/src/MSBuildTaskHost/Exceptions/InternalErrorException.cs index 1207c4c459f..688958af533 100644 --- a/src/MSBuildTaskHost/Exceptions/InternalErrorException.cs +++ b/src/MSBuildTaskHost/Exceptions/InternalErrorException.cs @@ -5,8 +5,6 @@ using System.Diagnostics; using System.Runtime.Serialization; -#nullable disable - namespace Microsoft.Build.TaskHost.Exceptions; /// @@ -17,21 +15,13 @@ namespace Microsoft.Build.TaskHost.Exceptions; [Serializable] internal sealed class InternalErrorException : BuildExceptionBase { - /// - /// Default constructor. - /// SHOULD ONLY BE CALLED BY DESERIALIZER. - /// SUPPLY A MESSAGE INSTEAD. - /// private InternalErrorException() : base() { } - /// - /// Creates an instance of this exception using the given message. - /// internal InternalErrorException(string message) - : base("MSB0001: Internal MSBuild Error: " + message) + : base($"MSB0001: Internal MSBuild Error: {message}") { ConsiderDebuggerLaunch(message, null); } @@ -41,24 +31,16 @@ internal InternalErrorException(string message) /// Adds the inner exception's details to the exception message because most bug reporters don't bother /// to provide the inner exception details which is typically what we care about. /// - internal InternalErrorException(string message, Exception innerException) - : this(message, innerException, false) + internal InternalErrorException(string message, Exception? innerException) + : this(message, innerException, calledFromDeserialization: false) { } - internal static InternalErrorException CreateFromRemote(string message, Exception innerException) - { - return new InternalErrorException(message, innerException, calledFromDeserialization: true); - } + internal static InternalErrorException CreateFromRemote(string message, Exception? innerException) + => new(message, innerException, calledFromDeserialization: true); - private InternalErrorException(string message, Exception innerException, bool calledFromDeserialization) - : base( - calledFromDeserialization - ? message - : "MSB0001: Internal MSBuild Error: " + message + (innerException == null - ? string.Empty - : $"\n=============\n{innerException}\n\n"), - innerException) + private InternalErrorException(string message, Exception? innerException, bool calledFromDeserialization) + : base(GetMessage(calledFromDeserialization, message, innerException)) { if (!calledFromDeserialization) { @@ -66,7 +48,12 @@ private InternalErrorException(string message, Exception innerException, bool ca } } - #region Serialization (update when adding new class members) + private static string GetMessage(bool calledFromDeserialization, string message, Exception? innerException) + => calledFromDeserialization + ? message + : innerException is null + ? $"MSB0001: Internal MSBuild Error: {message}" + : $"MSB0001: Internal MSBuild Error: {message}\n=============\n{innerException}\n\n"; /// /// Private constructor used for (de)serialization. The constructor is private as this class is sealed @@ -78,10 +65,6 @@ private InternalErrorException(SerializationInfo info, StreamingContext context) // Do nothing: no fields } - // Base implementation of GetObjectData() is sufficient; we have no fields - #endregion - - #region ConsiderDebuggerLaunch /// /// A fatal internal error due to a bug has occurred. Give the dev a chance to debug it, if possible. /// @@ -102,9 +85,9 @@ private InternalErrorException(SerializationInfo info, StreamingContext context) /// If it is going to launch the debugger, it first does a Debug.Fail to give information about what needs to /// be debugged -- the exception hasn't been thrown yet. This automatically displays the current callstack. /// - private static void ConsiderDebuggerLaunch(string message, Exception innerException) + private static void ConsiderDebuggerLaunch(string message, Exception? innerException) { - string innerMessage = (innerException == null) ? String.Empty : innerException.ToString(); + string innerMessage = innerException == null ? string.Empty : innerException.ToString(); if (Environment.GetEnvironmentVariable("MSBUILDLAUNCHDEBUGGER") != null) { @@ -140,5 +123,4 @@ private static void LaunchDebugger(string message, string innerMessage) } #endif } - #endregion } From ae0c6b18d56c99356310b574b6351a106d1dfd2d Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 6 Feb 2026 12:42:20 -0800 Subject: [PATCH 116/136] MSBuildTaskHost: Clean up EnvironmentUtilities - File-scoped namespace --- .../Utilities/EnvironmentUtilities.cs | 85 +++++++++---------- 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/src/MSBuildTaskHost/Utilities/EnvironmentUtilities.cs b/src/MSBuildTaskHost/Utilities/EnvironmentUtilities.cs index 8a430cf3a5a..ef04d7441ed 100644 --- a/src/MSBuildTaskHost/Utilities/EnvironmentUtilities.cs +++ b/src/MSBuildTaskHost/Utilities/EnvironmentUtilities.cs @@ -1,63 +1,60 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable enable - using System.Diagnostics; using System.Threading; -namespace Microsoft.Build.TaskHost.Utilities +namespace Microsoft.Build.TaskHost.Utilities; + +internal static partial class EnvironmentUtilities { - internal static partial class EnvironmentUtilities - { - private static volatile int s_processId; - private static volatile string? s_processPath; + private static volatile int s_processId; + private static volatile string? s_processPath; - /// Gets the unique identifier for the current process. - public static int CurrentProcessId + /// Gets the unique identifier for the current process. + public static int CurrentProcessId + { + get { - get + // copied from Environment.ProcessId + int processId = s_processId; + if (processId == 0) { - // copied from Environment.ProcessId - int processId = s_processId; - if (processId == 0) - { - using Process currentProcess = Process.GetCurrentProcess(); - s_processId = processId = currentProcess.Id; - - // Assume that process Id zero is invalid for user processes. It holds for all mainstream operating systems. - Debug.Assert(processId != 0); - } - - return processId; + using Process currentProcess = Process.GetCurrentProcess(); + s_processId = processId = currentProcess.Id; + + // Assume that process Id zero is invalid for user processes. It holds for all mainstream operating systems. + Debug.Assert(processId != 0); } + + return processId; } + } - /// - /// Returns the path of the executable that started the currently executing process. Returns null when the path is not available. - /// - /// Path of the executable that started the currently executing process - /// - /// If the executable is renamed or deleted before this property is first accessed, the return value is undefined and depends on the operating system. - /// - public static string? ProcessPath + /// + /// Returns the path of the executable that started the currently executing process. Returns null when the path is not available. + /// + /// Path of the executable that started the currently executing process + /// + /// If the executable is renamed or deleted before this property is first accessed, the return value is undefined and depends on the operating system. + /// + public static string? ProcessPath + { + get { - get + // copied from Environment.ProcessPath + string? processPath = s_processPath; + if (processPath == null) { - // copied from Environment.ProcessPath - string? processPath = s_processPath; - if (processPath == null) - { - // The value is cached both as a performance optimization and to ensure that the API always returns - // the same path in a given process. - using Process currentProcess = Process.GetCurrentProcess(); - Interlocked.CompareExchange(ref s_processPath, currentProcess.MainModule.FileName ?? "", null); - processPath = s_processPath; - Debug.Assert(processPath != null); - } - - return (processPath?.Length != 0) ? processPath : null; + // The value is cached both as a performance optimization and to ensure that the API always returns + // the same path in a given process. + using Process currentProcess = Process.GetCurrentProcess(); + Interlocked.CompareExchange(ref s_processPath, currentProcess.MainModule.FileName ?? "", null); + processPath = s_processPath; + Debug.Assert(processPath != null); } + + return (processPath?.Length != 0) ? processPath : null; } } } From 970cef6ff5937a76b46c5cf5e5f86c6654e966c6 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 6 Feb 2026 12:44:35 -0800 Subject: [PATCH 117/136] MSBuildTaskHost: Clean up ErrorUtilities - File-scoped namespace - Remove uncalled members --- .../Utilities/ErrorUtilities.cs | 305 ++++++++---------- 1 file changed, 140 insertions(+), 165 deletions(-) diff --git a/src/MSBuildTaskHost/Utilities/ErrorUtilities.cs b/src/MSBuildTaskHost/Utilities/ErrorUtilities.cs index 563b9169678..a2252e561b3 100644 --- a/src/MSBuildTaskHost/Utilities/ErrorUtilities.cs +++ b/src/MSBuildTaskHost/Utilities/ErrorUtilities.cs @@ -7,195 +7,170 @@ using Microsoft.Build.TaskHost.Exceptions; using Microsoft.Build.TaskHost.Resources; -namespace Microsoft.Build.TaskHost.Utilities +namespace Microsoft.Build.TaskHost.Utilities; + +/// +/// This class contains methods that are useful for error checking and validation. +/// +internal static class ErrorUtilities { + [DoesNotReturn] + internal static void ThrowInternalError(string message) + => throw new InternalErrorException(message); + + [DoesNotReturn] + internal static void ThrowInternalError(string format, object? arg0) + => throw new InternalErrorException(string.Format(format, arg0)); + + /// + /// Throws InternalErrorException. + /// Indicates the code path followed should not have been possible. + /// This is only for situations that would mean that there is a bug in MSBuild itself. + /// + [DoesNotReturn] + internal static void ThrowInternalErrorUnreachable() + => throw new InternalErrorException("Unreachable?"); + /// - /// This class contains methods that are useful for error checking and validation. + /// Helper to throw an InternalErrorException when the specified parameter is null. + /// This should be used ONLY if this would indicate a bug in MSBuild rather than + /// anything caused by user action. /// - internal static class ErrorUtilities + /// The value of the argument. + /// Parameter that should not be null + internal static void VerifyThrowInternalNull( + [NotNull] object? parameter, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) { - [DoesNotReturn] - internal static void ThrowInternalError(string message) - => throw new InternalErrorException(message); - - [DoesNotReturn] - internal static void ThrowInternalError(string format, object? arg0) - => throw new InternalErrorException(string.Format(format, arg0)); - - [DoesNotReturn] - internal static void ThrowInternalError(string format, object? arg0, object? arg1) - => throw new InternalErrorException(string.Format(format, arg0, arg1)); - - [DoesNotReturn] - internal static void ThrowInternalError(string format, object? arg0, object? arg1, object? arg2) - => throw new InternalErrorException(string.Format(format, arg0, arg1, arg2)); - - /// - /// Throws InternalErrorException. - /// This is only for situations that would mean that there is a bug in MSBuild itself. - /// - [DoesNotReturn] - internal static void ThrowInternalError(string format, params object?[] args) - => throw new InternalErrorException(string.Format(format, args)); - - /// - /// Throws InternalErrorException. - /// This is only for situations that would mean that there is a bug in MSBuild itself. - /// - [DoesNotReturn] - internal static void ThrowInternalError(string message, Exception? innerException) - => throw new InternalErrorException(message, innerException); - - /// - /// Throws InternalErrorException. - /// Indicates the code path followed should not have been possible. - /// This is only for situations that would mean that there is a bug in MSBuild itself. - /// - [DoesNotReturn] - internal static void ThrowInternalErrorUnreachable() - => throw new InternalErrorException("Unreachable?"); - - /// - /// Helper to throw an InternalErrorException when the specified parameter is null. - /// This should be used ONLY if this would indicate a bug in MSBuild rather than - /// anything caused by user action. - /// - /// The value of the argument. - /// Parameter that should not be null - internal static void VerifyThrowInternalNull( - [NotNull] object? parameter, - [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) + if (parameter is null) { - if (parameter is null) - { - ThrowInternalError($"{parameterName} unexpectedly null"); - } + ThrowInternalError($"{parameterName} unexpectedly null"); } + } - /// - /// Helper to throw an InternalErrorException when the specified parameter is null or zero length. - /// This should be used ONLY if this would indicate a bug in MSBuild rather than - /// anything caused by user action. - /// - /// The value of the argument. - /// Parameter that should not be null or zero length - internal static void VerifyThrowInternalLength( - [NotNull] string? parameterValue, - [CallerArgumentExpression(nameof(parameterValue))] string? parameterName = null) - { - VerifyThrowInternalNull(parameterValue, parameterName); + /// + /// Helper to throw an InternalErrorException when the specified parameter is null or zero length. + /// This should be used ONLY if this would indicate a bug in MSBuild rather than + /// anything caused by user action. + /// + /// The value of the argument. + /// Parameter that should not be null or zero length + internal static void VerifyThrowInternalLength( + [NotNull] string? parameterValue, + [CallerArgumentExpression(nameof(parameterValue))] string? parameterName = null) + { + VerifyThrowInternalNull(parameterValue, parameterName); - if (parameterValue.Length == 0) - { - ThrowInternalError($"{parameterName} unexpectedly empty"); - } + if (parameterValue.Length == 0) + { + ThrowInternalError($"{parameterName} unexpectedly empty"); } + } - /// - /// This method should be used in places where one would normally put - /// an "assert". It should be used to validate that our assumptions are - /// true, where false would indicate that there must be a bug in our - /// code somewhere. This should not be used to throw errors based on bad - /// user input or anything that the user did wrong. - /// - internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string message) + /// + /// This method should be used in places where one would normally put + /// an "assert". It should be used to validate that our assumptions are + /// true, where false would indicate that there must be a bug in our + /// code somewhere. This should not be used to throw errors based on bad + /// user input or anything that the user did wrong. + /// + internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string message) + { + if (!condition) { - if (!condition) - { - ThrowInternalError(message); - } + ThrowInternalError(message); } + } - /// - /// Overload for one string format argument. - /// - internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string format, object? arg0) + /// + /// Overload for one string format argument. + /// + internal static void VerifyThrow([DoesNotReturnIf(false)] bool condition, string format, object? arg0) + { + if (!condition) { - if (!condition) - { - ThrowInternalError(format, arg0); - } + ThrowInternalError(format, arg0); } + } - /// - /// Throws an InvalidOperationException with the specified resource string - /// - /// Resource to use in the exception - /// Formatting args. - [DoesNotReturn] - internal static void ThrowInvalidOperation(string format, object? arg0, object? arg1, object? arg2) - => throw new InvalidOperationException(string.Format(format, arg0, arg1, arg2)); - - /// - /// Throws an ArgumentException that can include an inner exception. - /// - /// PERF WARNING: calling a method that takes a variable number of arguments - /// is expensive, because memory is allocated for the array of arguments -- do - /// not call this method repeatedly in performance-critical scenarios - /// - /// - /// This method is thread-safe. - /// - /// Can be null. - /// - /// - [DoesNotReturn] - private static void ThrowArgument(Exception? innerException, string format, object? arg0) - => throw new ArgumentException(string.Format(format, arg0), innerException); - - /// - /// Overload for one string format argument. - /// - internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, string format, object? arg0) - => VerifyThrowArgument(condition, innerException: null, format, arg0); - - /// - /// Overload for one string format argument. - /// - internal static void VerifyThrowArgument( - [DoesNotReturnIf(false)] bool condition, Exception? innerException, string format, object? arg0) + /// + /// Throws an InvalidOperationException with the specified resource string + /// + /// Resource to use in the exception + /// Formatting args. + [DoesNotReturn] + internal static void ThrowInvalidOperation(string format, object? arg0, object? arg1, object? arg2) + => throw new InvalidOperationException(string.Format(format, arg0, arg1, arg2)); + + /// + /// Throws an ArgumentException that can include an inner exception. + /// + /// PERF WARNING: calling a method that takes a variable number of arguments + /// is expensive, because memory is allocated for the array of arguments -- do + /// not call this method repeatedly in performance-critical scenarios + /// + /// + /// This method is thread-safe. + /// + /// Can be null. + /// + /// + [DoesNotReturn] + private static void ThrowArgument(Exception? innerException, string format, object? arg0) + => throw new ArgumentException(string.Format(format, arg0), innerException); + + /// + /// Overload for one string format argument. + /// + internal static void VerifyThrowArgument([DoesNotReturnIf(false)] bool condition, string format, object? arg0) + => VerifyThrowArgument(condition, innerException: null, format, arg0); + + /// + /// Overload for one string format argument. + /// + internal static void VerifyThrowArgument( + [DoesNotReturnIf(false)] bool condition, Exception? innerException, string format, object? arg0) + { + if (!condition) { - if (!condition) - { - ThrowArgument(innerException, format, arg0); - } + ThrowArgument(innerException, format, arg0); } + } - /// - /// Throws an ArgumentNullException if the given string parameter is null - /// and ArgumentException if it has zero length. - /// - internal static void VerifyThrowArgumentLength( - [NotNull] string? parameter, - [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) - { - VerifyThrowArgumentNull(parameter, parameterName); + /// + /// Throws an ArgumentNullException if the given string parameter is null + /// and ArgumentException if it has zero length. + /// + internal static void VerifyThrowArgumentLength( + [NotNull] string? parameter, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) + { + VerifyThrowArgumentNull(parameter, parameterName); - if (parameter.Length == 0) - { - ThrowArgumentLength(parameterName); - } + if (parameter.Length == 0) + { + ThrowArgumentLength(parameterName); } + } - [DoesNotReturn] - private static void ThrowArgumentLength(string? parameterName) - => throw new ArgumentException(string.Format(SR.Shared_ParameterCannotHaveZeroLength, parameterName), parameterName); + [DoesNotReturn] + private static void ThrowArgumentLength(string? parameterName) + => throw new ArgumentException(string.Format(SR.Shared_ParameterCannotHaveZeroLength, parameterName), parameterName); - /// - /// Throws an ArgumentNullException if the given parameter is null. - /// - internal static void VerifyThrowArgumentNull( - [NotNull] object? parameter, - [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) + /// + /// Throws an ArgumentNullException if the given parameter is null. + /// + internal static void VerifyThrowArgumentNull( + [NotNull] object? parameter, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null) + { + if (parameter is null) { - if (parameter is null) - { - ThrowArgumentNull(parameterName, SR.Shared_ParameterCannotBeNull); - } + ThrowArgumentNull(parameterName, SR.Shared_ParameterCannotBeNull); } - - [DoesNotReturn] - private static void ThrowArgumentNull(string? parameterName, string message) - => throw new ArgumentNullException(parameterName, string.Format(message, parameterName)); } + + [DoesNotReturn] + private static void ThrowArgumentNull(string? parameterName, string message) + => throw new ArgumentNullException(parameterName, string.Format(message, parameterName)); } From 61b2259ac14eb09ac3506d8a86db20f93ebbb0da Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 6 Feb 2026 12:48:17 -0800 Subject: [PATCH 118/136] MSBuildTaskHost: Clean up EscapingUtilities - File-scoped namespace - Expression-bodied members - Enable nullability - Remove unneeded members --- src/MSBuildTaskHost/BackEnd/TaskParameter.cs | 2 +- .../Utilities/EscapingUtilities.cs | 440 ++++++++---------- 2 files changed, 197 insertions(+), 245 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/TaskParameter.cs b/src/MSBuildTaskHost/BackEnd/TaskParameter.cs index 9975810aeca..081e6236907 100644 --- a/src/MSBuildTaskHost/BackEnd/TaskParameter.cs +++ b/src/MSBuildTaskHost/BackEnd/TaskParameter.cs @@ -718,7 +718,7 @@ public IDictionary CloneCustomMetadata() { foreach (KeyValuePair metadatum in _customEscapedMetadata) { - clonedMetadata.Add(metadatum.Key, EscapingUtilities.UnescapeAll(metadatum.Value)); + clonedMetadata.Add(metadatum.Key, EscapingUtilities.UnescapeAll(metadatum.Value!)); } } diff --git a/src/MSBuildTaskHost/Utilities/EscapingUtilities.cs b/src/MSBuildTaskHost/Utilities/EscapingUtilities.cs index 88df273001f..1e2a3941b82 100644 --- a/src/MSBuildTaskHost/Utilities/EscapingUtilities.cs +++ b/src/MSBuildTaskHost/Utilities/EscapingUtilities.cs @@ -6,305 +6,257 @@ using System.Text; using Microsoft.NET.StringTools; -#nullable disable - -namespace Microsoft.Build.TaskHost.Utilities +namespace Microsoft.Build.TaskHost.Utilities; + +/// +/// This class implements static methods to assist with unescaping of %XX codes +/// in the MSBuild file format. +/// +/// +/// PERF: since we escape and unescape relatively frequently, it may be worth caching +/// the last N strings that were (un)escaped +/// +internal static class EscapingUtilities { /// - /// This class implements static methods to assist with unescaping of %XX codes - /// in the MSBuild file format. + /// Special characters that need escaping. + /// It's VERY important that the percent character is the FIRST on the list - since it's both a character + /// we escape and use in escape sequences, we can unintentionally escape other escape sequences if we + /// don't process it first. Of course we'll have a similar problem if we ever decide to escape hex digits + /// (that would require rewriting the algorithm) but since it seems unlikely that we ever do, this should + /// be good enough to avoid complicating the algorithm at this point. /// - /// - /// PERF: since we escape and unescape relatively frequently, it may be worth caching - /// the last N strings that were (un)escaped - /// - internal static class EscapingUtilities - { - /// - /// Optional cache of escaped strings for use when needing to escape in performance-critical scenarios with significant - /// expected string reuse. - /// - private static readonly Dictionary s_unescapedToEscapedStrings = new Dictionary(StringComparer.Ordinal); + private static readonly char[] s_charsToEscape = ['%', '*', '?', '@', '$', '(', ')', ';', '\'']; - private static bool TryDecodeHexDigit(char character, out int value) + /// + /// Optional cache of escaped strings for use when needing to escape in performance-critical scenarios with significant + /// expected string reuse. + /// + private static readonly Dictionary s_unescapedToEscapedStrings = new(StringComparer.Ordinal); + + private static bool TryDecodeHexDigit(char character, out int value) + { + switch (character) { - if (character >= '0' && character <= '9') - { + case >= '0' and <= '9': value = character - '0'; return true; - } - if (character >= 'A' && character <= 'F') - { + case >= 'A' and <= 'F': value = character - 'A' + 10; return true; - } - if (character >= 'a' && character <= 'f') - { + case >= 'a' and <= 'f': value = character - 'a' + 10; return true; - } - value = default; - return false; } - /// - /// Replaces all instances of %XX in the input string with the character represented - /// by the hexadecimal number XX. - /// - /// The string to unescape. - /// If the string should be trimmed before being unescaped. - /// unescaped string - internal static string UnescapeAll(string escapedString, bool trim = false) + value = default; + return false; + } + + /// + /// Replaces all instances of %XX in the input string with the character represented + /// by the hexadecimal number XX. + /// + /// The string to unescape. + /// If the string should be trimmed before being unescaped. + /// unescaped string + internal static string UnescapeAll(string escapedString, bool trim = false) + { + // If the string doesn't contain anything, then by definition it doesn't + // need unescaping. + if (string.IsNullOrEmpty(escapedString)) { - // If the string doesn't contain anything, then by definition it doesn't - // need unescaping. - if (String.IsNullOrEmpty(escapedString)) - { - return escapedString; - } + return escapedString; + } - // If there are no percent signs, just return the original string immediately. - // Don't even instantiate the StringBuilder. - int indexOfPercent = escapedString.IndexOf('%'); - if (indexOfPercent == -1) - { - return trim ? escapedString.Trim() : escapedString; - } + // If there are no percent signs, just return the original string immediately. + // Don't even instantiate the StringBuilder. + int indexOfPercent = escapedString.IndexOf('%'); + if (indexOfPercent == -1) + { + return trim ? escapedString.Trim() : escapedString; + } - // This is where we're going to build up the final string to return to the caller. - StringBuilder unescapedString = StringBuilderCache.Acquire(escapedString.Length); + // This is where we're going to build up the final string to return to the caller. + StringBuilder unescapedString = StringBuilderCache.Acquire(escapedString.Length); - int currentPosition = 0; - int escapedStringLength = escapedString.Length; - if (trim) + int currentPosition = 0; + int escapedStringLength = escapedString.Length; + if (trim) + { + while (currentPosition < escapedString.Length && char.IsWhiteSpace(escapedString[currentPosition])) { - while (currentPosition < escapedString.Length && Char.IsWhiteSpace(escapedString[currentPosition])) - { - currentPosition++; - } - if (currentPosition == escapedString.Length) - { - return String.Empty; - } - while (Char.IsWhiteSpace(escapedString[escapedStringLength - 1])) - { - escapedStringLength--; - } + currentPosition++; } - // Loop until there are no more percent signs in the input string. - while (indexOfPercent != -1) + if (currentPosition == escapedString.Length) { - // There must be two hex characters following the percent sign - // for us to even consider doing anything with this. - if ( - (indexOfPercent <= (escapedStringLength - 3)) && - TryDecodeHexDigit(escapedString[indexOfPercent + 1], out int digit1) && - TryDecodeHexDigit(escapedString[indexOfPercent + 2], out int digit2)) - { - // First copy all the characters up to the current percent sign into - // the destination. - unescapedString.Append(escapedString, currentPosition, indexOfPercent - currentPosition); - - // Convert the %XX to an actual real character. - char unescapedCharacter = (char)((digit1 << 4) + digit2); - - // if the unescaped character is not on the exception list, append it - unescapedString.Append(unescapedCharacter); - - // Advance the current pointer to reflect the fact that the destination string - // is up to date with everything up to and including this escape code we just found. - currentPosition = indexOfPercent + 3; - } - - // Find the next percent sign. - indexOfPercent = escapedString.IndexOf('%', indexOfPercent + 1); + return string.Empty; } - // Okay, there are no more percent signs in the input string, so just copy the remaining - // characters into the destination. - unescapedString.Append(escapedString, currentPosition, escapedStringLength - currentPosition); - - return StringBuilderCache.GetStringAndRelease(unescapedString); + while (char.IsWhiteSpace(escapedString[escapedStringLength - 1])) + { + escapedStringLength--; + } } - - /// - /// Adds instances of %XX in the input string where the char to be escaped appears - /// XX is the hex value of the ASCII code for the char. Interns and caches the result. - /// - /// - /// NOTE: Only recommended for use in scenarios where there's expected to be significant - /// repetition of the escaped string. Cache currently grows unbounded. - /// - internal static string EscapeWithCaching(string unescapedString) + // Loop until there are no more percent signs in the input string. + while (indexOfPercent != -1) { - return EscapeWithOptionalCaching(unescapedString, cache: true); - } + // There must be two hex characters following the percent sign + // for us to even consider doing anything with this. + if ((indexOfPercent <= (escapedStringLength - 3)) && + TryDecodeHexDigit(escapedString[indexOfPercent + 1], out int digit1) && + TryDecodeHexDigit(escapedString[indexOfPercent + 2], out int digit2)) + { + // First copy all the characters up to the current percent sign into + // the destination. + unescapedString.Append(escapedString, currentPosition, indexOfPercent - currentPosition); - /// - /// Adds instances of %XX in the input string where the char to be escaped appears - /// XX is the hex value of the ASCII code for the char. - /// - /// The string to escape. - /// escaped string - internal static string Escape(string unescapedString) - { - return EscapeWithOptionalCaching(unescapedString, cache: false); - } + // Convert the %XX to an actual real character. + char unescapedCharacter = (char)((digit1 << 4) + digit2); - /// - /// Adds instances of %XX in the input string where the char to be escaped appears - /// XX is the hex value of the ASCII code for the char. Caches if requested. - /// - /// The string to escape. - /// - /// True if the cache should be checked, and if the resultant string - /// should be cached. - /// - private static string EscapeWithOptionalCaching(string unescapedString, bool cache) - { - // If there are no special chars, just return the original string immediately. - // Don't even instantiate the StringBuilder. - if (String.IsNullOrEmpty(unescapedString) || !ContainsReservedCharacters(unescapedString)) - { - return unescapedString; - } + // if the unescaped character is not on the exception list, append it + unescapedString.Append(unescapedCharacter); - // next, if we're caching, check to see if it's already there. - if (cache) - { - lock (s_unescapedToEscapedStrings) - { - string cachedEscapedString; - if (s_unescapedToEscapedStrings.TryGetValue(unescapedString, out cachedEscapedString)) - { - return cachedEscapedString; - } - } + // Advance the current pointer to reflect the fact that the destination string + // is up to date with everything up to and including this escape code we just found. + currentPosition = indexOfPercent + 3; } - // This is where we're going to build up the final string to return to the caller. - StringBuilder escapedStringBuilder = StringBuilderCache.Acquire(unescapedString.Length * 2); - - AppendEscapedString(escapedStringBuilder, unescapedString); + // Find the next percent sign. + indexOfPercent = escapedString.IndexOf('%', indexOfPercent + 1); + } - if (!cache) - { - return StringBuilderCache.GetStringAndRelease(escapedStringBuilder); - } + // Okay, there are no more percent signs in the input string, so just copy the remaining + // characters into the destination. + unescapedString.Append(escapedString, currentPosition, escapedStringLength - currentPosition); - string escapedString = Strings.WeakIntern(escapedStringBuilder.ToString()); - StringBuilderCache.Release(escapedStringBuilder); + return StringBuilderCache.GetStringAndRelease(unescapedString); + } - lock (s_unescapedToEscapedStrings) - { - s_unescapedToEscapedStrings[unescapedString] = escapedString; - } + /// + /// Adds instances of %XX in the input string where the char to be escaped appears + /// XX is the hex value of the ASCII code for the char. Interns and caches the result. + /// + /// + /// NOTE: Only recommended for use in scenarios where there's expected to be significant + /// repetition of the escaped string. Cache currently grows unbounded. + /// + internal static string EscapeWithCaching(string unescapedString) + => EscapeWithOptionalCaching(unescapedString, cache: true); - return escapedString; - } + /// + /// Adds instances of %XX in the input string where the char to be escaped appears + /// XX is the hex value of the ASCII code for the char. + /// + /// The string to escape. + /// escaped string + internal static string Escape(string unescapedString) + => EscapeWithOptionalCaching(unescapedString, cache: false); - /// - /// Before trying to actually escape the string, it can be useful to call this method to determine - /// if escaping is necessary at all. This can save lots of calls to copy around item metadata - /// that is really the same whether escaped or not. - /// - /// - /// - private static bool ContainsReservedCharacters( - string unescapedString) + /// + /// Adds instances of %XX in the input string where the char to be escaped appears + /// XX is the hex value of the ASCII code for the char. Caches if requested. + /// + /// The string to escape. + /// + /// True if the cache should be checked, and if the resultant string + /// should be cached. + /// + private static string EscapeWithOptionalCaching(string unescapedString, bool cache) + { + // If there are no special chars, just return the original string immediately. + // Don't even instantiate the StringBuilder. + if (string.IsNullOrEmpty(unescapedString) || !ContainsReservedCharacters(unescapedString)) { - return -1 != unescapedString.IndexOfAny(s_charsToEscape); + return unescapedString; } - /// - /// Determines whether the string contains the escaped form of '*' or '?'. - /// - /// - /// - internal static bool ContainsEscapedWildcards(string escapedString) + // next, if we're caching, check to see if it's already there. + if (cache) { - if (escapedString.Length < 3) - { - return false; - } - // Look for the first %. We know that it has to be followed by at least two more characters so we subtract 2 - // from the length to search. - int index = escapedString.IndexOf('%', 0, escapedString.Length - 2); - while (index != -1) + lock (s_unescapedToEscapedStrings) { - if (escapedString[index + 1] == '2' && (escapedString[index + 2] == 'a' || escapedString[index + 2] == 'A')) - { - // %2a or %2A - return true; - } - if (escapedString[index + 1] == '3' && (escapedString[index + 2] == 'f' || escapedString[index + 2] == 'F')) + if (s_unescapedToEscapedStrings.TryGetValue(unescapedString, out string? cachedEscapedString)) { - // %3f or %3F - return true; + return cachedEscapedString; } - // Continue searching for % starting at (index + 1). We know that it has to be followed by at least two - // more characters so we subtract 2 from the length of the substring to search. - index = escapedString.IndexOf('%', index + 1, escapedString.Length - (index + 1) - 2); } - return false; } - /// - /// Convert the given integer into its hexadecimal representation. - /// - /// The number to convert, which must be non-negative and less than 16 - /// The character which is the hexadecimal representation of . - private static char HexDigitChar(int x) + // This is where we're going to build up the final string to return to the caller. + StringBuilder escapedStringBuilder = StringBuilderCache.Acquire(unescapedString.Length * 2); + + AppendEscapedString(escapedStringBuilder, unescapedString); + + if (!cache) { - return (char)(x + (x < 10 ? '0' : ('a' - 10))); + return StringBuilderCache.GetStringAndRelease(escapedStringBuilder); } - /// - /// Append the escaped version of the given character to a . - /// - /// The to which to append. - /// The character to escape. - private static void AppendEscapedChar(StringBuilder sb, char ch) + string escapedString = Strings.WeakIntern(escapedStringBuilder.ToString()); + StringBuilderCache.Release(escapedStringBuilder); + + lock (s_unescapedToEscapedStrings) { - // Append the escaped version which is a percent sign followed by two hexadecimal digits - sb.Append('%'); - sb.Append(HexDigitChar(ch / 0x10)); - sb.Append(HexDigitChar(ch & 0x0F)); + s_unescapedToEscapedStrings[unescapedString] = escapedString; } - /// - /// Append the escaped version of the given string to a . - /// - /// The to which to append. - /// The unescaped string. - private static void AppendEscapedString(StringBuilder sb, string unescapedString) + return escapedString; + } + + /// + /// Before trying to actually escape the string, it can be useful to call this method to determine + /// if escaping is necessary at all. This can save lots of calls to copy around item metadata + /// that is really the same whether escaped or not. + /// + /// + /// + private static bool ContainsReservedCharacters(string unescapedString) + => unescapedString.IndexOfAny(s_charsToEscape) >= 0; + + /// + /// Convert the given integer into its hexadecimal representation. + /// + /// The number to convert, which must be non-negative and less than 16 + /// The character which is the hexadecimal representation of . + private static char HexDigitChar(int x) + => (char)(x + (x < 10 ? '0' : ('a' - 10))); + + /// + /// Append the escaped version of the given character to a . + /// + /// The to which to append. + /// The character to escape. + private static void AppendEscapedChar(StringBuilder sb, char ch) + { + // Append the escaped version which is a percent sign followed by two hexadecimal digits + sb.Append('%'); + sb.Append(HexDigitChar(ch / 0x10)); + sb.Append(HexDigitChar(ch & 0x0F)); + } + + /// + /// Append the escaped version of the given string to a . + /// + /// The to which to append. + /// The unescaped string. + private static void AppendEscapedString(StringBuilder sb, string unescapedString) + { + // Replace each unescaped special character with an escape sequence one + for (int idx = 0; ;) { - // Replace each unescaped special character with an escape sequence one - for (int idx = 0; ;) + int nextIdx = unescapedString.IndexOfAny(s_charsToEscape, idx); + if (nextIdx == -1) { - int nextIdx = unescapedString.IndexOfAny(s_charsToEscape, idx); - if (nextIdx == -1) - { - sb.Append(unescapedString, idx, unescapedString.Length - idx); - break; - } - - sb.Append(unescapedString, idx, nextIdx - idx); - AppendEscapedChar(sb, unescapedString[nextIdx]); - idx = nextIdx + 1; + sb.Append(unescapedString, idx, unescapedString.Length - idx); + break; } - } - /// - /// Special characters that need escaping. - /// It's VERY important that the percent character is the FIRST on the list - since it's both a character - /// we escape and use in escape sequences, we can unintentionally escape other escape sequences if we - /// don't process it first. Of course we'll have a similar problem if we ever decide to escape hex digits - /// (that would require rewriting the algorithm) but since it seems unlikely that we ever do, this should - /// be good enough to avoid complicating the algorithm at this point. - /// - private static readonly char[] s_charsToEscape = { '%', '*', '?', '@', '$', '(', ')', ';', '\'' }; + sb.Append(unescapedString, idx, nextIdx - idx); + AppendEscapedChar(sb, unescapedString[nextIdx]); + idx = nextIdx + 1; + } } } From 4b8def8b46dabd8f4905f3e1b9fc91517bf87cbb Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 6 Feb 2026 12:54:00 -0800 Subject: [PATCH 119/136] MSBuildTaskHost: Clean up ExceptionHandling - File-scoped namespace - Expression-bodied members - Enable nullability - Use pattern matching --- .../Utilities/ExceptionHandling.cs | 209 ++++++++---------- 1 file changed, 95 insertions(+), 114 deletions(-) diff --git a/src/MSBuildTaskHost/Utilities/ExceptionHandling.cs b/src/MSBuildTaskHost/Utilities/ExceptionHandling.cs index 2028e9a8a08..7ea54c7ffe3 100644 --- a/src/MSBuildTaskHost/Utilities/ExceptionHandling.cs +++ b/src/MSBuildTaskHost/Utilities/ExceptionHandling.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable - using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -11,143 +9,126 @@ using System.Threading; using Microsoft.Build.TaskHost.Exceptions; -namespace Microsoft.Build.TaskHost.Utilities +namespace Microsoft.Build.TaskHost.Utilities; + +/// +/// Utility methods for classifying and handling exceptions. +/// +internal static class ExceptionHandling { /// - /// Utility methods for classifying and handling exceptions. + /// The directory used for diagnostic log files. /// - internal static class ExceptionHandling - { - /// - /// The directory used for diagnostic log files. - /// - public static string DebugDumpPath { get; } = GetDebugDumpPath(); - - /// - /// Gets the location of the directory used for diagnostic log files. - /// - /// - private static string GetDebugDumpPath() - { - string debugPath = Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); + public static string DebugDumpPath { get; } = GetDebugDumpPath(); - return !string.IsNullOrEmpty(debugPath) - ? debugPath - : FileUtilities.TempFileDirectory; - } - - /// - /// The filename that exceptions will be dumped to - /// - private static string s_dumpFileName; + /// + /// Gets the location of the directory used for diagnostic log files. + /// + /// + private static string GetDebugDumpPath() + { + string debugPath = Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); - /// - /// If the given exception is "ignorable under some circumstances" return false. - /// Otherwise it's "really bad", and return true. - /// This makes it possible to catch(Exception ex) without catching disasters. - /// - /// The exception to check. - /// True if exception is critical. - internal static bool IsCriticalException(Exception e) - { - if (e is OutOfMemoryException - || e is StackOverflowException - || e is ThreadAbortException - || e is ThreadInterruptedException - || e is AccessViolationException - || e is InternalErrorException) - { - // Ideally we would include NullReferenceException, because it should only ever be thrown by CLR (use ArgumentNullException for arguments) - // but we should handle it if tasks and loggers throw it. + return !string.IsNullOrEmpty(debugPath) + ? debugPath + : FileUtilities.TempFileDirectory; + } - // ExecutionEngineException has been deprecated by the CLR - return true; - } + /// + /// The filename that exceptions will be dumped to. + /// + private static string? s_dumpFileName; - return false; - } + /// + /// If the given exception is "ignorable under some circumstances" return false. + /// Otherwise it's "really bad", and return true. + /// This makes it possible to catch(Exception ex) without catching disasters. + /// + /// The exception to check. + /// True if exception is critical. + internal static bool IsCriticalException(Exception e) + => e is OutOfMemoryException + or StackOverflowException + or ThreadAbortException + or ThreadInterruptedException + or AccessViolationException + or InternalErrorException; - /// - /// Determine whether the exception is file-IO related. - /// - /// The exception to check. - /// True if exception is IO related. - internal static bool IsIoRelatedException(Exception e) - { - // These all derive from IOException - // DirectoryNotFoundException - // DriveNotFoundException - // EndOfStreamException - // FileLoadException - // FileNotFoundException - // PathTooLongException - // PipeException - return e is UnauthorizedAccessException - || e is NotSupportedException - || (e is ArgumentException && !(e is ArgumentNullException)) - || e is SecurityException - || e is IOException; - } + /// + /// Determine whether the exception is file-IO related. + /// + /// The exception to check. + /// True if exception is IO related. + internal static bool IsIoRelatedException(Exception e) + // These all derive from IOException + // DirectoryNotFoundException + // DriveNotFoundException + // EndOfStreamException + // FileLoadException + // FileNotFoundException + // PathTooLongException + // PipeException + => e is UnauthorizedAccessException + or NotSupportedException + or (ArgumentException and not ArgumentNullException) + or SecurityException + or IOException; - /// - /// Dump any unhandled exceptions to a file so they can be diagnosed - /// - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "It is called by the CLR")] - internal static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e) - { - Exception ex = (Exception)e.ExceptionObject; - DumpExceptionToFile(ex); - } + /// + /// Dump any unhandled exceptions to a file so they can be diagnosed. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "It is called by the CLR")] + internal static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e) + => DumpExceptionToFile((Exception)e.ExceptionObject); - /// - /// Dump the exception information to a file - /// - internal static void DumpExceptionToFile(Exception ex) + /// + /// Dump the exception information to a file. + /// + internal static void DumpExceptionToFile(Exception exception) + { + try { - try + // Locking on a type is not recommended. However, we are doing it here to be extra cautious about compatibility because + // this method previously had a [MethodImpl(MethodImplOptions.Synchronized)] attribute, which does lock on the type when + // applied to a static method. + lock (typeof(ExceptionHandling)) { - // Locking on a type is not recommended. However, we are doing it here to be extra cautious about compatibility because - // this method previously had a [MethodImpl(MethodImplOptions.Synchronized)] attribute, which does lock on the type when - // applied to a static method. - lock (typeof(ExceptionHandling)) + if (s_dumpFileName == null) { - if (s_dumpFileName == null) - { - Guid guid = Guid.NewGuid(); + Guid guid = Guid.NewGuid(); - // For some reason we get Watson buckets because GetTempPath gives us a folder here that doesn't exist. - // Either because %TMP% is misdefined, or because they deleted the temp folder during the build. - // If this throws, no sense catching it, we can't log it now, and we're here - // because we're a child node with no console to log to, so die - Directory.CreateDirectory(DebugDumpPath); + // For some reason we get Watson buckets because GetTempPath gives us a folder here that doesn't exist. + // Either because %TMP% is misdefined, or because they deleted the temp folder during the build. + // If this throws, no sense catching it, we can't log it now, and we're here + // because we're a child node with no console to log to, so die + Directory.CreateDirectory(DebugDumpPath); - var pid = EnvironmentUtilities.CurrentProcessId; - // This naming pattern is assumed in ReadAnyExceptionFromFile - s_dumpFileName = Path.Combine(DebugDumpPath, $"MSBuild_pid-{pid}_{guid:n}.failure.txt"); + var pid = EnvironmentUtilities.CurrentProcessId; - using (StreamWriter writer = FileUtilities.OpenWrite(s_dumpFileName, append: true)) - { - writer.WriteLine("UNHANDLED EXCEPTIONS FROM PROCESS {0}:", pid); - writer.WriteLine("====================="); - } - } + // This naming pattern is assumed in ReadAnyExceptionFromFile + s_dumpFileName = Path.Combine(DebugDumpPath, $"MSBuild_pid-{pid}_{guid:n}.failure.txt"); using (StreamWriter writer = FileUtilities.OpenWrite(s_dumpFileName, append: true)) { - // "G" format is, e.g., 6/15/2008 9:15:07 PM - writer.WriteLine(DateTime.Now.ToString("G", CultureInfo.CurrentCulture)); - writer.WriteLine(ex.ToString()); - writer.WriteLine("==================="); + writer.WriteLine("UNHANDLED EXCEPTIONS FROM PROCESS {0}:", pid); + writer.WriteLine("====================="); } } - } + using (StreamWriter writer = FileUtilities.OpenWrite(s_dumpFileName, append: true)) + { + // "G" format is, e.g., 6/15/2008 9:15:07 PM + writer.WriteLine(DateTime.Now.ToString("G", CultureInfo.CurrentCulture)); + writer.WriteLine(exception.ToString()); + writer.WriteLine("==================="); + } + } + } + catch + { // Some customers experience exceptions such as 'OutOfMemory' errors when msbuild attempts to log errors to a local file. // This catch helps to prevent the application from crashing in this best-effort dump-diagnostics path, // but doesn't prevent the overall crash from going to Watson. - catch - { - } } } } From 15970ea633c7e3cfd052efa8e65115c26e9f8359 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 6 Feb 2026 13:05:34 -0800 Subject: [PATCH 120/136] MSBuildTaskHost: Clean up FileUtilities - File-scoped namespace - Expression-bodied members - Enable nullability - Remove FixFilePath. This doesn't really do anything in the MSBuildTaskHost, since it always runs on Windows. --- .../CommunicationsUtilities.cs | 2 +- .../Utilities/ExceptionHandling.cs | 4 +- .../Utilities/FileUtilities.cs | 447 +++++++++--------- src/MSBuildTaskHost/Utilities/Modifiers.cs | 2 +- 4 files changed, 215 insertions(+), 240 deletions(-) diff --git a/src/MSBuildTaskHost/CommunicationsUtilities.cs b/src/MSBuildTaskHost/CommunicationsUtilities.cs index b8dd6e4350c..ebf0610f425 100644 --- a/src/MSBuildTaskHost/CommunicationsUtilities.cs +++ b/src/MSBuildTaskHost/CommunicationsUtilities.cs @@ -771,7 +771,7 @@ private static void TraceCore(int nodeId, string message) string filePath = Path.Combine(s_debugDumpPath, fileName); - using (StreamWriter file = FileUtilities.OpenWrite(filePath, append: true)) + using (StreamWriter file = FileUtilities.CreateWriterForAppend(filePath)) { long now = DateTime.UtcNow.Ticks; float millisecondsSinceLastLog = (float)(now - s_lastLoggedTicks) / 10000L; diff --git a/src/MSBuildTaskHost/Utilities/ExceptionHandling.cs b/src/MSBuildTaskHost/Utilities/ExceptionHandling.cs index 7ea54c7ffe3..bd5a5e976c3 100644 --- a/src/MSBuildTaskHost/Utilities/ExceptionHandling.cs +++ b/src/MSBuildTaskHost/Utilities/ExceptionHandling.cs @@ -108,14 +108,14 @@ internal static void DumpExceptionToFile(Exception exception) // This naming pattern is assumed in ReadAnyExceptionFromFile s_dumpFileName = Path.Combine(DebugDumpPath, $"MSBuild_pid-{pid}_{guid:n}.failure.txt"); - using (StreamWriter writer = FileUtilities.OpenWrite(s_dumpFileName, append: true)) + using (StreamWriter writer = FileUtilities.CreateWriterForAppend(s_dumpFileName)) { writer.WriteLine("UNHANDLED EXCEPTIONS FROM PROCESS {0}:", pid); writer.WriteLine("====================="); } } - using (StreamWriter writer = FileUtilities.OpenWrite(s_dumpFileName, append: true)) + using (StreamWriter writer = FileUtilities.CreateWriterForAppend(s_dumpFileName)) { // "G" format is, e.g., 6/15/2008 9:15:07 PM writer.WriteLine(DateTime.Now.ToString("G", CultureInfo.CurrentCulture)); diff --git a/src/MSBuildTaskHost/Utilities/FileUtilities.cs b/src/MSBuildTaskHost/Utilities/FileUtilities.cs index fb6ecf083dc..3e633e01b32 100644 --- a/src/MSBuildTaskHost/Utilities/FileUtilities.cs +++ b/src/MSBuildTaskHost/Utilities/FileUtilities.cs @@ -3,297 +3,272 @@ using System; using System.IO; -using System.Text; using Microsoft.Build.TaskHost.Resources; -#nullable disable +namespace Microsoft.Build.TaskHost.Utilities; -namespace Microsoft.Build.TaskHost.Utilities +/// +/// This class contains utility methods for file IO. +/// PERF\COVERAGE NOTE: Try to keep classes in 'shared' as granular as possible. All the methods in +/// each class get pulled into the resulting assembly. +/// +internal static partial class FileUtilities { + internal static string TempFileDirectory => Path.GetTempPath(); + + /// + /// Indicates if the given character is a slash. + /// + /// + /// true, if slash + private static bool IsSlash(char c) + => c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar; + + /// + /// Indicates if the given file-spec ends with a slash. + /// + /// The file spec. + /// true, if file-spec has trailing slash + private static bool EndsWithSlash(string fileSpec) + => fileSpec.Length > 0 && IsSlash(fileSpec[fileSpec.Length - 1]); + /// - /// This class contains utility methods for file IO. - /// PERF\COVERAGE NOTE: Try to keep classes in 'shared' as granular as possible. All the methods in - /// each class get pulled into the resulting assembly. + /// Gets the canonicalized full path of the provided path. + /// Guidance for use: call this on all paths accepted through public entry + /// points that need normalization. After that point, only verify the path + /// is rooted, using ErrorUtilities.VerifyThrowPathRooted. + /// ASSUMES INPUT IS ALREADY UNESCAPED. /// - internal static partial class FileUtilities + private static string NormalizePath(string path) { - internal static string TempFileDirectory => Path.GetTempPath(); - - /// - /// Indicates if the given character is a slash. - /// - /// - /// true, if slash - private static bool IsSlash(char c) - { - return (c == Path.DirectorySeparatorChar) || (c == Path.AltDirectorySeparatorChar); - } + ErrorUtilities.VerifyThrowArgumentLength(path); + return GetFullPath(path); + } - /// - /// Indicates if the given file-spec ends with a slash. - /// - /// The file spec. - /// true, if file-spec has trailing slash - private static bool EndsWithSlash(string fileSpec) - { - return (fileSpec.Length > 0) - ? IsSlash(fileSpec[fileSpec.Length - 1]) - : false; - } + private static string GetFullPath(string path) + { + string uncheckedFullPath = NativeMethods.GetFullPath(path); - private static string FixFilePath(string path) + if (IsPathTooLong(uncheckedFullPath)) { - return string.IsNullOrEmpty(path) || Path.DirectorySeparatorChar == '\\' ? path : path.Replace('\\', '/'); + string message = string.Format(SR.Shared_PathTooLong, path, NativeMethods.MaxPath); + throw new PathTooLongException(message); } - /// - /// Gets the canonicalized full path of the provided path. - /// Guidance for use: call this on all paths accepted through public entry - /// points that need normalization. After that point, only verify the path - /// is rooted, using ErrorUtilities.VerifyThrowPathRooted. - /// ASSUMES INPUT IS ALREADY UNESCAPED. - /// - private static string NormalizePath(string path) + // We really don't care about extensions here, but Path.HasExtension provides a great way to + // invoke the CLR's invalid path checks (these are independent of path length) + _ = Path.HasExtension(uncheckedFullPath); + + // If we detect we are a UNC path then we need to use the regular get full path in order to do the correct checks for UNC formatting + // and security checks for strings like \\?\GlobalRoot + return IsUNCPath(uncheckedFullPath) ? Path.GetFullPath(uncheckedFullPath) : uncheckedFullPath; + } + + private static bool IsUNCPath(string path) + { + if (!path.StartsWith(@"\\", StringComparison.Ordinal)) { - ErrorUtilities.VerifyThrowArgumentLength(path); - string fullPath = GetFullPath(path); - return FixFilePath(fullPath); + return false; } - private static string GetFullPath(string path) + bool isUNC = true; + for (int i = 2; i < path.Length - 1; i++) { - string uncheckedFullPath = NativeMethods.GetFullPath(path); - - if (IsPathTooLong(uncheckedFullPath)) + if (path[i] == '\\') { - string message = string.Format(SR.Shared_PathTooLong, path, NativeMethods.MaxPath); - throw new PathTooLongException(message); + isUNC = false; + break; } - - // We really don't care about extensions here, but Path.HasExtension provides a great way to - // invoke the CLR's invalid path checks (these are independent of path length) - _ = Path.HasExtension(uncheckedFullPath); - - // If we detect we are a UNC path then we need to use the regular get full path in order to do the correct checks for UNC formatting - // and security checks for strings like \\?\GlobalRoot - return IsUNCPath(uncheckedFullPath) ? Path.GetFullPath(uncheckedFullPath) : uncheckedFullPath; } - private static bool IsUNCPath(string path) - { - if (!path.StartsWith(@"\\", StringComparison.Ordinal)) - { - return false; - } + /* + From Path.cs in the CLR - bool isUNC = true; - for (int i = 2; i < path.Length - 1; i++) - { - if (path[i] == '\\') - { - isUNC = false; - break; - } - } + Throw an ArgumentException for paths like \\, \\server, \\server\ + This check can only be properly done after normalizing, so + \\foo\.. will be properly rejected. Also, reject \\?\GLOBALROOT\ + (an internal kernel path) because it provides aliases for drives. - /* - From Path.cs in the CLR + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC")); - Throw an ArgumentException for paths like \\, \\server, \\server\ - This check can only be properly done after normalizing, so - \\foo\.. will be properly rejected. Also, reject \\?\GLOBALROOT\ - (an internal kernel path) because it provides aliases for drives. + // Check for \\?\Globalroot, an internal mechanism to the kernel + // that provides aliases for drives and other undocumented stuff. + // The kernel team won't even describe the full set of what + // is available here - we don't want managed apps mucking + // with this for security reasons. + */ + return isUNC || path.IndexOf(@"\\?\globalroot", StringComparison.OrdinalIgnoreCase) != -1; + } - throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC")); + /// + /// Extracts the directory from the given file-spec. + /// + /// The filespec. + /// directory path + private static string GetDirectory(string fileSpec) + { + string directory = Path.GetDirectoryName(fileSpec); - // Check for \\?\Globalroot, an internal mechanism to the kernel - // that provides aliases for drives and other undocumented stuff. - // The kernel team won't even describe the full set of what - // is available here - we don't want managed apps mucking - // with this for security reasons. - */ - return isUNC || path.IndexOf(@"\\?\globalroot", StringComparison.OrdinalIgnoreCase) != -1; + // if file-spec is a root directory e.g. c:, c:\, \, \\server\share + // NOTE: Path.GetDirectoryName also treats invalid UNC file-specs as root directories e.g. \\, \\server + if (directory == null) + { + // just use the file-spec as-is + directory = fileSpec; } - - /// - /// Extracts the directory from the given file-spec. - /// - /// The filespec. - /// directory path - private static string GetDirectory(string fileSpec) + else if ((directory.Length > 0) && !EndsWithSlash(directory)) { - string directory = Path.GetDirectoryName(FixFilePath(fileSpec)); - - // if file-spec is a root directory e.g. c:, c:\, \, \\server\share - // NOTE: Path.GetDirectoryName also treats invalid UNC file-specs as root directories e.g. \\, \\server - if (directory == null) - { - // just use the file-spec as-is - directory = fileSpec; - } - else if ((directory.Length > 0) && !EndsWithSlash(directory)) - { - // restore trailing slash if Path.GetDirectoryName has removed it (this happens with non-root directories) - directory += Path.DirectorySeparatorChar; - } - - return directory; + // restore trailing slash if Path.GetDirectoryName has removed it (this happens with non-root directories) + directory += Path.DirectorySeparatorChar; } - // ISO 8601 Universal time with sortable format - private const string FileTimeFormat = "yyyy'-'MM'-'dd HH':'mm':'ss'.'fffffff"; - - public static string MSBuildTaskHostDirectory - => field ??= Path.GetDirectoryName(Path.GetFullPath(typeof(FileUtilities).Assembly.Location)); - - /// - /// Determines the full path for the given file-spec. - /// ASSUMES INPUT IS STILL ESCAPED - /// - /// The file spec to get the full path of. - /// - /// Whether to escape the path after getting the full path. - /// Full path to the file, escaped if not specified otherwise. - private static string GetFullPath(string fileSpec, string currentDirectory, bool escape = true) - { - // Sending data out of the engine into the filesystem, so time to unescape. - fileSpec = FixFilePath(EscapingUtilities.UnescapeAll(fileSpec)); + return directory; + } - string fullPath = NormalizePath(Path.Combine(currentDirectory, fileSpec)); - // In some cases we might want to NOT escape in order to preserve symbols like @, %, $ etc. - if (escape) - { - // Data coming back from the filesystem into the engine, so time to escape it back. - fullPath = EscapingUtilities.Escape(fullPath); - } + // ISO 8601 Universal time with sortable format + private const string FileTimeFormat = "yyyy'-'MM'-'dd HH':'mm':'ss'.'fffffff"; - if (!EndsWithSlash(fullPath)) - { - if (FileUtilitiesRegex.IsDrivePattern(fileSpec) || - FileUtilitiesRegex.IsUncPattern(fullPath)) - { - // append trailing slash if Path.GetFullPath failed to (this happens with drive-specs and UNC shares) - fullPath += Path.DirectorySeparatorChar; - } - } + public static string MSBuildTaskHostDirectory + => field ??= Path.GetDirectoryName(Path.GetFullPath(typeof(FileUtilities).Assembly.Location)); - return fullPath; + /// + /// Determines the full path for the given file-spec. + /// ASSUMES INPUT IS STILL ESCAPED. + /// + /// The file spec to get the full path of. + /// + /// Whether to escape the path after getting the full path. + /// Full path to the file, escaped if not specified otherwise. + private static string GetFullPath(string fileSpec, string currentDirectory, bool escape = true) + { + // Sending data out of the engine into the filesystem, so time to unescape. + fileSpec = EscapingUtilities.UnescapeAll(fileSpec); + + string fullPath = NormalizePath(Path.Combine(currentDirectory, fileSpec)); + // In some cases we might want to NOT escape in order to preserve symbols like @, %, $ etc. + if (escape) + { + // Data coming back from the filesystem into the engine, so time to escape it back. + fullPath = EscapingUtilities.Escape(fullPath); } - /// - /// A variation of Path.GetFullPath that will return the input value - /// instead of throwing any IO exception. - /// Useful to get a better path for an error message, without the risk of throwing - /// if the error message was itself caused by the path being invalid! - /// - private static string GetFullPathNoThrow(string path) + if (!EndsWithSlash(fullPath)) { - try - { - path = NormalizePath(path); - } - catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) + if (FileUtilitiesRegex.IsDrivePattern(fileSpec) || + FileUtilitiesRegex.IsUncPattern(fullPath)) { + // append trailing slash if Path.GetFullPath failed to (this happens with drive-specs and UNC shares) + fullPath += Path.DirectorySeparatorChar; } - - return path; } - /// - /// Gets a file info object for the specified file path. If the file path - /// is invalid, or is a directory, or cannot be accessed, or does not exist, - /// it returns null rather than throwing or returning a FileInfo around a non-existent file. - /// This allows it to be called where File.Exists() (which never throws, and returns false - /// for directories) was called - but with the advantage that a FileInfo object is returned - /// that can be queried (e.g., for LastWriteTime) without hitting the disk again. - /// - /// - /// FileInfo around path if it is an existing /file/, else null - private static FileInfo GetFileInfoNoThrow(string filePath) + return fullPath; + } + + /// + /// A variation of Path.GetFullPath that will return the input value + /// instead of throwing any IO exception. + /// Useful to get a better path for an error message, without the risk of throwing + /// if the error message was itself caused by the path being invalid! + /// + private static string GetFullPathNoThrow(string path) + { + try { - filePath = AttemptToShortenPath(filePath); + path = NormalizePath(path); + } + catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) + { + } - FileInfo fileInfo; + return path; + } - try - { - fileInfo = new FileInfo(filePath); - } - catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) - { - // Invalid or inaccessible path: treat as if nonexistent file, just as File.Exists does - return null; - } + /// + /// Gets a file info object for the specified file path. If the file path + /// is invalid, or is a directory, or cannot be accessed, or does not exist, + /// it returns null rather than throwing or returning a FileInfo around a non-existent file. + /// This allows it to be called where File.Exists() (which never throws, and returns false + /// for directories) was called - but with the advantage that a FileInfo object is returned + /// that can be queried (e.g., for LastWriteTime) without hitting the disk again. + /// + /// + /// FileInfo around path if it is an existing /file/, else nul.l + private static FileInfo? GetFileInfoNoThrow(string filePath) + { + filePath = AttemptToShortenPath(filePath); - if (fileInfo.Exists) - { - // It's an existing file - return fileInfo; - } - else - { - // Nonexistent, or existing but a directory, just as File.Exists behaves - return null; - } - } + FileInfo fileInfo; - /// - /// Normalizes the path if and only if it is longer than max path, - /// or would be if rooted by the current directory. - /// This may make it shorter by removing ".."'s. - /// - private static string AttemptToShortenPath(string path) + try { - if (IsPathTooLong(path) || IsPathTooLongIfRooted(path)) - { - // Attempt to make it shorter -- perhaps there are some \..\ elements - path = GetFullPathNoThrow(path); - } - return FixFilePath(path); + fileInfo = new FileInfo(filePath); } - - private static bool IsPathTooLong(string path) + catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) { - // >= not > because MAX_PATH assumes a trailing null - return path.Length >= NativeMethods.MaxPath; + // Invalid or inaccessible path: treat as if nonexistent file, just as File.Exists does + return null; } - private static bool IsPathTooLongIfRooted(string path) + return fileInfo.Exists + ? fileInfo // It's an existing file + : null; // Nonexistent, or existing but a directory, just as File.Exists behaves + } + + /// + /// Normalizes the path if and only if it is longer than max path, + /// or would be if rooted by the current directory. + /// This may make it shorter by removing ".."'s. + /// + private static string AttemptToShortenPath(string path) + { + if (IsPathTooLong(path) || IsPathTooLongIfRooted(path)) { - bool hasMaxPath = NativeMethods.HasMaxPath; - int maxPath = NativeMethods.MaxPath; - // >= not > because MAX_PATH assumes a trailing null - return hasMaxPath && !IsRootedNoThrow(path) && NativeMethods.GetCurrentDirectory().Length + path.Length + 1 /* slash */ >= maxPath; + // Attempt to make it shorter -- perhaps there are some \..\ elements + path = GetFullPathNoThrow(path); } - /// - /// A variation of Path.IsRooted that not throw any IO exception. - /// - private static bool IsRootedNoThrow(string path) + return path; + } + + private static bool IsPathTooLong(string path) + => path.Length >= NativeMethods.MaxPath; // >= not > because MAX_PATH assumes a trailing null + + private static bool IsPathTooLongIfRooted(string path) + { + bool hasMaxPath = NativeMethods.HasMaxPath; + int maxPath = NativeMethods.MaxPath; + // >= not > because MAX_PATH assumes a trailing null + return hasMaxPath && !IsRootedNoThrow(path) && NativeMethods.GetCurrentDirectory().Length + path.Length + 1 /* slash */ >= maxPath; + } + + /// + /// A variation of Path.IsRooted that not throw any IO exception. + /// + private static bool IsRootedNoThrow(string path) + { + try { - try - { - return Path.IsPathRooted(FixFilePath(path)); - } - catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) - { - return false; - } + return Path.IsPathRooted(path); } - - internal static StreamWriter OpenWrite(string path, bool append, Encoding encoding = null) + catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) { - const int DefaultFileStreamBufferSize = 4096; - FileMode mode = append ? FileMode.Append : FileMode.Create; - Stream fileStream = new FileStream(path, mode, FileAccess.Write, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan); - if (encoding == null) - { - return new StreamWriter(fileStream); - } - else - { - return new StreamWriter(fileStream, encoding); - } + return false; } } + + internal static StreamWriter CreateWriterForAppend(string path) + { + const int DefaultFileStreamBufferSize = 4096; + + var fileStream = new FileStream( + path, + mode: FileMode.Append, + access: FileAccess.Write, + share: FileShare.Read, + bufferSize: DefaultFileStreamBufferSize, + options: FileOptions.SequentialScan); + + return new StreamWriter(fileStream); + } } diff --git a/src/MSBuildTaskHost/Utilities/Modifiers.cs b/src/MSBuildTaskHost/Utilities/Modifiers.cs index f0c1b090fa4..09730bb50a6 100644 --- a/src/MSBuildTaskHost/Utilities/Modifiers.cs +++ b/src/MSBuildTaskHost/Utilities/Modifiers.cs @@ -217,7 +217,7 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS else { // Fix path to avoid problem with Path.GetFileNameWithoutExtension when backslashes in itemSpec on Unix - modifiedItemSpec = Path.GetFileNameWithoutExtension(FixFilePath(itemSpec)); + modifiedItemSpec = Path.GetFileNameWithoutExtension(itemSpec); } } else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.Extension, StringComparison.OrdinalIgnoreCase)) From daf60cda5f6c457227adaf2ac23e01bfa2cccbc5 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 6 Feb 2026 13:26:04 -0800 Subject: [PATCH 121/136] MSBuildTaskHost: Merge FileUtilitiesRegex into FileUtilities --- .../Utilities/FileUtilities.cs | 152 ++++++++++++++-- .../Utilities/FileUtilitiesRegex.cs | 162 ------------------ src/MSBuildTaskHost/Utilities/Modifiers.cs | 29 ++-- 3 files changed, 151 insertions(+), 192 deletions(-) delete mode 100644 src/MSBuildTaskHost/Utilities/FileUtilitiesRegex.cs diff --git a/src/MSBuildTaskHost/Utilities/FileUtilities.cs b/src/MSBuildTaskHost/Utilities/FileUtilities.cs index 3e633e01b32..65e508a85e7 100644 --- a/src/MSBuildTaskHost/Utilities/FileUtilities.cs +++ b/src/MSBuildTaskHost/Utilities/FileUtilities.cs @@ -14,15 +14,24 @@ namespace Microsoft.Build.TaskHost.Utilities; /// internal static partial class FileUtilities { - internal static string TempFileDirectory => Path.GetTempPath(); + private const char BackSlash = '\\'; + private const char ForwardSlash = '/'; + + // ISO 8601 Universal time with sortable format + private const string FileTimeFormat = "yyyy'-'MM'-'dd HH':'mm':'ss'.'fffffff"; + + public static string TempFileDirectory => Path.GetTempPath(); + + public static string MSBuildTaskHostDirectory + => field ??= Path.GetDirectoryName(Path.GetFullPath(typeof(FileUtilities).Assembly.Location)); /// /// Indicates if the given character is a slash. /// /// /// true, if slash - private static bool IsSlash(char c) - => c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar; + private static bool IsAnySlash(char c) + => c is BackSlash or ForwardSlash; /// /// Indicates if the given file-spec ends with a slash. @@ -30,7 +39,130 @@ private static bool IsSlash(char c) /// The file spec. /// true, if file-spec has trailing slash private static bool EndsWithSlash(string fileSpec) - => fileSpec.Length > 0 && IsSlash(fileSpec[fileSpec.Length - 1]); + => fileSpec.Length > 0 && IsAnySlash(fileSpec[fileSpec.Length - 1]); + + /// + /// Indicates whether the specified string follows the pattern drive pattern (for example "C:", "D:"). + /// + /// Input to check for drive pattern. + /// true if follows the drive pattern, false otherwise. + private static bool IsDrivePattern(string pattern) + => pattern.Length == 2 && StartsWithDrivePattern(pattern); // Format must be two characters long: ":" + + /// + /// Indicates whether the specified string follows the pattern drive pattern (for example "C:/" or "C:\"). + /// + /// Input to check for drive pattern with slash. + /// true if follows the drive pattern with slash, false otherwise. + private static bool IsDrivePatternWithSlash(string pattern) + => pattern.Length == 3 && StartsWithDrivePatternWithSlash(pattern); + + /// + /// Indicates whether the specified string starts with the drive pattern (for example "C:"). + /// + /// Input to check for drive pattern. + /// true if starts with drive pattern, false otherwise. + private static bool StartsWithDrivePattern(string pattern) + // Format dictates a length of at least 2, + // first character must be a letter, + // second character must be a ":" + => pattern.Length >= 2 && + (pattern[0] is (>= 'A' and <= 'Z') or (>= 'a' and <= 'z')) && + pattern[1] == ':'; + + /// + /// Indicates whether the specified string starts with the drive pattern (for example "C:/" or "C:\"). + /// + /// Input to check for drive pattern. + /// true if starts with drive pattern with slash, false otherwise. + private static bool StartsWithDrivePatternWithSlash(string pattern) + // Format dictates a length of at least 3, + // first character must be a letter, + // second character must be a ":" + // third character must be a slash. + => pattern.Length >= 3 && + StartsWithDrivePattern(pattern) && + pattern[2] is BackSlash or ForwardSlash; + + /// + /// Indicates whether the specified file-spec comprises exactly "\\server\share" (with no trailing characters). + /// + /// Input to check for UNC pattern. + /// true if comprises UNC pattern. + private static bool IsUncPattern(string pattern) + // Return value == pattern.length means: + // meets minimum unc requirements + // pattern does not end in a '/' or '\' + // if a subfolder were found the value returned would be length up to that subfolder, therefore no subfolder exists + => StartsWithUncPatternMatchLength(pattern) == pattern.Length; + + /// + /// Indicates whether the specified file-spec begins with "\\server\share". + /// + /// Input to check for UNC pattern. + /// true if starts with UNC pattern. + private static bool StartsWithUncPattern(string pattern) + // Any non -1 value returned means there was a match, therefore is begins with the pattern. + => StartsWithUncPatternMatchLength(pattern) != -1; + + /// + /// Indicates whether the file-spec begins with a UNC pattern and how long the match is. + /// + /// Input to check for UNC pattern. + /// length of the match, -1 if no match. + private static int StartsWithUncPatternMatchLength(string pattern) + { + if (!MeetsUncPatternMinimumRequirements(pattern)) + { + return -1; + } + + bool prevCharWasSlash = true; + bool hasShare = false; + + for (int i = 2; i < pattern.Length; i++) + { + // Real UNC paths should only contain backslashes. However, the previous + // regex pattern accepted both so functionality will be retained. + if (pattern[i] is BackSlash or ForwardSlash) + { + if (prevCharWasSlash) + { + // We get here in the case of an extra slash. + return -1; + } + else if (hasShare) + { + return i; + } + + hasShare = true; + prevCharWasSlash = true; + } + else + { + prevCharWasSlash = false; + } + } + + if (!hasShare) + { + // no subfolder means no unc pattern. string is something like "\\abc" in this case + return -1; + } + + return pattern.Length; + } + + /// + /// Indicates whether or not the file-spec meets the minimum requirements of a UNC pattern. + /// + /// Input to check for UNC pattern minimum requirements. + /// true if the UNC pattern is a minimum length of 5 and the first two characters are be a slash, false otherwise. + private static bool MeetsUncPatternMinimumRequirements(string pattern) + => pattern.Length >= 5 && + pattern[0] is BackSlash or ForwardSlash && + pattern[1] is BackSlash or ForwardSlash; /// /// Gets the canonicalized full path of the provided path. @@ -116,7 +248,7 @@ private static string GetDirectory(string fileSpec) // just use the file-spec as-is directory = fileSpec; } - else if ((directory.Length > 0) && !EndsWithSlash(directory)) + else if (directory.Length > 0 && !EndsWithSlash(directory)) { // restore trailing slash if Path.GetDirectoryName has removed it (this happens with non-root directories) directory += Path.DirectorySeparatorChar; @@ -125,12 +257,6 @@ private static string GetDirectory(string fileSpec) return directory; } - // ISO 8601 Universal time with sortable format - private const string FileTimeFormat = "yyyy'-'MM'-'dd HH':'mm':'ss'.'fffffff"; - - public static string MSBuildTaskHostDirectory - => field ??= Path.GetDirectoryName(Path.GetFullPath(typeof(FileUtilities).Assembly.Location)); - /// /// Determines the full path for the given file-spec. /// ASSUMES INPUT IS STILL ESCAPED. @@ -145,6 +271,7 @@ private static string GetFullPath(string fileSpec, string currentDirectory, bool fileSpec = EscapingUtilities.UnescapeAll(fileSpec); string fullPath = NormalizePath(Path.Combine(currentDirectory, fileSpec)); + // In some cases we might want to NOT escape in order to preserve symbols like @, %, $ etc. if (escape) { @@ -154,8 +281,7 @@ private static string GetFullPath(string fileSpec, string currentDirectory, bool if (!EndsWithSlash(fullPath)) { - if (FileUtilitiesRegex.IsDrivePattern(fileSpec) || - FileUtilitiesRegex.IsUncPattern(fullPath)) + if (IsDrivePattern(fileSpec) || IsUncPattern(fullPath)) { // append trailing slash if Path.GetFullPath failed to (this happens with drive-specs and UNC shares) fullPath += Path.DirectorySeparatorChar; diff --git a/src/MSBuildTaskHost/Utilities/FileUtilitiesRegex.cs b/src/MSBuildTaskHost/Utilities/FileUtilitiesRegex.cs deleted file mode 100644 index e70a12bb7bf..00000000000 --- a/src/MSBuildTaskHost/Utilities/FileUtilitiesRegex.cs +++ /dev/null @@ -1,162 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable disable - -namespace Microsoft.Build.TaskHost.Utilities -{ - /// - /// This class contains utility methods for file IO. - /// Separate from FileUtilities because some assemblies may only need the patterns. - /// PERF\COVERAGE NOTE: Try to keep classes in 'shared' as granular as possible. All the methods in - /// each class get pulled into the resulting assembly. - /// - internal static class FileUtilitiesRegex - { - private const char _backSlash = '\\'; - private const char _forwardSlash = '/'; - - /// - /// Indicates whether the specified string follows the pattern drive pattern (for example "C:", "D:"). - /// - /// Input to check for drive pattern. - /// true if follows the drive pattern, false otherwise. - internal static bool IsDrivePattern(string pattern) - { - // Format must be two characters long: ":" - return pattern.Length == 2 && - StartsWithDrivePattern(pattern); - } - - /// - /// Indicates whether the specified string follows the pattern drive pattern (for example "C:/" or "C:\"). - /// - /// Input to check for drive pattern with slash. - /// true if follows the drive pattern with slash, false otherwise. - internal static bool IsDrivePatternWithSlash(string pattern) - { - return pattern.Length == 3 && - StartsWithDrivePatternWithSlash(pattern); - } - - /// - /// Indicates whether the specified string starts with the drive pattern (for example "C:"). - /// - /// Input to check for drive pattern. - /// true if starts with drive pattern, false otherwise. - internal static bool StartsWithDrivePattern(string pattern) - { - // Format dictates a length of at least 2, - // first character must be a letter, - // second character must be a ":" - return pattern.Length >= 2 && - ((pattern[0] >= 'A' && pattern[0] <= 'Z') || (pattern[0] >= 'a' && pattern[0] <= 'z')) && - pattern[1] == ':'; - } - - /// - /// Indicates whether the specified string starts with the drive pattern (for example "C:/" or "C:\"). - /// - /// Input to check for drive pattern. - /// true if starts with drive pattern with slash, false otherwise. - internal static bool StartsWithDrivePatternWithSlash(string pattern) - { - // Format dictates a length of at least 3, - // first character must be a letter, - // second character must be a ":" - // third character must be a slash. - return pattern.Length >= 3 && - StartsWithDrivePattern(pattern) && - (pattern[2] == _backSlash || pattern[2] == _forwardSlash); - } - - /// - /// Indicates whether the specified file-spec comprises exactly "\\server\share" (with no trailing characters). - /// - /// Input to check for UNC pattern. - /// true if comprises UNC pattern. - internal static bool IsUncPattern(string pattern) - { - // Return value == pattern.length means: - // meets minimum unc requirements - // pattern does not end in a '/' or '\' - // if a subfolder were found the value returned would be length up to that subfolder, therefore no subfolder exists - return StartsWithUncPatternMatchLength(pattern) == pattern.Length; - } - - /// - /// Indicates whether the specified file-spec begins with "\\server\share". - /// - /// Input to check for UNC pattern. - /// true if starts with UNC pattern. - internal static bool StartsWithUncPattern(string pattern) - { - // Any non -1 value returned means there was a match, therefore is begins with the pattern. - return StartsWithUncPatternMatchLength(pattern) != -1; - } - - /// - /// Indicates whether the file-spec begins with a UNC pattern and how long the match is. - /// - /// Input to check for UNC pattern. - /// length of the match, -1 if no match. - internal static int StartsWithUncPatternMatchLength(string pattern) - { - if (!MeetsUncPatternMinimumRequirements(pattern)) - { - return -1; - } - - bool prevCharWasSlash = true; - bool hasShare = false; - - for (int i = 2; i < pattern.Length; i++) - { - // Real UNC paths should only contain backslashes. However, the previous - // regex pattern accepted both so functionality will be retained. - if (pattern[i] == _backSlash || - pattern[i] == _forwardSlash) - { - if (prevCharWasSlash) - { - // We get here in the case of an extra slash. - return -1; - } - else if (hasShare) - { - return i; - } - - hasShare = true; - prevCharWasSlash = true; - } - else - { - prevCharWasSlash = false; - } - } - - if (!hasShare) - { - // no subfolder means no unc pattern. string is something like "\\abc" in this case - return -1; - } - - return pattern.Length; - } - - /// - /// Indicates whether or not the file-spec meets the minimum requirements of a UNC pattern. - /// - /// Input to check for UNC pattern minimum requirements. - /// true if the UNC pattern is a minimum length of 5 and the first two characters are be a slash, false otherwise. - internal static bool MeetsUncPatternMinimumRequirements(string pattern) - { - return pattern.Length >= 5 && - (pattern[0] == _backSlash || - pattern[0] == _forwardSlash) && - (pattern[1] == _backSlash || - pattern[1] == _forwardSlash); - } - } -} diff --git a/src/MSBuildTaskHost/Utilities/Modifiers.cs b/src/MSBuildTaskHost/Utilities/Modifiers.cs index 09730bb50a6..9ea3dd2dcb4 100644 --- a/src/MSBuildTaskHost/Utilities/Modifiers.cs +++ b/src/MSBuildTaskHost/Utilities/Modifiers.cs @@ -197,7 +197,8 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS if (!EndsWithSlash(modifiedItemSpec)) { - ErrorUtilities.VerifyThrow(FileUtilitiesRegex.StartsWithUncPattern(modifiedItemSpec), + ErrorUtilities.VerifyThrow( + StartsWithUncPattern(modifiedItemSpec), "Only UNC shares should be missing trailing slashes."); // restore/append trailing slash if Path.GetPathRoot() has either removed it, or failed to add it @@ -244,19 +245,13 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS modifiedItemSpec = GetDirectory(fullPath); - int length = -1; - if (FileUtilitiesRegex.StartsWithDrivePattern(modifiedItemSpec)) - { - length = 2; - } - else - { - length = FileUtilitiesRegex.StartsWithUncPatternMatchLength(modifiedItemSpec); - } + int length = StartsWithDrivePattern(modifiedItemSpec) + ? 2 + : StartsWithUncPatternMatchLength(modifiedItemSpec); if (length != -1) { - ErrorUtilities.VerifyThrow((modifiedItemSpec.Length > length) && IsSlash(modifiedItemSpec[length]), + ErrorUtilities.VerifyThrow((modifiedItemSpec.Length > length) && IsAnySlash(modifiedItemSpec[length]), "Root directory must have a trailing slash."); modifiedItemSpec = modifiedItemSpec.Substring(length + 1); @@ -389,11 +384,11 @@ private static bool IsRootDirectory(string path) return false; } - int uncMatchLength = FileUtilitiesRegex.StartsWithUncPatternMatchLength(path); + int uncMatchLength = StartsWithUncPatternMatchLength(path); // Determine if the given path is a standard drive/unc pattern root - if (FileUtilitiesRegex.IsDrivePattern(path) || - FileUtilitiesRegex.IsDrivePatternWithSlash(path) || + if (IsDrivePattern(path) || + IsDrivePatternWithSlash(path) || uncMatchLength == path.Length) { return true; @@ -408,9 +403,9 @@ private static bool IsRootDirectory(string path) // Eliminate any drive patterns that don't have a slash after the colon or where the 4th character is a non-slash // A non-slash at [3] is specifically checked here because Path.GetDirectoryName // considers "C:///" a valid root. - if (FileUtilitiesRegex.StartsWithDrivePattern(path) && - ((path.Length >= 3 && path[2] != '\\' && path[2] != '/') || - (path.Length >= 4 && path[3] != '\\' && path[3] != '/'))) + if (StartsWithDrivePattern(path) && + ((path.Length >= 3 && path[2] is not (BackSlash or ForwardSlash)) || + (path.Length >= 4 && path[3] is not (BackSlash or ForwardSlash)))) { return false; } From bf875be288e7db341558ac61ca0de139f7ec4b2c Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 6 Feb 2026 15:18:56 -0800 Subject: [PATCH 122/136] MSBuildTaskHost: Clean up Modifiers and NativeMethods - File-scoped namespace - Expression-bodied members - Enable nullability - Rename file to FileUtilities.ItemSpecModifiers.cs - Refactor and break down GetItemSpecModifier for clarity - Use Dictionary rather than HashSet to avoid repeated string comparisons. - Use same approach for ModifiedTime as CreatedTime and AccessedTime, resulting in uncalled methods to remove in FileUtilities. --- src/MSBuildTaskHost/BackEnd/TaskParameter.cs | 3 +- .../FileUtilities.ItemSpecModifiers.cs | 435 ++++++++++++++++++ .../Utilities/FileUtilities.cs | 97 +--- src/MSBuildTaskHost/Utilities/Modifiers.cs | 433 ----------------- .../Utilities/NativeMethods.cs | 143 +----- 5 files changed, 459 insertions(+), 652 deletions(-) create mode 100644 src/MSBuildTaskHost/Utilities/FileUtilities.ItemSpecModifiers.cs delete mode 100644 src/MSBuildTaskHost/Utilities/Modifiers.cs diff --git a/src/MSBuildTaskHost/BackEnd/TaskParameter.cs b/src/MSBuildTaskHost/BackEnd/TaskParameter.cs index 081e6236907..cae9c289530 100644 --- a/src/MSBuildTaskHost/BackEnd/TaskParameter.cs +++ b/src/MSBuildTaskHost/BackEnd/TaskParameter.cs @@ -735,7 +735,8 @@ private string GetMetadataValueEscaped(string metadataName) { // FileUtilities.GetItemSpecModifier is expecting escaped data, which we assume we already are. // Passing in a null for currentDirectory indicates we are already in the correct current directory - metadataValue = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(null, _escapedItemSpec, _escapedDefiningProject, metadataName, ref _fullPath); + metadataValue = FileUtilities.ItemSpecModifiers.GetItemSpecModifier( + currentDirectory: null, _escapedItemSpec!, _escapedDefiningProject, metadataName, ref _fullPath); } else if (_customEscapedMetadata != null) { diff --git a/src/MSBuildTaskHost/Utilities/FileUtilities.ItemSpecModifiers.cs b/src/MSBuildTaskHost/Utilities/FileUtilities.ItemSpecModifiers.cs new file mode 100644 index 00000000000..d01bde782c1 --- /dev/null +++ b/src/MSBuildTaskHost/Utilities/FileUtilities.ItemSpecModifiers.cs @@ -0,0 +1,435 @@ +// 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; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using Microsoft.Build.TaskHost.Resources; + +namespace Microsoft.Build.TaskHost.Utilities; + +internal static partial class FileUtilities +{ + /// + /// Encapsulates the definitions of the item-spec modifiers a.k.a. reserved item metadata. + /// + internal static class ItemSpecModifiers + { + internal const string FullPath = "FullPath"; + internal const string RootDir = "RootDir"; + internal const string Filename = "Filename"; + internal const string Extension = "Extension"; + internal const string RelativeDir = "RelativeDir"; + internal const string Directory = "Directory"; + internal const string RecursiveDir = "RecursiveDir"; + internal const string Identity = "Identity"; + internal const string ModifiedTime = "ModifiedTime"; + internal const string CreatedTime = "CreatedTime"; + internal const string AccessedTime = "AccessedTime"; + internal const string DefiningProjectFullPath = "DefiningProjectFullPath"; + internal const string DefiningProjectDirectory = "DefiningProjectDirectory"; + internal const string DefiningProjectName = "DefiningProjectName"; + internal const string DefiningProjectExtension = "DefiningProjectExtension"; + + // These are all the well-known attributes. + internal static readonly string[] All = + [ + FullPath, + RootDir, + Filename, + Extension, + RelativeDir, + Directory, + RecursiveDir, // <-- Not derivable. + Identity, + ModifiedTime, + CreatedTime, + AccessedTime, + DefiningProjectFullPath, + DefiningProjectDirectory, + DefiningProjectName, + DefiningProjectExtension + ]; + + private enum ItemSpecModifierKind + { + FullPath, + RootDir, + Filename, + Extension, + RelativeDir, + Directory, + RecursiveDir, // <-- Not derivable. + Identity, + ModifiedTime, + CreatedTime, + AccessedTime, + DefiningProjectFullPath, + DefiningProjectDirectory, + DefiningProjectName, + DefiningProjectExtension + } + + private static readonly Dictionary s_itemSpecModifierMap = new(StringComparer.OrdinalIgnoreCase) + { + { FullPath, ItemSpecModifierKind.FullPath }, + { RootDir, ItemSpecModifierKind.RootDir }, + { Filename, ItemSpecModifierKind.Filename }, + { Extension, ItemSpecModifierKind.Extension }, + { RelativeDir, ItemSpecModifierKind.RelativeDir }, + { Directory, ItemSpecModifierKind.Directory }, + { RecursiveDir, ItemSpecModifierKind.RecursiveDir }, + { Identity, ItemSpecModifierKind.Identity }, + { ModifiedTime, ItemSpecModifierKind.ModifiedTime }, + { CreatedTime, ItemSpecModifierKind.CreatedTime }, + { AccessedTime, ItemSpecModifierKind.AccessedTime }, + { DefiningProjectFullPath, ItemSpecModifierKind.DefiningProjectFullPath }, + { DefiningProjectDirectory, ItemSpecModifierKind.DefiningProjectDirectory }, + { DefiningProjectName, ItemSpecModifierKind.DefiningProjectName }, + { DefiningProjectExtension, ItemSpecModifierKind.DefiningProjectExtension } + }; + + /// + /// Indicates if the given name is reserved for an item-spec modifier. + /// + internal static bool IsItemSpecModifier([NotNullWhen(true)] string name) + => name != null && s_itemSpecModifierMap.ContainsKey(name); + + /// + /// Indicates if the given name is reserved for a derivable item-spec modifier. + /// Derivable means it can be computed given a file name. + /// + /// Name to check. + /// true, if name of a derivable modifier + internal static bool IsDerivableItemSpecModifier(string name) + { + bool isItemSpecModifier = IsItemSpecModifier(name); + + if (isItemSpecModifier && name.Length == 12 && name[0] is 'R' or 'r') + { + // The only 12 letter ItemSpecModifier that starts with 'R' is 'RecursiveDir' + return false; + } + + return isItemSpecModifier; + } + + /// + /// Performs path manipulations on the given item-spec as directed. + /// + /// Supported modifiers: + /// %(FullPath) = full path of item + /// %(RootDir) = root directory of item + /// %(Filename) = item filename without extension + /// %(Extension) = item filename extension + /// %(RelativeDir) = item directory as given in item-spec + /// %(Directory) = full path of item directory relative to root + /// %(RecursiveDir) = portion of item path that matched a recursive wildcard + /// %(Identity) = item-spec as given + /// %(ModifiedTime) = last write time of item + /// %(CreatedTime) = creation time of item + /// %(AccessedTime) = last access time of item + /// + /// NOTES: + /// 1) This method always returns an empty string for the %(RecursiveDir) modifier because it does not have enough + /// information to compute it -- only the BuildItem class can compute this modifier. + /// 2) All but the file time modifiers could be cached, but it's not worth the space. Only full path is cached, as the others are just string manipulations. + /// + /// + /// Methods of the Path class "normalize" slashes and periods. For example: + /// 1) successive slashes are combined into 1 slash + /// 2) trailing periods are discarded + /// 3) forward slashes are changed to back-slashes + /// + /// As a result, we cannot rely on any file-spec that has passed through a Path method to remain the same. We will + /// therefore not bother preserving slashes and periods when file-specs are transformed. + /// + /// Never returns null. + /// + /// The root directory for relative item-specs. When called on the Engine thread, this is the project directory. When called as part of building a task, it is null, indicating that the current directory should be used. + /// The item-spec to modify. + /// The path to the project that defined this item (may be null). + /// The modifier to apply to the item-spec. + /// Full path if any was previously computed, to cache. + /// The modified item-spec (can be empty string, but will never be null). + /// Thrown when the item-spec is not a path. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Pre-existing")] + internal static string GetItemSpecModifier( + string? currentDirectory, + string itemSpec, + string? definingProjectEscaped, + string modifier, + ref string? fullPath) + { + ErrorUtilities.VerifyThrow(itemSpec != null, "Need item-spec to modify."); + ErrorUtilities.VerifyThrow(modifier != null, "Need modifier to apply to item-spec."); + + try + { + if (s_itemSpecModifierMap.TryGetValue(modifier, out ItemSpecModifierKind kind)) + { + switch (kind) + { + case ItemSpecModifierKind.FullPath: + ComputeFullPath(itemSpec, currentDirectory, ref fullPath); + return fullPath; + + case ItemSpecModifierKind.RootDir: + ComputeFullPath(itemSpec, currentDirectory, ref fullPath); + return ComputeRootDir(fullPath); + + case ItemSpecModifierKind.Filename: + return ComputeFileName(itemSpec); + + case ItemSpecModifierKind.Extension: + return ComputeExtension(itemSpec); + + case ItemSpecModifierKind.RelativeDir: + return ComputeRelativeDir(itemSpec); + + case ItemSpecModifierKind.Directory: + ComputeFullPath(itemSpec, currentDirectory, ref fullPath); + return ComputeDirectory(fullPath); + + case ItemSpecModifierKind.RecursiveDir: + // only the BuildItem class can compute this modifier -- so leave empty + return string.Empty; + + case ItemSpecModifierKind.Identity: + return itemSpec; + + case ItemSpecModifierKind.ModifiedTime: + return ComputeModifiedTime(itemSpec); + + case ItemSpecModifierKind.CreatedTime: + return ComputeCreatedTime(itemSpec); + + case ItemSpecModifierKind.AccessedTime: + return ComputeAccessedTime(itemSpec); + + case ItemSpecModifierKind.DefiningProjectDirectory: + case ItemSpecModifierKind.DefiningProjectFullPath: + case ItemSpecModifierKind.DefiningProjectName: + case ItemSpecModifierKind.DefiningProjectExtension: + if (string.IsNullOrEmpty(definingProjectEscaped)) + { + // We have nothing to work with, but that's sometimes OK -- so just return String.Empty + return string.Empty; + } + + ErrorUtilities.VerifyThrow(definingProjectEscaped != null, $"{nameof(definingProjectEscaped)} is null."); + + switch (kind) + { + case ItemSpecModifierKind.DefiningProjectDirectory: + return ComputeDefiningProjectDirectory(definingProjectEscaped, currentDirectory); + + case ItemSpecModifierKind.DefiningProjectFullPath: + return ComputeDefiningProjectFullPath(definingProjectEscaped, currentDirectory); + + case ItemSpecModifierKind.DefiningProjectName: + return ComputeDefiningProjectFileName(definingProjectEscaped); + + case ItemSpecModifierKind.DefiningProjectExtension: + return ComputeDefiningProjectExtension(definingProjectEscaped); + } + + break; + } + } + } + catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) + { + ErrorUtilities.ThrowInvalidOperation(SR.Shared_InvalidFilespecForTransform, modifier, itemSpec, e.Message); + } + + ErrorUtilities.ThrowInternalError($"\"{modifier}\" is not a valid item-spec modifier."); + return null; + } + + private static string ComputeFullPath(string itemSpec, string? currentDirectory) + { + currentDirectory ??= string.Empty; + string fullPath = GetFullPath(itemSpec, currentDirectory); + + ThrowForUrl(fullPath, itemSpec, currentDirectory); + + return fullPath; + } + + private static void ComputeFullPath(string itemSpec, string? currentDirectory, [NotNull] ref string? existingFullPath) + { + if (existingFullPath != null) + { + return; + } + + existingFullPath = ComputeFullPath(itemSpec, currentDirectory); + } + + private static string ComputeRootDir(string fullPath) + { + string rootDir = Path.GetPathRoot(fullPath); + + if (!EndsWithSlash(rootDir)) + { + ErrorUtilities.VerifyThrow( + StartsWithUncPattern(rootDir), + "Only UNC shares should be missing trailing slashes."); + + // restore/append trailing slash if Path.GetPathRoot() has either removed it, or failed to add it + // (this happens with UNC shares) + rootDir += Path.DirectorySeparatorChar; + } + + return rootDir; + } + + private static string ComputeFileName(string itemSpec) + // if the item-spec is a root directory, it can have no filename + // NOTE: this is to prevent Path.GetFileNameWithoutExtension() from treating server and share elements + // in a UNC file-spec as filenames e.g. \\server, \\server\share + => IsRootDirectory(itemSpec) + ? string.Empty + : Path.GetFileNameWithoutExtension(itemSpec); + + private static string ComputeExtension(string itemSpec) + // if the item-spec is a root directory, it can have no extension + // NOTE: this is to prevent Path.GetFileNameWithoutExtension() from treating server and share elements + // in a UNC file-spec as filenames e.g. \\server, \\server\share + => IsRootDirectory(itemSpec) + ? string.Empty + : Path.GetExtension(itemSpec); + + private static string ComputeRelativeDir(string itemSpec) + => GetDirectory(itemSpec); + + private static string ComputeDirectory(string fullPath) + { + string directory = GetDirectory(fullPath); + + int length = StartsWithDrivePattern(directory) + ? 2 + : StartsWithUncPatternMatchLength(directory); + + if (length != -1) + { + ErrorUtilities.VerifyThrow( + directory.Length > length && IsAnySlash(directory[length]), + "Root directory must have a trailing slash."); + + directory = directory.Substring(length + 1); + } + + return directory; + } + + private static string ComputeModifiedTime(string itemSpec) + { + // About to go out to the filesystem. This means data is leaving the engine, so need to unescape first. + string unescapedItemSpec = EscapingUtilities.UnescapeAll(itemSpec); + + return NativeMethods.FileExists(unescapedItemSpec) + ? File.GetLastWriteTime(unescapedItemSpec).ToString(FileTimeFormat, provider: null) + : string.Empty; // File does not exist, or path is a directory + } + + private static string ComputeCreatedTime(string itemSpec) + { + // About to go out to the filesystem. This means data is leaving the engine, so need to unescape first. + string unescapedItemSpec = EscapingUtilities.UnescapeAll(itemSpec); + + return NativeMethods.FileExists(unescapedItemSpec) + ? File.GetCreationTime(unescapedItemSpec).ToString(FileTimeFormat, provider: null) + : string.Empty; // File does not exist, or path is a directory + } + + private static string ComputeAccessedTime(string itemSpec) + { + // About to go out to the filesystem. This means data is leaving the engine, so need to unescape first. + string unescapedItemSpec = EscapingUtilities.UnescapeAll(itemSpec); + + return NativeMethods.FileExists(unescapedItemSpec) + ? File.GetLastAccessTime(unescapedItemSpec).ToString(FileTimeFormat, provider: null) + : string.Empty; // File does not exist, or path is a directory + } + + private static string ComputeDefiningProjectDirectory(string itemSpec, string? currentDirectory) + { + string fullPath = ComputeFullPath(itemSpec, currentDirectory); + string rootDir = ComputeRootDir(fullPath); + string directory = ComputeDirectory(fullPath); + + return Path.Combine(rootDir, directory); + } + + private static string ComputeDefiningProjectFullPath(string itemSpec, string? currentDirectory) + => ComputeFullPath(itemSpec, currentDirectory); + + private static string ComputeDefiningProjectFileName(string itemSpec) + => ComputeFileName(itemSpec); + + private static string ComputeDefiningProjectExtension(string itemSpec) + => ComputeExtension(itemSpec); + + /// + /// Indicates whether the given path is a UNC or drive pattern root directory. + /// Note: This function mimics the behavior of checking if Path.GetDirectoryName(path) == null. + /// + /// + /// + private static bool IsRootDirectory(string path) + { + // Eliminate all non-rooted paths + if (!Path.IsPathRooted(path)) + { + return false; + } + + int uncMatchLength = StartsWithUncPatternMatchLength(path); + + // Determine if the given path is a standard drive/unc pattern root + if (IsDrivePattern(path) || + IsDrivePatternWithSlash(path) || + uncMatchLength == path.Length) + { + return true; + } + + // Eliminate all non-root unc paths. + if (uncMatchLength != -1) + { + return false; + } + + // Eliminate any drive patterns that don't have a slash after the colon or where the 4th character is a non-slash + // A non-slash at [3] is specifically checked here because Path.GetDirectoryName + // considers "C:///" a valid root. + if (StartsWithDrivePattern(path) && + ((path.Length >= 3 && path[2] is not (BackSlash or ForwardSlash)) || + (path.Length >= 4 && path[3] is not (BackSlash or ForwardSlash)))) + { + return false; + } + + // There are some edge cases that can get to this point. + // After eliminating valid / invalid roots, fall back on original behavior. + return Path.GetDirectoryName(path) == null; + } + + /// + /// Temporary check for something like http://foo which will end up like c:\foo\bar\http://foo + /// We should either have no colon, or exactly one colon. + /// UNDONE: This is a minimal safe change for Dev10. The correct fix should be to make GetFullPath/NormalizePath throw for this. + /// + private static void ThrowForUrl(string fullPath, string itemSpec, string currentDirectory) + { + if (fullPath.IndexOf(':') != fullPath.LastIndexOf(':')) + { + // Cause a better error to appear + _ = Path.GetFullPath(Path.Combine(currentDirectory, itemSpec)); + } + } + } +} diff --git a/src/MSBuildTaskHost/Utilities/FileUtilities.cs b/src/MSBuildTaskHost/Utilities/FileUtilities.cs index 65e508a85e7..96dce0caa28 100644 --- a/src/MSBuildTaskHost/Utilities/FileUtilities.cs +++ b/src/MSBuildTaskHost/Utilities/FileUtilities.cs @@ -174,11 +174,6 @@ pattern[0] is BackSlash or ForwardSlash && private static string NormalizePath(string path) { ErrorUtilities.VerifyThrowArgumentLength(path); - return GetFullPath(path); - } - - private static string GetFullPath(string path) - { string uncheckedFullPath = NativeMethods.GetFullPath(path); if (IsPathTooLong(uncheckedFullPath)) @@ -248,7 +243,8 @@ private static string GetDirectory(string fileSpec) // just use the file-spec as-is directory = fileSpec; } - else if (directory.Length > 0 && !EndsWithSlash(directory)) + + if (directory.Length > 0 && !EndsWithSlash(directory)) { // restore trailing slash if Path.GetDirectoryName has removed it (this happens with non-root directories) directory += Path.DirectorySeparatorChar; @@ -291,98 +287,9 @@ private static string GetFullPath(string fileSpec, string currentDirectory, bool return fullPath; } - /// - /// A variation of Path.GetFullPath that will return the input value - /// instead of throwing any IO exception. - /// Useful to get a better path for an error message, without the risk of throwing - /// if the error message was itself caused by the path being invalid! - /// - private static string GetFullPathNoThrow(string path) - { - try - { - path = NormalizePath(path); - } - catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) - { - } - - return path; - } - - /// - /// Gets a file info object for the specified file path. If the file path - /// is invalid, or is a directory, or cannot be accessed, or does not exist, - /// it returns null rather than throwing or returning a FileInfo around a non-existent file. - /// This allows it to be called where File.Exists() (which never throws, and returns false - /// for directories) was called - but with the advantage that a FileInfo object is returned - /// that can be queried (e.g., for LastWriteTime) without hitting the disk again. - /// - /// - /// FileInfo around path if it is an existing /file/, else nul.l - private static FileInfo? GetFileInfoNoThrow(string filePath) - { - filePath = AttemptToShortenPath(filePath); - - FileInfo fileInfo; - - try - { - fileInfo = new FileInfo(filePath); - } - catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) - { - // Invalid or inaccessible path: treat as if nonexistent file, just as File.Exists does - return null; - } - - return fileInfo.Exists - ? fileInfo // It's an existing file - : null; // Nonexistent, or existing but a directory, just as File.Exists behaves - } - - /// - /// Normalizes the path if and only if it is longer than max path, - /// or would be if rooted by the current directory. - /// This may make it shorter by removing ".."'s. - /// - private static string AttemptToShortenPath(string path) - { - if (IsPathTooLong(path) || IsPathTooLongIfRooted(path)) - { - // Attempt to make it shorter -- perhaps there are some \..\ elements - path = GetFullPathNoThrow(path); - } - - return path; - } - private static bool IsPathTooLong(string path) => path.Length >= NativeMethods.MaxPath; // >= not > because MAX_PATH assumes a trailing null - private static bool IsPathTooLongIfRooted(string path) - { - bool hasMaxPath = NativeMethods.HasMaxPath; - int maxPath = NativeMethods.MaxPath; - // >= not > because MAX_PATH assumes a trailing null - return hasMaxPath && !IsRootedNoThrow(path) && NativeMethods.GetCurrentDirectory().Length + path.Length + 1 /* slash */ >= maxPath; - } - - /// - /// A variation of Path.IsRooted that not throw any IO exception. - /// - private static bool IsRootedNoThrow(string path) - { - try - { - return Path.IsPathRooted(path); - } - catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) - { - return false; - } - } - internal static StreamWriter CreateWriterForAppend(string path) { const int DefaultFileStreamBufferSize = 4096; diff --git a/src/MSBuildTaskHost/Utilities/Modifiers.cs b/src/MSBuildTaskHost/Utilities/Modifiers.cs deleted file mode 100644 index 9ea3dd2dcb4..00000000000 --- a/src/MSBuildTaskHost/Utilities/Modifiers.cs +++ /dev/null @@ -1,433 +0,0 @@ -// 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; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using Microsoft.Build.TaskHost.Resources; - -#nullable disable - -namespace Microsoft.Build.TaskHost.Utilities -{ - /// - /// This class contains utility methods for file IO. - /// - /// - /// Partial class in order to reduce the amount of sharing into different assemblies - /// - internal static partial class FileUtilities - { - /// - /// Encapsulates the definitions of the item-spec modifiers a.k.a. reserved item metadata. - /// - internal static class ItemSpecModifiers - { - internal const string FullPath = "FullPath"; - internal const string RootDir = "RootDir"; - internal const string Filename = "Filename"; - internal const string Extension = "Extension"; - internal const string RelativeDir = "RelativeDir"; - internal const string Directory = "Directory"; - internal const string RecursiveDir = "RecursiveDir"; - internal const string Identity = "Identity"; - internal const string ModifiedTime = "ModifiedTime"; - internal const string CreatedTime = "CreatedTime"; - internal const string AccessedTime = "AccessedTime"; - internal const string DefiningProjectFullPath = "DefiningProjectFullPath"; - internal const string DefiningProjectDirectory = "DefiningProjectDirectory"; - internal const string DefiningProjectName = "DefiningProjectName"; - internal const string DefiningProjectExtension = "DefiningProjectExtension"; - - // These are all the well-known attributes. - internal static readonly string[] All = - { - FullPath, - RootDir, - Filename, - Extension, - RelativeDir, - Directory, - RecursiveDir, // <-- Not derivable. - Identity, - ModifiedTime, - CreatedTime, - AccessedTime, - DefiningProjectFullPath, - DefiningProjectDirectory, - DefiningProjectName, - DefiningProjectExtension - }; - - private static readonly HashSet s_tableOfItemSpecModifiers = new HashSet(All, StringComparer.OrdinalIgnoreCase); - private static readonly HashSet s_tableOfDefiningProjectModifiers = new HashSet( - [ - DefiningProjectFullPath, - DefiningProjectDirectory, - DefiningProjectName, - DefiningProjectExtension, - ], StringComparer.OrdinalIgnoreCase); - - /// - /// Indicates if the given name is reserved for an item-spec modifier. - /// - internal static bool IsItemSpecModifier(string name) - { - if (name == null) - { - return false; - } - - // Could still be a case-insensitive match. - bool result = s_tableOfItemSpecModifiers.Contains(name); - - return result; - } - - /// - /// Indicates if the given name is reserved for one of the specific subset of itemspec - /// modifiers to do with the defining project of the item. - /// - internal static bool IsDefiningProjectModifier(string name) => s_tableOfDefiningProjectModifiers.Contains(name); - - /// - /// Indicates if the given name is reserved for a derivable item-spec modifier. - /// Derivable means it can be computed given a file name. - /// - /// Name to check. - /// true, if name of a derivable modifier - internal static bool IsDerivableItemSpecModifier(string name) - { - bool isItemSpecModifier = IsItemSpecModifier(name); - - if (isItemSpecModifier) - { - if (name.Length == 12) - { - if (name[0] == 'R' || name[0] == 'r') - { - // The only 12 letter ItemSpecModifier that starts with 'R' is 'RecursiveDir' - return false; - } - } - } - - return isItemSpecModifier; - } - - /// - /// Performs path manipulations on the given item-spec as directed. - /// Does not cache the result. - /// - internal static string GetItemSpecModifier(string currentDirectory, string itemSpec, string definingProjectEscaped, string modifier) - { - string dummy = null; - return GetItemSpecModifier(currentDirectory, itemSpec, definingProjectEscaped, modifier, ref dummy); - } - - /// - /// Performs path manipulations on the given item-spec as directed. - /// - /// Supported modifiers: - /// %(FullPath) = full path of item - /// %(RootDir) = root directory of item - /// %(Filename) = item filename without extension - /// %(Extension) = item filename extension - /// %(RelativeDir) = item directory as given in item-spec - /// %(Directory) = full path of item directory relative to root - /// %(RecursiveDir) = portion of item path that matched a recursive wildcard - /// %(Identity) = item-spec as given - /// %(ModifiedTime) = last write time of item - /// %(CreatedTime) = creation time of item - /// %(AccessedTime) = last access time of item - /// - /// NOTES: - /// 1) This method always returns an empty string for the %(RecursiveDir) modifier because it does not have enough - /// information to compute it -- only the BuildItem class can compute this modifier. - /// 2) All but the file time modifiers could be cached, but it's not worth the space. Only full path is cached, as the others are just string manipulations. - /// - /// - /// Methods of the Path class "normalize" slashes and periods. For example: - /// 1) successive slashes are combined into 1 slash - /// 2) trailing periods are discarded - /// 3) forward slashes are changed to back-slashes - /// - /// As a result, we cannot rely on any file-spec that has passed through a Path method to remain the same. We will - /// therefore not bother preserving slashes and periods when file-specs are transformed. - /// - /// Never returns null. - /// - /// The root directory for relative item-specs. When called on the Engine thread, this is the project directory. When called as part of building a task, it is null, indicating that the current directory should be used. - /// The item-spec to modify. - /// The path to the project that defined this item (may be null). - /// The modifier to apply to the item-spec. - /// Full path if any was previously computed, to cache. - /// The modified item-spec (can be empty string, but will never be null). - /// Thrown when the item-spec is not a path. - [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Pre-existing")] - internal static string GetItemSpecModifier(string currentDirectory, string itemSpec, string definingProjectEscaped, string modifier, ref string fullPath) - { - ErrorUtilities.VerifyThrow(itemSpec != null, "Need item-spec to modify."); - ErrorUtilities.VerifyThrow(modifier != null, "Need modifier to apply to item-spec."); - - string modifiedItemSpec = null; - - try - { - if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.FullPath, StringComparison.OrdinalIgnoreCase)) - { - if (fullPath != null) - { - return fullPath; - } - - currentDirectory ??= string.Empty; - - modifiedItemSpec = GetFullPath(itemSpec, currentDirectory); - fullPath = modifiedItemSpec; - - ThrowForUrl(modifiedItemSpec, itemSpec, currentDirectory); - } - else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.RootDir, StringComparison.OrdinalIgnoreCase)) - { - GetItemSpecModifier(currentDirectory, itemSpec, definingProjectEscaped, ItemSpecModifiers.FullPath, ref fullPath); - - modifiedItemSpec = Path.GetPathRoot(fullPath); - - if (!EndsWithSlash(modifiedItemSpec)) - { - ErrorUtilities.VerifyThrow( - StartsWithUncPattern(modifiedItemSpec), - "Only UNC shares should be missing trailing slashes."); - - // restore/append trailing slash if Path.GetPathRoot() has either removed it, or failed to add it - // (this happens with UNC shares) - modifiedItemSpec += Path.DirectorySeparatorChar; - } - } - else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.Filename, StringComparison.OrdinalIgnoreCase)) - { - // if the item-spec is a root directory, it can have no filename - if (IsRootDirectory(itemSpec)) - { - // NOTE: this is to prevent Path.GetFileNameWithoutExtension() from treating server and share elements - // in a UNC file-spec as filenames e.g. \\server, \\server\share - modifiedItemSpec = String.Empty; - } - else - { - // Fix path to avoid problem with Path.GetFileNameWithoutExtension when backslashes in itemSpec on Unix - modifiedItemSpec = Path.GetFileNameWithoutExtension(itemSpec); - } - } - else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.Extension, StringComparison.OrdinalIgnoreCase)) - { - // if the item-spec is a root directory, it can have no extension - if (IsRootDirectory(itemSpec)) - { - // NOTE: this is to prevent Path.GetExtension() from treating server and share elements in a UNC - // file-spec as filenames e.g. \\server.ext, \\server\share.ext - modifiedItemSpec = String.Empty; - } - else - { - modifiedItemSpec = Path.GetExtension(itemSpec); - } - } - else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.RelativeDir, StringComparison.OrdinalIgnoreCase)) - { - modifiedItemSpec = GetDirectory(itemSpec); - } - else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.Directory, StringComparison.OrdinalIgnoreCase)) - { - GetItemSpecModifier(currentDirectory, itemSpec, definingProjectEscaped, ItemSpecModifiers.FullPath, ref fullPath); - - modifiedItemSpec = GetDirectory(fullPath); - - int length = StartsWithDrivePattern(modifiedItemSpec) - ? 2 - : StartsWithUncPatternMatchLength(modifiedItemSpec); - - if (length != -1) - { - ErrorUtilities.VerifyThrow((modifiedItemSpec.Length > length) && IsAnySlash(modifiedItemSpec[length]), - "Root directory must have a trailing slash."); - - modifiedItemSpec = modifiedItemSpec.Substring(length + 1); - } - } - else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.RecursiveDir, StringComparison.OrdinalIgnoreCase)) - { - // only the BuildItem class can compute this modifier -- so leave empty - modifiedItemSpec = String.Empty; - } - else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.Identity, StringComparison.OrdinalIgnoreCase)) - { - modifiedItemSpec = itemSpec; - } - else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.ModifiedTime, StringComparison.OrdinalIgnoreCase)) - { - // About to go out to the filesystem. This means data is leaving the engine, so need - // to unescape first. - string unescapedItemSpec = EscapingUtilities.UnescapeAll(itemSpec); - - FileInfo info = FileUtilities.GetFileInfoNoThrow(unescapedItemSpec); - - if (info != null) - { - modifiedItemSpec = info.LastWriteTime.ToString(FileTimeFormat, null); - } - else - { - // File does not exist, or path is a directory - modifiedItemSpec = String.Empty; - } - } - else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.CreatedTime, StringComparison.OrdinalIgnoreCase)) - { - // About to go out to the filesystem. This means data is leaving the engine, so need - // to unescape first. - string unescapedItemSpec = EscapingUtilities.UnescapeAll(itemSpec); - - if (NativeMethods.FileExists(unescapedItemSpec)) - { - modifiedItemSpec = File.GetCreationTime(unescapedItemSpec).ToString(FileTimeFormat, null); - } - else - { - // File does not exist, or path is a directory - modifiedItemSpec = String.Empty; - } - } - else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.AccessedTime, StringComparison.OrdinalIgnoreCase)) - { - // About to go out to the filesystem. This means data is leaving the engine, so need - // to unescape first. - string unescapedItemSpec = EscapingUtilities.UnescapeAll(itemSpec); - - if (NativeMethods.FileExists(unescapedItemSpec)) - { - modifiedItemSpec = File.GetLastAccessTime(unescapedItemSpec).ToString(FileTimeFormat, null); - } - else - { - // File does not exist, or path is a directory - modifiedItemSpec = String.Empty; - } - } - else if (IsDefiningProjectModifier(modifier)) - { - if (String.IsNullOrEmpty(definingProjectEscaped)) - { - // We have nothing to work with, but that's sometimes OK -- so just return String.Empty - modifiedItemSpec = String.Empty; - } - else - { - if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.DefiningProjectDirectory, StringComparison.OrdinalIgnoreCase)) - { - // ItemSpecModifiers.Directory does not contain the root directory - modifiedItemSpec = Path.Combine( - GetItemSpecModifier(currentDirectory, definingProjectEscaped, null, ItemSpecModifiers.RootDir), - GetItemSpecModifier(currentDirectory, definingProjectEscaped, null, ItemSpecModifiers.Directory)); - } - else - { - string additionalModifier = null; - - if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.DefiningProjectFullPath, StringComparison.OrdinalIgnoreCase)) - { - additionalModifier = ItemSpecModifiers.FullPath; - } - else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.DefiningProjectName, StringComparison.OrdinalIgnoreCase)) - { - additionalModifier = ItemSpecModifiers.Filename; - } - else if (string.Equals(modifier, FileUtilities.ItemSpecModifiers.DefiningProjectExtension, StringComparison.OrdinalIgnoreCase)) - { - additionalModifier = ItemSpecModifiers.Extension; - } - else - { - ErrorUtilities.ThrowInternalError($"\"{modifier}\" is not a valid item-spec modifier."); - } - - modifiedItemSpec = GetItemSpecModifier(currentDirectory, definingProjectEscaped, null, additionalModifier); - } - } - } - else - { - ErrorUtilities.ThrowInternalError($"\"{modifier}\" is not a valid item-spec modifier."); - } - } - catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) - { - ErrorUtilities.ThrowInvalidOperation(SR.Shared_InvalidFilespecForTransform, modifier, itemSpec, e.Message); - } - - return modifiedItemSpec; - } - - /// - /// Indicates whether the given path is a UNC or drive pattern root directory. - /// Note: This function mimics the behavior of checking if Path.GetDirectoryName(path) == null. - /// - /// - /// - private static bool IsRootDirectory(string path) - { - // Eliminate all non-rooted paths - if (!Path.IsPathRooted(path)) - { - return false; - } - - int uncMatchLength = StartsWithUncPatternMatchLength(path); - - // Determine if the given path is a standard drive/unc pattern root - if (IsDrivePattern(path) || - IsDrivePatternWithSlash(path) || - uncMatchLength == path.Length) - { - return true; - } - - // Eliminate all non-root unc paths. - if (uncMatchLength != -1) - { - return false; - } - - // Eliminate any drive patterns that don't have a slash after the colon or where the 4th character is a non-slash - // A non-slash at [3] is specifically checked here because Path.GetDirectoryName - // considers "C:///" a valid root. - if (StartsWithDrivePattern(path) && - ((path.Length >= 3 && path[2] is not (BackSlash or ForwardSlash)) || - (path.Length >= 4 && path[3] is not (BackSlash or ForwardSlash)))) - { - return false; - } - - // There are some edge cases that can get to this point. - // After eliminating valid / invalid roots, fall back on original behavior. - return Path.GetDirectoryName(path) == null; - } - - /// - /// Temporary check for something like http://foo which will end up like c:\foo\bar\http://foo - /// We should either have no colon, or exactly one colon. - /// UNDONE: This is a minimal safe change for Dev10. The correct fix should be to make GetFullPath/NormalizePath throw for this. - /// - private static void ThrowForUrl(string fullPath, string itemSpec, string currentDirectory) - { - if (fullPath.IndexOf(':') != fullPath.LastIndexOf(':')) - { - // Cause a better error to appear - fullPath = Path.GetFullPath(Path.Combine(currentDirectory, itemSpec)); - } - } - } - } -} diff --git a/src/MSBuildTaskHost/Utilities/NativeMethods.cs b/src/MSBuildTaskHost/Utilities/NativeMethods.cs index 1ee011465d9..c0f33d48d84 100644 --- a/src/MSBuildTaskHost/Utilities/NativeMethods.cs +++ b/src/MSBuildTaskHost/Utilities/NativeMethods.cs @@ -6,8 +6,6 @@ using System.Runtime.InteropServices; using Microsoft.Win32; -#nullable disable - namespace Microsoft.Build.TaskHost.Utilities; internal static class NativeMethods @@ -21,8 +19,8 @@ internal static class NativeMethods /// private const int MAX_PATH = 260; - private const string WINDOWS_FILE_SYSTEM_REGISTRY_KEY = @"SYSTEM\CurrentControlSet\Control\FileSystem"; - private const string WINDOWS_LONG_PATHS_ENABLED_VALUE_NAME = "LongPathsEnabled"; + private const string WindowsFileSystemKeyName = @"SYSTEM\CurrentControlSet\Control\FileSystem"; + private const string LongPathsEnabledValueName = "LongPathsEnabled"; private enum LOGICAL_PROCESSOR_RELATIONSHIP { @@ -138,104 +136,40 @@ private static unsafe int GetLogicalCoreCountOnWindows() return -1; } - internal static bool HasMaxPath => MaxPath == MAX_PATH; - /// - /// Gets the max path limit of the current OS. + /// Cached value for MaxPath. /// - internal static int MaxPath - { - get - { - if (!IsMaxPathSet) - { - SetMaxPath(); - } - - return _maxPath; - } - } + private static int? s_maxPath; /// - /// Cached value for MaxPath. + /// Gets the max path limit of the current OS. /// - private static int _maxPath; - - private static bool IsMaxPathSet { get; set; } + internal static int MaxPath + => s_maxPath ??= ComputeMaxPath(); - private static readonly object MaxPathLock = new(); + private static int ComputeMaxPath() + => Traits.Instance.EscapeHatches.DisableLongPaths || !LongPathsEnabled() + ? MAX_PATH + : int.MaxValue; - private static void SetMaxPath() + private static bool LongPathsEnabled() { - lock (MaxPathLock) + try { - if (!IsMaxPathSet) + using RegistryKey? fileSystemKey = Registry.LocalMachine.OpenSubKey(WindowsFileSystemKeyName); + + if (fileSystemKey is null) { - bool isMaxPathRestricted = Traits.Instance.EscapeHatches.DisableLongPaths || IsMaxPathLegacyWindows(); - _maxPath = isMaxPathRestricted ? MAX_PATH : int.MaxValue; - IsMaxPathSet = true; + return false; } - } - } - private enum LongPathsStatus - { - /// - /// The registry key is set to 0 or does not exist. - /// - Disabled, - - /// - /// The registry key does not exist. - /// - Missing, - - /// - /// The registry key is set to 1. - /// - Enabled, - - /// - /// Not on Windows. - /// - NotApplicable, - } + int longPathsEnabledValue = (int)fileSystemKey.GetValue(LongPathsEnabledValueName, defaultValue: -1); - private static LongPathsStatus IsLongPathsEnabled() - { - try - { - return IsLongPathsEnabledRegistry(); + return longPathsEnabledValue == 1; } catch { - return LongPathsStatus.Disabled; - } - } - - private static bool IsMaxPathLegacyWindows() - { - var longPathsStatus = IsLongPathsEnabled(); - return longPathsStatus == LongPathsStatus.Disabled || longPathsStatus == LongPathsStatus.Missing; - } - - private static LongPathsStatus IsLongPathsEnabledRegistry() - { - using (RegistryKey fileSystemKey = Registry.LocalMachine.OpenSubKey(WINDOWS_FILE_SYSTEM_REGISTRY_KEY)) - { - object longPathsEnabledValue = fileSystemKey?.GetValue(WINDOWS_LONG_PATHS_ENABLED_VALUE_NAME, -1); - if (fileSystemKey != null && Convert.ToInt32(longPathsEnabledValue) == -1) - { - return LongPathsStatus.Missing; - } - else if (fileSystemKey != null && Convert.ToInt32(longPathsEnabledValue) == 1) - { - return LongPathsStatus.Enabled; - } - else - { - return LongPathsStatus.Disabled; - } + return false; } } @@ -261,26 +195,6 @@ private static void ThrowExceptionForErrorCode(int errorCode) Marshal.ThrowExceptionForHR(errorCode); } - /// - /// Internal, optimized GetCurrentDirectory implementation that simply delegates to the native method. - /// - internal static unsafe string GetCurrentDirectory() - { - int bufferSize = GetCurrentDirectoryWin32(nBufferLength: 0, lpBuffer: null); - - char* buffer = stackalloc char[bufferSize]; - int length = GetCurrentDirectoryWin32(bufferSize, buffer); - - return new string(buffer, startIndex: 0, length); - } - - private static unsafe int GetCurrentDirectoryWin32(int nBufferLength, char* lpBuffer) - { - int pathLength = GetCurrentDirectory(nBufferLength, lpBuffer); - VerifyThrowWin32Result(pathLength); - return pathLength; - } - internal static unsafe string GetFullPath(string path) { char* buffer = stackalloc char[MAX_PATH]; @@ -345,10 +259,6 @@ private static void VerifyThrowWin32Result(int result) [return: MarshalAs(UnmanagedType.Bool)] private static extern bool GetFileAttributesEx(String name, int fileInfoLevel, ref WIN32_FILE_ATTRIBUTE_DATA lpFileInformation); - [SuppressMessage("Microsoft.Usage", "CA2205:UseManagedEquivalentsOfWin32Api", Justification = "Using unmanaged equivalent for performance reasons")] - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern unsafe int GetCurrentDirectory(int nBufferLength, char* lpBuffer); - [SuppressMessage("Microsoft.Usage", "CA2205:UseManagedEquivalentsOfWin32Api", Justification = "Using unmanaged equivalent for performance reasons")] [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetCurrentDirectory")] [return: MarshalAs(UnmanagedType.Bool)] @@ -360,23 +270,10 @@ internal static bool SetCurrentDirectory(string path) [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern unsafe int GetFullPathName(string target, int bufferLength, char* buffer, IntPtr mustBeZero); - public static bool DirectoryExists(string fullPath) - { - WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); - bool success = GetFileAttributesEx(fullPath, 0, ref data); - return success && (data.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; - } - public static bool FileExists(string fullPath) { WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); bool success = GetFileAttributesEx(fullPath, 0, ref data); return success && (data.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0; } - - public static bool FileOrDirectoryExists(string path) - { - WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); - return GetFileAttributesEx(path, 0, ref data); - } } From c8c6b4b239a473c878c7c85ec38536c5b00ac081 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 6 Feb 2026 16:44:23 -0800 Subject: [PATCH 123/136] MSBuildTaskHost: Clean up StringBuilderCache - File-scoped namespace - Enable nullability --- .../Utilities/StringBuilderCache.cs | 150 +++++++++--------- 1 file changed, 73 insertions(+), 77 deletions(-) diff --git a/src/MSBuildTaskHost/Utilities/StringBuilderCache.cs b/src/MSBuildTaskHost/Utilities/StringBuilderCache.cs index dd1f231e4bd..970d132fe44 100644 --- a/src/MSBuildTaskHost/Utilities/StringBuilderCache.cs +++ b/src/MSBuildTaskHost/Utilities/StringBuilderCache.cs @@ -5,99 +5,95 @@ using System.Diagnostics; using System.Text; -#nullable disable +namespace Microsoft.Build.TaskHost.Utilities; -namespace Microsoft.Build.TaskHost.Utilities +/// +/// A cached reusable instance of StringBuilder. +/// +/// +/// An optimization that reduces the number of instances of constructed and collected. +/// +internal static class StringBuilderCache { + // The value 512 was chosen empirically as 95% percentile of returning string length. + private const int MAX_BUILDER_SIZE = 512; + + [ThreadStatic] + private static StringBuilder? t_cachedInstance; + /// - /// A cached reusable instance of StringBuilder. + /// Get a of at least the specified capacity. /// + /// The suggested starting size of this instance. + /// A that may or may not be reused. /// - /// An optimization that reduces the number of instances of constructed and collected. + /// It can be called any number of times; if a is in the cache then + /// it will be returned and the cache emptied. Subsequent calls will return a new . + /// + /// The instance is cached in Thread Local Storage and so there is one per thread. /// - internal static class StringBuilderCache + public static StringBuilder Acquire(int capacity = 16 /*StringBuilder.DefaultCapacity*/) { - // The value 512 was chosen empirically as 95% percentile of returning string length. - private const int MAX_BUILDER_SIZE = 512; - - [ThreadStatic] - private static StringBuilder t_cachedInstance; - - /// - /// Get a of at least the specified capacity. - /// - /// The suggested starting size of this instance. - /// A that may or may not be reused. - /// - /// It can be called any number of times; if a is in the cache then - /// it will be returned and the cache emptied. Subsequent calls will return a new . - /// - /// The instance is cached in Thread Local Storage and so there is one per thread. - /// - public static StringBuilder Acquire(int capacity = 16 /*StringBuilder.DefaultCapacity*/) + if (capacity <= MAX_BUILDER_SIZE) { - if (capacity <= MAX_BUILDER_SIZE) + StringBuilder? sb = t_cachedInstance; + t_cachedInstance = null; + if (sb != null) { - StringBuilder sb = StringBuilderCache.t_cachedInstance; - StringBuilderCache.t_cachedInstance = null; - if (sb != null) + // Avoid StringBuilder block fragmentation by getting a new StringBuilder + // when the requested size is larger than the current capacity + if (capacity <= sb.Capacity) { - // Avoid StringBuilder block fragmentation by getting a new StringBuilder - // when the requested size is larger than the current capacity - if (capacity <= sb.Capacity) - { - sb.Length = 0; // Equivalent of sb.Clear() that works on .Net 3.5 - return sb; - } + sb.Length = 0; // Equivalent of sb.Clear() that works on .Net 3.5 + return sb; } } - - StringBuilder stringBuilder = new StringBuilder(capacity); - return stringBuilder; } - /// - /// Place the specified builder in the cache if it is not too big. Unbalanced Releases are acceptable. - /// The StringBuilder should not be used after it has - /// been released. - /// Unbalanced Releases are perfectly acceptable.It - /// will merely cause the runtime to create a new - /// StringBuilder next time Acquire is called. - /// - /// The to cache. Likely returned from . - /// - /// The StringBuilder should not be used after it has been released. - /// - /// - /// Unbalanced Releases are perfectly acceptable.It - /// will merely cause the runtime to create a new - /// StringBuilder next time Acquire is called. - /// - /// - public static void Release(StringBuilder sb) - { - if (sb.Capacity <= MAX_BUILDER_SIZE) - { - // Assert we are not replacing another string builder. That could happen when Acquire is reentered. - // User of StringBuilderCache has to make sure that calling method call stacks do not also use StringBuilderCache. - Debug.Assert(StringBuilderCache.t_cachedInstance == null, "Unexpected replacing of other StringBuilder."); - StringBuilderCache.t_cachedInstance = sb; - } - } + return new StringBuilder(capacity); + } - /// - /// Get a string and return its builder to the cache. - /// - /// Builder to cache (if it's not too big). - /// The equivalent to 's contents. - /// - /// Convenience method equivalent to calling followed by . - /// - public static string GetStringAndRelease(StringBuilder sb) + /// + /// Place the specified builder in the cache if it is not too big. Unbalanced Releases are acceptable. + /// The StringBuilder should not be used after it has + /// been released. + /// Unbalanced Releases are perfectly acceptable.It + /// will merely cause the runtime to create a new + /// StringBuilder next time Acquire is called. + /// + /// The to cache. Likely returned from . + /// + /// The StringBuilder should not be used after it has been released. + /// + /// + /// Unbalanced Releases are perfectly acceptable.It + /// will merely cause the runtime to create a new + /// StringBuilder next time Acquire is called. + /// + /// + public static void Release(StringBuilder sb) + { + if (sb.Capacity <= MAX_BUILDER_SIZE) { - string result = sb.ToString(); - Release(sb); - return result; + // Assert we are not replacing another string builder. That could happen when Acquire is reentered. + // User of StringBuilderCache has to make sure that calling method call stacks do not also use StringBuilderCache. + Debug.Assert(t_cachedInstance == null, "Unexpected replacing of other StringBuilder."); + t_cachedInstance = sb; } } + + /// + /// Get a string and return its builder to the cache. + /// + /// Builder to cache (if it's not too big). + /// The equivalent to 's contents. + /// + /// Convenience method equivalent to calling followed by . + /// + public static string GetStringAndRelease(StringBuilder sb) + { + string result = sb.ToString(); + Release(sb); + return result; + } } From 2ff2b903a9bd86ff327eaff94348773d890fcc0d Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 9 Feb 2026 08:42:18 -0800 Subject: [PATCH 124/136] MSBuildTaskHost: Clean up CommunicationUtilities - File-scoped namespace - Expression-bodied members - Enable nullability - Remove unnecessary Trace overloads and use interpolated strings at callsites. - Always perform process sessionId calculation and cache the result, since includeSessionId is always true. - Use Dictionary<,> rather than IDictionary<,> --- .../BackEnd/NodeEndpointOutOfProcTaskHost.cs | 33 +- .../CommunicationsUtilities.cs | 1298 ++++++++--------- src/MSBuildTaskHost/OutOfProcTaskHostNode.cs | 6 +- .../Utilities/EnvironmentUtilities.cs | 15 + 4 files changed, 621 insertions(+), 731 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs b/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs index 213205ac853..b16ed9f33e9 100644 --- a/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs +++ b/src/MSBuildTaskHost/BackEnd/NodeEndpointOutOfProcTaskHost.cs @@ -218,7 +218,7 @@ public void ClientWillDisconnect() private void ChangeLinkStatus(LinkStatus newStatus) { ErrorUtilities.VerifyThrow(_status != newStatus, "Attempting to change status to existing status {0}.", _status); - CommunicationsUtilities.Trace("Changing link status from {0} to {1}", _status.ToString(), newStatus.ToString()); + CommunicationsUtilities.Trace($"Changing link status from {_status} to {newStatus}"); _status = newStatus; OnLinkStatusChanged?.Invoke(this, newStatus); } @@ -256,7 +256,7 @@ private void PacketPumpProc() { // Wait for a connection IAsyncResult resultForConnection = localPipeServer.BeginWaitForConnection(null, null); - CommunicationsUtilities.Trace("Waiting for connection {0} ms...", waitTimeRemaining); + CommunicationsUtilities.Trace($"Waiting for connection {waitTimeRemaining} ms..."); bool connected = resultForConnection.AsyncWaitHandle.WaitOne(waitTimeRemaining, false); if (!connected) { @@ -290,11 +290,7 @@ private void PacketPumpProc() if (!IsHandshakePartValid(component, result.Value)) { - CommunicationsUtilities.Trace( - "Handshake failed. Received {0} from host for {1} but expected {2}. Probably the host is a different MSBuild build.", - result.Value, - component.Key, - component.Value); + CommunicationsUtilities.Trace($"Handshake failed. Received {result.Value} from host for {component.Key} but expected {component.Value}. Probably the host is a different MSBuild build."); _pipeServer.WriteIntForHandshake(index + 1); gotValidConnection = false; break; @@ -314,7 +310,7 @@ private void PacketPumpProc() { _pipeServer.WriteIntForHandshake(Handshake.PacketVersionFromChildMarker); // Marker: PacketVersion follows _pipeServer.WriteIntForHandshake(NodePacketTypeExtensions.PacketVersion); - CommunicationsUtilities.Trace("Sent PacketVersion: {0}", NodePacketTypeExtensions.PacketVersion); + CommunicationsUtilities.Trace($"Sent PacketVersion: {NodePacketTypeExtensions.PacketVersion}"); } CommunicationsUtilities.Trace("Successfully connected to parent."); @@ -329,7 +325,8 @@ private void PacketPumpProc() if (clientIdentity == null || !string.Equals(clientIdentity.Name, currentIdentity.Name, StringComparison.OrdinalIgnoreCase)) { - CommunicationsUtilities.Trace("Handshake failed. Host user is {0} but we were created by {1}.", (clientIdentity == null) ? "" : clientIdentity.Name, currentIdentity.Name); + string clientIdentityName = clientIdentity != null ? clientIdentity.Name : ""; + CommunicationsUtilities.Trace($"Handshake failed. Host user is {clientIdentityName} but we were created by {currentIdentity.Name}."); gotValidConnection = false; continue; } @@ -343,7 +340,7 @@ private void PacketPumpProc() // and if they don't match it disconnects immediately leaving us still trying to read the blank handshake // 2. The host is too old sending us bits we automatically reject in the handshake // 3. We expected to read the EndOfHandshake signal, but we received something else - CommunicationsUtilities.Trace("Client connection failed but we will wait for another connection. Exception: {0}", e.Message); + CommunicationsUtilities.Trace($"Client connection failed but we will wait for another connection. Exception: {e.Message}"); gotValidConnection = false; } @@ -366,7 +363,7 @@ private void PacketPumpProc() } catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) { - CommunicationsUtilities.Trace("Client connection failed. Exiting comm thread. {0}", e); + CommunicationsUtilities.Trace($"Client connection failed. Exiting comm thread. {e}"); if (localPipeServer.IsConnected) { localPipeServer.Disconnect(); @@ -411,11 +408,7 @@ private static bool IsHandshakePartValid(KeyValuePair component, in return true; } - CommunicationsUtilities.Trace( - "Handshake failed. Received {0} from host for {1} but expected {2}. Probably the host is a different MSBuild build.", - handshakePart, - component.Key, - component.Value); + CommunicationsUtilities.Trace($"Handshake failed. Received {handshakePart} from host for {component.Key} but expected {component.Value}. Probably the host is a different MSBuild build."); return false; } @@ -465,7 +458,7 @@ private void RunReadLoop( catch (Exception e) { // Lost communications. Abort (but allow node reuse) - CommunicationsUtilities.Trace("Exception reading from server. {0}", e); + CommunicationsUtilities.Trace($"Exception reading from server. {e}"); ExceptionHandling.DumpExceptionToFile(e); ChangeLinkStatus(LinkStatus.Inactive); exitLoop = true; @@ -493,7 +486,7 @@ private void RunReadLoop( } else { - CommunicationsUtilities.Trace("Incomplete header read from server. {0} of {1} bytes read", bytesRead, headerByte.Length); + CommunicationsUtilities.Trace($"Incomplete header read from server. {bytesRead} of {headerByte.Length} bytes read"); ChangeLinkStatus(LinkStatus.Failed); } @@ -525,7 +518,7 @@ private void RunReadLoop( catch (Exception e) { // Error while deserializing or handling packet. Abort. - CommunicationsUtilities.Trace("Exception while deserializing packet {0}: {1}", packetType, e); + CommunicationsUtilities.Trace($"Exception while deserializing packet {packetType}: {e}"); ExceptionHandling.DumpExceptionToFile(e); ChangeLinkStatus(LinkStatus.Failed); exitLoop = true; @@ -573,7 +566,7 @@ private void RunReadLoop( catch (Exception e) { // Error while deserializing or handling packet. Abort. - CommunicationsUtilities.Trace("Exception while serializing packets: {0}", e); + CommunicationsUtilities.Trace($"Exception while serializing packets: {e}"); ExceptionHandling.DumpExceptionToFile(e); ChangeLinkStatus(LinkStatus.Failed); exitLoop = true; diff --git a/src/MSBuildTaskHost/CommunicationsUtilities.cs b/src/MSBuildTaskHost/CommunicationsUtilities.cs index ebf0610f425..ddfef8abcb5 100644 --- a/src/MSBuildTaskHost/CommunicationsUtilities.cs +++ b/src/MSBuildTaskHost/CommunicationsUtilities.cs @@ -14,871 +14,753 @@ using Microsoft.Build.TaskHost.BackEnd; using Microsoft.Build.TaskHost.Utilities; -#nullable disable +namespace Microsoft.Build.TaskHost; -namespace Microsoft.Build.TaskHost +/// +/// Enumeration of all possible (currently supported) options for handshakes. +/// +[Flags] +internal enum HandshakeOptions { + None = 0, + /// - /// Enumeration of all possible (currently supported) options for handshakes. + /// Process is a TaskHost. /// - [Flags] - internal enum HandshakeOptions - { - None = 0, - - /// - /// Process is a TaskHost. - /// - TaskHost = 1, - - /// - /// Using the 2.0 CLR. - /// - CLR2 = 2, - - /// - /// 64-bit Intel process. - /// - X64 = 4, - - /// - /// Node reuse enabled. - /// - NodeReuse = 8, - - /// - /// Building with BelowNormal priority. - /// - LowPriority = 16, - - /// - /// Building with administrator privileges. - /// - Administrator = 32, - - /// - /// Using the .NET Core/.NET 5.0+ runtime. - /// - NET = 64, - - /// - /// ARM64 process. - /// - Arm64 = 128, - - /// - /// Using a long-running sidecar TaskHost process to reduce startup overhead and reuse in-memory caches. - /// - SidecarTaskHost = 256, - } + TaskHost = 1, + + /// + /// Using the 2.0 CLR. + /// + CLR2 = 2, + + /// + /// 64-bit Intel process. + /// + X64 = 4, + + /// + /// Node reuse enabled. + /// + NodeReuse = 8, + + /// + /// Building with BelowNormal priority. + /// + LowPriority = 16, + + /// + /// Building with administrator privileges. + /// + Administrator = 32, + + /// + /// Using the .NET Core/.NET 5.0+ runtime. + /// + NET = 64, + + /// + /// ARM64 process. + /// + Arm64 = 128, + + /// + /// Using a long-running sidecar TaskHost process to reduce startup overhead and reuse in-memory caches. + /// + SidecarTaskHost = 256, +} + +/// +/// Status codes for the handshake process. +/// It aggregates return values across several functions so we use an aggregate instead of a separate class for each method. +/// +internal enum HandshakeStatus +{ + /// + /// The handshake operation completed successfully. + /// + Success = 0, + + /// + /// The other node returned a different value than expected. + /// This can happen either by attempting to connect to a wrong node type + /// (e.g., transient TaskHost trying to connect to a long-running TaskHost) + /// or by trying to connect to a node that has a different MSBuild version. + /// + VersionMismatch = 1, + + /// + /// The handshake was aborted due to connection from an old MSBuild version. + /// Occurs in TryReadInt when detecting legacy MSBuild.exe connections. + /// + OldMSBuild = 2, + + /// + /// The handshake operation timed out before completion. + /// + Timeout = 3, + + /// + /// The stream ended unexpectedly during the handshake operation. + /// Indicates an incomplete or corrupted handshake sequence. + /// + UnexpectedEndOfStream = 4, + + /// + /// The endianness (byte order) of the communicating nodes does not match. + /// Indicates an architecture compatibility issue. + /// + EndiannessMismatch = 5, + + /// + /// The handshake status is undefined or uninitialized. + /// + Undefined, +} + +/// +/// An aggregate class for passing around results of a handshake and adjacent information. +/// ErrorMessage is to propagate error messages where necessary. +/// +internal sealed class HandshakeResult +{ + /// + /// Gets the status code indicating the result of the handshake operation. + /// + public HandshakeStatus Status { get; } + + /// + /// Handshake in MSBuild is performed as passing integers back and forth. + /// This field holds the value returned from a successful handshake step. + /// + public int Value { get; } + + /// + /// Gets the error message when a handshake operation fails. + /// + public string? ErrorMessage { get; } /// - /// Status codes for the handshake process. - /// It aggregates return values across several functions so we use an aggregate instead of a separate class for each method. + /// The negotiated packet version with the child node. + /// It's needed to ensure both sides of the communication can read/write data in pipe. /// - internal enum HandshakeStatus + public byte NegotiatedPacketVersion { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The status of the handshake operation. + /// The value returned from the handshake. + /// The error message if the handshake failed. + /// The packet version from the child node. + private HandshakeResult(HandshakeStatus status, int value, string? errorMessage, byte negotiatedPacketVersion = 1) { - /// - /// The handshake operation completed successfully. - /// - Success = 0, - - /// - /// The other node returned a different value than expected. - /// This can happen either by attempting to connect to a wrong node type - /// (e.g., transient TaskHost trying to connect to a long-running TaskHost) - /// or by trying to connect to a node that has a different MSBuild version. - /// - VersionMismatch = 1, - - /// - /// The handshake was aborted due to connection from an old MSBuild version. - /// Occurs in TryReadInt when detecting legacy MSBuild.exe connections. - /// - OldMSBuild = 2, - - /// - /// The handshake operation timed out before completion. - /// - Timeout = 3, - - /// - /// The stream ended unexpectedly during the handshake operation. - /// Indicates an incomplete or corrupted handshake sequence. - /// - UnexpectedEndOfStream = 4, - - /// - /// The endianness (byte order) of the communicating nodes does not match. - /// Indicates an architecture compatibility issue. - /// - EndiannessMismatch = 5, - - /// - /// The handshake status is undefined or uninitialized. - /// - Undefined, + Status = status; + Value = value; + ErrorMessage = errorMessage; + NegotiatedPacketVersion = negotiatedPacketVersion; } /// - /// An aggregate class for passing around results of a handshake and adjacent information. - /// ErrorMessage is to propagate error messages where necessary - /// - internal class HandshakeResult + /// Creates a successful handshake result with the specified value. + /// + /// The value returned from the handshake operation. + /// The packet version received from the child node. + /// A new instance representing a successful operation. + public static HandshakeResult Success(int value = 0, byte negotiatedPacketVersion = 1) + => new(HandshakeStatus.Success, value, null, negotiatedPacketVersion); + + /// + /// Creates a failed handshake result with the specified status and error message. + /// + /// The error status code for the failure. + /// A description of the error that occurred. + /// A new instance representing a failed operation. + public static HandshakeResult Failure(HandshakeStatus status, string errorMessage) + => new(status, value: 0, errorMessage); +} + +internal sealed class Handshake +{ + /// + /// Marker indicating that the next integer in the child handshake response is the PacketVersion. + /// + public const int PacketVersionFromChildMarker = -1; + + private readonly HandshakeComponents _handshakeComponents; + + // Helper method to validate handshake option presence + internal static bool IsHandshakeOptionEnabled(HandshakeOptions hostContext, HandshakeOptions option) + => (hostContext & option) == option; + + // Source options of the handshake. + internal HandshakeOptions HandshakeOptions { get; } + + public Handshake(HandshakeOptions nodeType) { - /// - /// Gets the status code indicating the result of the handshake operation. - /// - public HandshakeStatus Status { get; } - - /// - /// Handshake in MSBuild is performed as passing integers back and forth. - /// This field holds the value returned from a successful handshake step. - /// - public int Value { get; } - - /// - /// Gets the error message when a handshake operation fails. - /// - public string ErrorMessage { get; } - - /// - /// The negotiated packet version with the child node. - /// It's needed to ensure both sides of the communication can read/write data in pipe. - /// - public byte NegotiatedPacketVersion { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The status of the handshake operation. - /// The value returned from the handshake. - /// The error message if the handshake failed. - /// The packet version from the child node. - private HandshakeResult(HandshakeStatus status, int value, string errorMessage, byte negotiatedPacketVersion = 1) - { - Status = status; - Value = value; - ErrorMessage = errorMessage; - NegotiatedPacketVersion = negotiatedPacketVersion; - } + HandshakeOptions = nodeType; + + // Build handshake options with version in upper bits + const int handshakeVersion = CommunicationsUtilities.HandshakeVersion; + int options = (int)nodeType | (handshakeVersion << 24); + CommunicationsUtilities.Trace($"Building handshake for node type {nodeType}, (version {handshakeVersion}): options {options}."); - /// - /// Creates a successful handshake result with the specified value. - /// - /// The value returned from the handshake operation. - /// The packet version received from the child node. - /// A new instance representing a successful operation. - public static HandshakeResult Success(int value = 0, byte negotiatedPacketVersion = 1) - => new(HandshakeStatus.Success, value, null, negotiatedPacketVersion); - - /// - /// Creates a failed handshake result with the specified status and error message. - /// - /// The error status code for the failure. - /// A description of the error that occurred. - /// A new instance representing a failed operation. - public static HandshakeResult Failure(HandshakeStatus status, string errorMessage) - => new(status, 0, errorMessage); + string toolsDirectory = FileUtilities.MSBuildTaskHostDirectory; + + // Calculate salt from environment and tools directory + string handshakeSalt = Environment.GetEnvironmentVariable("MSBUILDNODEHANDSHAKESALT") ?? ""; + int salt = CommunicationsUtilities.GetHashCode($"{handshakeSalt}{toolsDirectory}"); + + CommunicationsUtilities.Trace($"Handshake salt is {handshakeSalt}"); + CommunicationsUtilities.Trace($"Tools directory root is {toolsDirectory}"); + + int sessionId = EnvironmentUtilities.ProcessSessionId; + + _handshakeComponents = CreateStandardComponents(options, salt, sessionId); } - internal sealed class Handshake + private static HandshakeComponents CreateStandardComponents(int options, int salt, int sessionId) { - /// - /// Marker indicating that the next integer in the child handshake response is the PacketVersion. - /// - public const int PacketVersionFromChildMarker = -1; + var fileVersion = new Version(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion); + + return new( + options, + salt, + fileVersion.Major, + fileVersion.Minor, + fileVersion.Build, + fileVersion.Revision, + sessionId); + } - private readonly HandshakeComponents _handshakeComponents; + public HandshakeComponents RetrieveHandshakeComponents() => new( + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.Options), + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.Salt), + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionMajor), + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionMinor), + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionBuild), + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionPrivate), + CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.SessionId)); +} - // Helper method to validate handshake option presence - internal static bool IsHandshakeOptionEnabled(HandshakeOptions hostContext, HandshakeOptions option) - => (hostContext & option) == option; +/// +/// This class contains utility methods for the MSBuild engine. +/// +internal static class CommunicationsUtilities +{ + /// + /// Indicates to the NodeEndpoint that all the various parts of the Handshake have been sent. + /// + private const int EndOfHandshakeSignal = -0x2a2a2a2a; - // Source options of the handshake. - internal HandshakeOptions HandshakeOptions { get; } + /// + /// The version of the handshake. This should be updated each time the handshake structure is altered. + /// + internal const byte HandshakeVersion = 0x01; - public Handshake(HandshakeOptions nodeType, bool includeSessionId = true) - { - HandshakeOptions = nodeType; + /// + /// The timeout to connect to a node. + /// + private const int DefaultNodeConnectionTimeout = 900 * 1000; // 15 minutes; enough time that a dev will typically do another build in this time - // Build handshake options with version in upper bits - const int handshakeVersion = CommunicationsUtilities.HandshakeVersion; - var options = (int)nodeType | (handshakeVersion << 24); - CommunicationsUtilities.Trace("Building handshake for node type {0}, (version {1}): options {2}.", nodeType, handshakeVersion, options); + /// + /// Whether to trace communications. + /// + private static readonly bool s_trace = Traits.Instance.DebugNodeCommunication; - string toolsDirectory = FileUtilities.MSBuildTaskHostDirectory; + /// + /// Lock trace to ensure we are logging in serial fashion. + /// + private static readonly object s_traceLock = new(); - // Calculate salt from environment and tools directory - string handshakeSalt = Environment.GetEnvironmentVariable("MSBUILDNODEHANDSHAKESALT") ?? ""; - int salt = CommunicationsUtilities.GetHashCode($"{handshakeSalt}{toolsDirectory}"); + /// + /// Place to dump trace. + /// + private static string? s_debugDumpPath; - CommunicationsUtilities.Trace("Handshake salt is {0}", handshakeSalt); - CommunicationsUtilities.Trace("Tools directory root is {0}", toolsDirectory); + /// + /// Ticks at last time logged. + /// + private static long s_lastLoggedTicks = DateTime.UtcNow.Ticks; - // Get session ID if needed (expensive call) - int sessionId = 0; - if (includeSessionId) - { - using var currentProcess = Process.GetCurrentProcess(); - sessionId = currentProcess.SessionId; - } + /// + /// Gets or sets the node connection timeout. + /// + internal static int NodeConnectionTimeout + => GetIntegerVariableOrDefault("MSBUILDNODECONNECTIONTIMEOUT", DefaultNodeConnectionTimeout); - _handshakeComponents = CreateStandardComponents(options, salt, sessionId); - } + /// + /// Get environment block. + /// + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern unsafe char* GetEnvironmentStrings(); - private static HandshakeComponents CreateStandardComponents(int options, int salt, int sessionId) - { - var fileVersion = new Version(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion); - - return new( - options, - salt, - fileVersion.Major, - fileVersion.Minor, - fileVersion.Build, - fileVersion.Revision, - sessionId); - } + /// + /// Free environment block. + /// + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern unsafe bool FreeEnvironmentStrings(char* pStrings); - public HandshakeComponents RetrieveHandshakeComponents() => new( - CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.Options), - CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.Salt), - CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionMajor), - CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionMinor), - CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionBuild), - CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionPrivate), - CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.SessionId)); - } + /// + /// Set environment variable P/Invoke. + /// + [DllImport("kernel32.dll", EntryPoint = "SetEnvironmentVariable", SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool SetEnvironmentVariableNative(string name, string? value); /// - /// This class contains utility methods for the MSBuild engine. + /// Sets an environment variable using P/Invoke to workaround the .NET Framework BCL implementation. /// - internal static class CommunicationsUtilities + /// + /// .NET Framework implementation of SetEnvironmentVariable checks the length of the value and throws an exception if + /// it's greater than or equal to 32,767 characters. This limitation does not exist on modern Windows or .NET. + /// + internal static void SetEnvironmentVariable(string name, string? value) { - /// - /// Indicates to the NodeEndpoint that all the various parts of the Handshake have been sent. - /// - private const int EndOfHandshakeSignal = -0x2a2a2a2a; - - /// - /// The version of the handshake. This should be updated each time the handshake structure is altered. - /// - internal const byte HandshakeVersion = 0x01; - - /// - /// The timeout to connect to a node. - /// - private const int DefaultNodeConnectionTimeout = 900 * 1000; // 15 minutes; enough time that a dev will typically do another build in this time - - /// - /// Whether to trace communications - /// - private static readonly bool s_trace = Traits.Instance.DebugNodeCommunication; - - /// - /// Lock trace to ensure we are logging in serial fashion. - /// - private static readonly object s_traceLock = new(); - - /// - /// Place to dump trace - /// - private static string s_debugDumpPath; - - /// - /// Ticks at last time logged - /// - private static long s_lastLoggedTicks = DateTime.UtcNow.Ticks; - - /// - /// Gets or sets the node connection timeout. - /// - internal static int NodeConnectionTimeout + if (!SetEnvironmentVariableNative(name, value)) { - get { return GetIntegerVariableOrDefault("MSBUILDNODECONNECTIONTIMEOUT", DefaultNodeConnectionTimeout); } + throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()); } + } - /// - /// Get environment block. - /// - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern unsafe char* GetEnvironmentStrings(); - - /// - /// Free environment block. - /// - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern unsafe bool FreeEnvironmentStrings(char* pStrings); - - /// - /// Set environment variable P/Invoke. - /// - [DllImport("kernel32.dll", EntryPoint = "SetEnvironmentVariable", SetLastError = true, CharSet = CharSet.Unicode)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool SetEnvironmentVariableNative(string name, string value); - - /// - /// Sets an environment variable using P/Invoke to workaround the .NET Framework BCL implementation. - /// - /// - /// .NET Framework implementation of SetEnvironmentVariable checks the length of the value and throws an exception if - /// it's greater than or equal to 32,767 characters. This limitation does not exist on modern Windows or .NET. - /// - internal static void SetEnvironmentVariable(string name, string value) + /// + /// Returns key value pairs of environment variables in a new dictionary + /// with a case-insensitive key comparer. + /// + /// + /// Copied from the BCL implementation to eliminate some expensive security asserts on .NET Framework. + /// + internal static Dictionary GetEnvironmentVariables() + { + unsafe { - if (!SetEnvironmentVariableNative(name, value)) - { - throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()); - } - } + char* pEnvironmentBlock = null; - /// - /// Returns key value pairs of environment variables in a new dictionary - /// with a case-insensitive key comparer. - /// - /// - /// Copied from the BCL implementation to eliminate some expensive security asserts on .NET Framework. - /// - internal static Dictionary GetEnvironmentVariables() - { - unsafe + try { - char* pEnvironmentBlock = null; - - try + pEnvironmentBlock = GetEnvironmentStrings(); + if (pEnvironmentBlock == null) { - pEnvironmentBlock = GetEnvironmentStrings(); - if (pEnvironmentBlock == null) - { - throw new OutOfMemoryException(); - } - - // Search for terminating \0\0 (two unicode \0's). - char* pEnvironmentBlockEnd = pEnvironmentBlock; - while (!(*pEnvironmentBlockEnd == '\0' && *(pEnvironmentBlockEnd + 1) == '\0')) - { - pEnvironmentBlockEnd++; - } - long stringBlockLength = pEnvironmentBlockEnd - pEnvironmentBlock; - - Dictionary table = new(200, StringComparer.OrdinalIgnoreCase); // Razzle has 150 environment variables - - // Copy strings out, parsing into pairs and inserting into the table. - // The first few environment variable entries start with an '='! - // The current working directory of every drive (except for those drives - // you haven't cd'ed into in your DOS window) are stored in the - // environment block (as =C:=pwd) and the program's exit code is - // as well (=ExitCode=00000000) Skip all that start with =. - // Read docs about Environment Blocks on MSDN's CreateProcess page. - - // Format for GetEnvironmentStrings is: - // (=HiddenVar=value\0 | Variable=value\0)* \0 - // See the description of Environment Blocks in MSDN's - // CreateProcess page (null-terminated array of null-terminated strings). - // Note the =HiddenVar's aren't always at the beginning. - for (int i = 0; i < stringBlockLength; i++) - { - int startKey = i; + throw new OutOfMemoryException(); + } - // Skip to key - // On some old OS, the environment block can be corrupted. - // Some lines will not have '=', so we need to check for '\0'. - while (*(pEnvironmentBlock + i) != '=' && *(pEnvironmentBlock + i) != '\0') - { - i++; - } + // Search for terminating \0\0 (two unicode \0's). + char* pEnvironmentBlockEnd = pEnvironmentBlock; + while (!(*pEnvironmentBlockEnd == '\0' && *(pEnvironmentBlockEnd + 1) == '\0')) + { + pEnvironmentBlockEnd++; + } - if (*(pEnvironmentBlock + i) == '\0') - { - continue; - } + long stringBlockLength = pEnvironmentBlockEnd - pEnvironmentBlock; - // Skip over environment variables starting with '=' - if (i - startKey == 0) - { - while (*(pEnvironmentBlock + i) != 0) - { - i++; - } + Dictionary table = new(capacity: 200, StringComparer.OrdinalIgnoreCase); // Razzle has 150 environment variables - continue; - } + // Copy strings out, parsing into pairs and inserting into the table. + // The first few environment variable entries start with an '='! + // The current working directory of every drive (except for those drives + // you haven't cd'ed into in your DOS window) are stored in the + // environment block (as =C:=pwd) and the program's exit code is + // as well (=ExitCode=00000000) Skip all that start with =. + // Read docs about Environment Blocks on MSDN's CreateProcess page. - string key = new string(pEnvironmentBlock, startKey, i - startKey); + // Format for GetEnvironmentStrings is: + // (=HiddenVar=value\0 | Variable=value\0)* \0 + // See the description of Environment Blocks in MSDN's + // CreateProcess page (null-terminated array of null-terminated strings). + // Note the =HiddenVar's aren't always at the beginning. + for (int i = 0; i < stringBlockLength; i++) + { + int startKey = i; + // Skip to key + // On some old OS, the environment block can be corrupted. + // Some lines will not have '=', so we need to check for '\0'. + while (*(pEnvironmentBlock + i) is not '=' and not '\0') + { i++; + } - // skip over '=' - int startValue = i; + if (*(pEnvironmentBlock + i) == '\0') + { + continue; + } + // Skip over environment variables starting with '=' + if (i - startKey == 0) + { while (*(pEnvironmentBlock + i) != 0) { - // Read to end of this entry i++; } - string value = new string(pEnvironmentBlock, startValue, i - startValue); - - // skip over 0 handled by for loop's i++ - table[key] = value; + continue; } - return table; - } - finally - { - if (pEnvironmentBlock != null) - { - FreeEnvironmentStrings(pEnvironmentBlock); - } - } - } - } + string key = new(pEnvironmentBlock, startKey, i - startKey); - /// - /// Updates the environment to match the provided dictionary. - /// - internal static void SetEnvironment(IDictionary newEnvironment) - { - if (newEnvironment != null) - { - // First, delete all no longer set variables - IDictionary currentEnvironment = GetEnvironmentVariables(); - foreach (KeyValuePair entry in currentEnvironment) - { - if (!newEnvironment.ContainsKey(entry.Key)) + i++; + + // skip over '=' + int startValue = i; + + while (*(pEnvironmentBlock + i) != 0) { - SetEnvironmentVariable(entry.Key, null); + // Read to end of this entry + i++; } + + string value = new(pEnvironmentBlock, startValue, i - startValue); + + // skip over 0 handled by for loop's i++ + table[key] = value; } - // Then, make sure the new ones have their new values. - foreach (KeyValuePair entry in newEnvironment) + return table; + } + finally + { + if (pEnvironmentBlock != null) { - if (!currentEnvironment.TryGetValue(entry.Key, out string currentValue) || currentValue != entry.Value) - { - SetEnvironmentVariable(entry.Key, entry.Value); - } + FreeEnvironmentStrings(pEnvironmentBlock); } } } + } -#nullable enable - /// - /// Indicate to the client that all elements of the Handshake have been sent. - /// - internal static void WriteEndOfHandshakeSignal(this PipeStream stream) + /// + /// Updates the environment to match the provided dictionary. + /// + internal static void SetEnvironment(Dictionary? newEnvironment) + { + if (newEnvironment == null) { - stream.WriteIntForHandshake(EndOfHandshakeSignal); + return; } - /// - /// Extension method to write a series of bytes to a stream - /// - internal static void WriteIntForHandshake(this PipeStream stream, int value) + // First, delete all no longer set variables + Dictionary currentEnvironment = GetEnvironmentVariables(); + foreach (KeyValuePair entry in currentEnvironment) { - byte[] bytes = BitConverter.GetBytes(value); - - // We want to read the long and send it from left to right (this means big endian) - // if we are little endian we need to reverse the array to keep the left to right reading - if (BitConverter.IsLittleEndian) + if (!newEnvironment.ContainsKey(entry.Key)) { - Array.Reverse(bytes); + SetEnvironmentVariable(entry.Key, null); } - - ErrorUtilities.VerifyThrow(bytes.Length == 4, "Int should be 4 bytes"); - - stream.Write(bytes, 0, bytes.Length); } - internal static bool TryReadEndOfHandshakeSignal( - this PipeStream stream, - bool isProvider, - out HandshakeResult result) + // Then, make sure the new ones have their new values. + foreach (KeyValuePair entry in newEnvironment) { - // Accept only the first byte of the EndOfHandshakeSignal - if (stream.TryReadIntForHandshake( - byteToAccept: null, - out HandshakeResult innerResult)) + if (!currentEnvironment.TryGetValue(entry.Key, out string currentValue) || currentValue != entry.Value) { - byte negotiatedPacketVersion = 1; - - if (innerResult.Value != EndOfHandshakeSignal) - { - // If the received handshake part is not PacketVersionFromChildMarker it means we communicate with the host that does not support packet version negotiation. - // Fallback to the old communication validation pattern. - if (innerResult.Value != Handshake.PacketVersionFromChildMarker) - { - result = CreateVersionMismatchResult(isProvider, innerResult.Value); - return false; - } - - // We detected packet version marker, now let's read actual PacketVersion - if (!stream.TryReadIntForHandshake( - byteToAccept: null, - out HandshakeResult versionResult)) - { - result = versionResult; - return false; - } - - byte childVersion = (byte)versionResult.Value; - negotiatedPacketVersion = NodePacketTypeExtensions.GetNegotiatedPacketVersion(childVersion); - Trace("Node PacketVersion: {0}, Local: {1}, Negotiated: {2}", childVersion, NodePacketTypeExtensions.PacketVersion, negotiatedPacketVersion); + SetEnvironmentVariable(entry.Key, entry.Value); + } + } + } - if (!stream.TryReadIntForHandshake( - byteToAccept: null, - out innerResult)) - { - result = innerResult; - return false; - } + /// + /// Indicate to the client that all elements of the Handshake have been sent. + /// + internal static void WriteEndOfHandshakeSignal(this PipeStream stream) + => stream.WriteIntForHandshake(EndOfHandshakeSignal); - if (innerResult.Value != EndOfHandshakeSignal) - { - result = CreateVersionMismatchResult(isProvider, innerResult.Value); - return false; - } - } + /// + /// Extension method to write a series of bytes to a stream. + /// + internal static void WriteIntForHandshake(this PipeStream stream, int value) + { + byte[] bytes = BitConverter.GetBytes(value); - result = HandshakeResult.Success(0, negotiatedPacketVersion); - return true; - } - else - { - result = innerResult; - return false; - } + // We want to read the long and send it from left to right (this means big endian) + // if we are little endian we need to reverse the array to keep the left to right reading + if (BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); } - private static HandshakeResult CreateVersionMismatchResult(bool isProvider, int receivedValue) - { - var errorMessage = isProvider - ? $"Handshake failed on part {receivedValue}. Probably the client is a different MSBuild build." - : $"Expected end of handshake signal but received {receivedValue}. Probably the host is a different MSBuild build."; - Trace(errorMessage); + ErrorUtilities.VerifyThrow(bytes.Length == 4, "Int should be 4 bytes"); - return HandshakeResult.Failure(HandshakeStatus.VersionMismatch, errorMessage); - } + stream.Write(bytes, 0, bytes.Length); + } - /// - /// Extension method to read a series of bytes from a stream. - /// If specified, leading byte matches one in the supplied array if any, returns rejection byte and throws IOException. - /// - internal static bool TryReadIntForHandshake( - this PipeStream stream, - byte? byteToAccept, - out HandshakeResult result) + internal static bool TryReadEndOfHandshakeSignal(this PipeStream stream, bool isProvider, out HandshakeResult result) + { + // Accept only the first byte of the EndOfHandshakeSignal + if (stream.TryReadIntForHandshake(byteToAccept: null, out HandshakeResult innerResult)) { - byte[] bytes = new byte[4]; - int bytesRead = stream.Read(bytes, 0, bytes.Length); + byte negotiatedPacketVersion = 1; - // Abort for connection attempts from ancient MSBuild.exes - if (byteToAccept != null && bytesRead > 0 && byteToAccept != bytes[0]) + if (innerResult.Value != EndOfHandshakeSignal) { - stream.WriteIntForHandshake(0x0F0F0F0F); - stream.WriteIntForHandshake(0x0F0F0F0F); - result = HandshakeResult.Failure(HandshakeStatus.OldMSBuild, String.Format(CultureInfo.InvariantCulture, "Client: rejected old host. Received byte {0} instead of {1}.", bytes[0], byteToAccept)); - return false; - } + // If the received handshake part is not PacketVersionFromChildMarker it means we communicate with the host that does not support packet version negotiation. + // Fallback to the old communication validation pattern. + if (innerResult.Value != Handshake.PacketVersionFromChildMarker) + { + result = CreateVersionMismatchResult(isProvider, innerResult.Value); + return false; + } - if (bytesRead != bytes.Length) - { - // We've unexpectly reached end of stream. - // We are now in a bad state, disconnect on our end - result = HandshakeResult.Failure(HandshakeStatus.UnexpectedEndOfStream, String.Format(CultureInfo.InvariantCulture, "Unexpected end of stream while reading for handshake")); + // We detected packet version marker, now let's read actual PacketVersion + if (!stream.TryReadIntForHandshake(byteToAccept: null, out HandshakeResult versionResult)) + { + result = versionResult; + return false; + } - return false; - } + byte childVersion = (byte)versionResult.Value; + negotiatedPacketVersion = NodePacketTypeExtensions.GetNegotiatedPacketVersion(childVersion); + Trace($"Node PacketVersion: {childVersion}, Local: {NodePacketTypeExtensions.PacketVersion}, Negotiated: {negotiatedPacketVersion}"); - try - { - // We want to read the long and send it from left to right (this means big endian) - // If we are little endian the stream has already been reversed by the sender, we need to reverse it again to get the original number - if (BitConverter.IsLittleEndian) + if (!stream.TryReadIntForHandshake(byteToAccept: null, out innerResult)) { - Array.Reverse(bytes); + result = innerResult; + return false; } - result = HandshakeResult.Success(BitConverter.ToInt32(bytes, 0 /* start index */)); - } - catch (ArgumentException ex) - { - result = HandshakeResult.Failure(HandshakeStatus.EndiannessMismatch, String.Format(CultureInfo.InvariantCulture, "Failed to convert the handshake to big-endian. {0}", ex.Message)); - return false; + if (innerResult.Value != EndOfHandshakeSignal) + { + result = CreateVersionMismatchResult(isProvider, innerResult.Value); + return false; + } } + result = HandshakeResult.Success(0, negotiatedPacketVersion); return true; } -#nullable disable - - /// - /// Given the appropriate information, return the equivalent HandshakeOptions. - /// - internal static HandshakeOptions GetHandshakeOptions() + else { - // For MSBuildTaskHost, the HandshakeOptions are easy to compute. - HandshakeOptions options = HandshakeOptions.TaskHost; + result = innerResult; + return false; + } + } - options |= HandshakeOptions.CLR2; + private static HandshakeResult CreateVersionMismatchResult(bool isProvider, int receivedValue) + { + string errorMessage = isProvider + ? $"Handshake failed on part {receivedValue}. Probably the client is a different MSBuild build." + : $"Expected end of handshake signal but received {receivedValue}. Probably the host is a different MSBuild build."; - if (NativeMethods.Is64Bit) - { - options |= HandshakeOptions.X64; - } + Trace(errorMessage); - // If we are running in elevated privs, we will only accept a handshake from an elevated process as well. - // Both the client and the host will calculate this separately, and the idea is that if they come out the same - // then we can be sufficiently confident that the other side has the same elevation level as us. This is complementary - // to the username check which is also done on connection. - if (new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) - { - options |= HandshakeOptions.Administrator; - } + return HandshakeResult.Failure(HandshakeStatus.VersionMismatch, errorMessage); + } - return options; - } + /// + /// Extension method to read a series of bytes from a stream. + /// If specified, leading byte matches one in the supplied array if any, returns rejection byte and throws IOException. + /// + internal static bool TryReadIntForHandshake(this PipeStream stream, byte? byteToAccept, out HandshakeResult result) + { + byte[] bytes = new byte[4]; + int bytesRead = stream.Read(bytes, 0, bytes.Length); - /// - /// Gets the value of an integer environment variable, or returns the default if none is set or it cannot be converted. - /// - internal static int GetIntegerVariableOrDefault(string environmentVariable, int defaultValue) + // Abort for connection attempts from ancient MSBuild.exes + if (byteToAccept != null && bytesRead > 0 && byteToAccept != bytes[0]) { - string environmentValue = Environment.GetEnvironmentVariable(environmentVariable); - if (String.IsNullOrEmpty(environmentValue)) - { - return defaultValue; - } - - int localDefaultValue; - if (Int32.TryParse(environmentValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out localDefaultValue)) - { - defaultValue = localDefaultValue; - } - - return defaultValue; + stream.WriteIntForHandshake(0x0F0F0F0F); + stream.WriteIntForHandshake(0x0F0F0F0F); + result = HandshakeResult.Failure(HandshakeStatus.OldMSBuild, string.Format(CultureInfo.InvariantCulture, "Client: rejected old host. Received byte {0} instead of {1}.", bytes[0], byteToAccept)); + return false; } - /// - /// Writes trace information to a log file - /// - internal static void Trace(string format, T arg0) + if (bytesRead != bytes.Length) { - Trace(nodeId: -1, format, arg0); + // We've unexpectedly reached end of stream. + // We are now in a bad state, disconnect on our end + result = HandshakeResult.Failure(HandshakeStatus.UnexpectedEndOfStream, "Unexpected end of stream while reading for handshake"); + + return false; } - /// - /// Writes trace information to a log file - /// - internal static void Trace(int nodeId, string format, T arg0) + try { - if (s_trace) + // We want to read the long and send it from left to right (this means big endian) + // If we are little endian the stream has already been reversed by the sender, we need to reverse it again to get the original number + if (BitConverter.IsLittleEndian) { - TraceCore(nodeId, string.Format(format, arg0)); + Array.Reverse(bytes); } - } - /// - /// Writes trace information to a log file - /// - internal static void Trace(string format, T0 arg0, T1 arg1) + result = HandshakeResult.Success(BitConverter.ToInt32(bytes, startIndex: 0)); + } + catch (ArgumentException ex) { - Trace(nodeId: -1, format, arg0, arg1); + result = HandshakeResult.Failure(HandshakeStatus.EndiannessMismatch, $"Failed to convert the handshake to big-endian. {ex.Message}"); + return false; } - /// - /// Writes trace information to a log file - /// - internal static void Trace(int nodeId, string format, T0 arg0, T1 arg1) + return true; + } + + /// + /// Given the appropriate information, return the equivalent HandshakeOptions. + /// + internal static HandshakeOptions GetHandshakeOptions() + { + // For MSBuildTaskHost, the HandshakeOptions are easy to compute. + HandshakeOptions options = HandshakeOptions.TaskHost; + + options |= HandshakeOptions.CLR2; + + if (NativeMethods.Is64Bit) { - if (s_trace) - { - TraceCore(nodeId, string.Format(format, arg0, arg1)); - } + options |= HandshakeOptions.X64; } - /// - /// Writes trace information to a log file - /// - internal static void Trace(string format, T0 arg0, T1 arg1, T2 arg2) + // If we are running in elevated privs, we will only accept a handshake from an elevated process as well. + // Both the client and the host will calculate this separately, and the idea is that if they come out the same + // then we can be sufficiently confident that the other side has the same elevation level as us. This is complementary + // to the username check which is also done on connection. + if (new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) { - Trace(nodeId: -1, format, arg0, arg1, arg2); + options |= HandshakeOptions.Administrator; } - /// - /// Writes trace information to a log file - /// - internal static void Trace(int nodeId, string format, T0 arg0, T1 arg1, T2 arg2) + return options; + } + + /// + /// Gets the value of an integer environment variable, or returns the default if none is set or it cannot be converted. + /// + internal static int GetIntegerVariableOrDefault(string environmentVariable, int defaultValue) + { + string environmentValue = Environment.GetEnvironmentVariable(environmentVariable); + + if (string.IsNullOrEmpty(environmentValue) || + !int.TryParse(environmentValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out int value)) { - if (s_trace) - { - TraceCore(nodeId, string.Format(format, arg0, arg1, arg2)); - } + return defaultValue; } - /// - /// Writes trace information to a log file - /// - internal static void Trace(string format, params object[] args) + return value; + } + + /// + /// Writes trace information to a log file. + /// + internal static void Trace(string message) + { + if (!s_trace) { - Trace(nodeId: -1, format, args); + return; } - /// - /// Writes trace information to a log file - /// - internal static void Trace(int nodeId, string format, params object[] args) + lock (s_traceLock) { - if (s_trace) + s_debugDumpPath ??= Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); + + if (string.IsNullOrEmpty(s_debugDumpPath)) { - string message = string.Format(CultureInfo.CurrentCulture, format, args); - TraceCore(nodeId, message); + s_debugDumpPath = FileUtilities.TempFileDirectory; } - } - - internal static void Trace(int nodeId, string message) - { - if (s_trace) + else { - TraceCore(nodeId, message); + Directory.CreateDirectory(s_debugDumpPath); } - } - /// - /// Writes trace information to a log file - /// - private static void TraceCore(int nodeId, string message) - { - lock (s_traceLock) + try { - s_debugDumpPath ??= Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); + string fileName = $"MSBuild_CommTrace_PID_{EnvironmentUtilities.CurrentProcessId}.txt"; + string filePath = Path.Combine(s_debugDumpPath, fileName); - if (string.IsNullOrEmpty(s_debugDumpPath)) + using (StreamWriter file = FileUtilities.CreateWriterForAppend(filePath)) { - s_debugDumpPath = FileUtilities.TempFileDirectory; - } - else - { - Directory.CreateDirectory(s_debugDumpPath); - } - - try - { - string fileName = nodeId != -1 - ? $"MSBuild_CommTrace_PID_{EnvironmentUtilities.CurrentProcessId}_node_{nodeId}.txt" - : $"MSBuild_CommTrace_PID_{EnvironmentUtilities.CurrentProcessId}.txt"; - - string filePath = Path.Combine(s_debugDumpPath, fileName); - - using (StreamWriter file = FileUtilities.CreateWriterForAppend(filePath)) - { - long now = DateTime.UtcNow.Ticks; - float millisecondsSinceLastLog = (float)(now - s_lastLoggedTicks) / 10000L; - s_lastLoggedTicks = now; - file.WriteLine("{0} (TID {1}) {2,15} +{3,10}ms: {4}", Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId, now, millisecondsSinceLastLog, message); - } - } - catch (IOException) - { - // Ignore + long now = DateTime.UtcNow.Ticks; + float millisecondsSinceLastLog = (float)(now - s_lastLoggedTicks) / 10000L; + s_lastLoggedTicks = now; + + file.WriteLine( + "{0} (TID {1}) {2,15} +{3,10}ms: {4}", + Thread.CurrentThread.Name, + Thread.CurrentThread.ManagedThreadId, + now, + millisecondsSinceLastLog, + message); } } + catch (IOException) + { + // Ignore + } } + } - /// - /// Gets a hash code for this string. If strings A and B are such that A.Equals(B), then - /// they will return the same hash code. - /// This is as implemented in CLR String.GetHashCode() [ndp\clr\src\BCL\system\String.cs] - /// but stripped out architecture specific defines - /// that causes the hashcode to be different and this causes problem in cross-architecture handshaking. - /// - internal static int GetHashCode(string fileVersion) + /// + /// Gets a hash code for this string. If strings A and B are such that A.Equals(B), then + /// they will return the same hash code. + /// This is as implemented in CLR String.GetHashCode() [ndp\clr\src\BCL\system\String.cs] + /// but stripped out architecture specific defines + /// that causes the hashcode to be different and this causes problem in cross-architecture handshaking. + /// + internal static int GetHashCode(string fileVersion) + { + unsafe { - unsafe + fixed (char* src = fileVersion) { - fixed (char* src = fileVersion) - { - int hash1 = (5381 << 16) + 5381; - int hash2 = hash1; + int hash1 = (5381 << 16) + 5381; + int hash2 = hash1; - int* pint = (int*)src; - int len = fileVersion.Length; - while (len > 0) + int* pint = (int*)src; + int len = fileVersion.Length; + while (len > 0) + { + hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0]; + if (len <= 2) { - hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0]; - if (len <= 2) - { - break; - } - - hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ pint[1]; - pint += 2; - len -= 4; + break; } - return hash1 + (hash2 * 1566083941); + hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ pint[1]; + pint += 2; + len -= 4; } + + return hash1 + (hash2 * 1566083941); } } - - internal static int AvoidEndOfHandshakeSignal(int x) => x == EndOfHandshakeSignal ? ~x : x; } - /// - /// Represents the components of a handshake in a structured format with named fields. - /// - internal readonly struct HandshakeComponents - { - private readonly int options; - private readonly int salt; - private readonly int fileVersionMajor; - private readonly int fileVersionMinor; - private readonly int fileVersionBuild; - private readonly int fileVersionPrivate; - private readonly int sessionId; - - public HandshakeComponents(int options, int salt, int fileVersionMajor, int fileVersionMinor, int fileVersionBuild, int fileVersionPrivate, int sessionId) - { - this.options = options; - this.salt = salt; - this.fileVersionMajor = fileVersionMajor; - this.fileVersionMinor = fileVersionMinor; - this.fileVersionBuild = fileVersionBuild; - this.fileVersionPrivate = fileVersionPrivate; - this.sessionId = sessionId; - } - - public HandshakeComponents(int options, int salt, int fileVersionMajor, int fileVersionMinor, int fileVersionBuild, int fileVersionPrivate) - : this(options, salt, fileVersionMajor, fileVersionMinor, fileVersionBuild, fileVersionPrivate, 0) - { - } + internal static int AvoidEndOfHandshakeSignal(int x) + => x == EndOfHandshakeSignal ? ~x : x; +} - public int Options => options; +/// +/// Represents the components of a handshake in a structured format with named fields. +/// +internal readonly struct HandshakeComponents( + int options, + int salt, + int fileVersionMajor, + int fileVersionMinor, + int fileVersionBuild, + int fileVersionPrivate, + int sessionId) +{ + public int Options => options; - public int Salt => salt; + public int Salt => salt; - public int FileVersionMajor => fileVersionMajor; + public int FileVersionMajor => fileVersionMajor; - public int FileVersionMinor => fileVersionMinor; + public int FileVersionMinor => fileVersionMinor; - public int FileVersionBuild => fileVersionBuild; + public int FileVersionBuild => fileVersionBuild; - public int FileVersionPrivate => fileVersionPrivate; + public int FileVersionPrivate => fileVersionPrivate; - public int SessionId => sessionId; + public int SessionId => sessionId; - public IEnumerable> EnumerateComponents() - { - yield return new KeyValuePair(nameof(Options), Options); - yield return new KeyValuePair(nameof(Salt), Salt); - yield return new KeyValuePair(nameof(FileVersionMajor), FileVersionMajor); - yield return new KeyValuePair(nameof(FileVersionMinor), FileVersionMinor); - yield return new KeyValuePair(nameof(FileVersionBuild), FileVersionBuild); - yield return new KeyValuePair(nameof(FileVersionPrivate), FileVersionPrivate); - yield return new KeyValuePair(nameof(SessionId), SessionId); - } - - public override string ToString() => $"{options} {salt} {fileVersionMajor} {fileVersionMinor} {fileVersionBuild} {fileVersionPrivate} {sessionId}"; + public IEnumerable> EnumerateComponents() + { + yield return new KeyValuePair(nameof(Options), Options); + yield return new KeyValuePair(nameof(Salt), Salt); + yield return new KeyValuePair(nameof(FileVersionMajor), FileVersionMajor); + yield return new KeyValuePair(nameof(FileVersionMinor), FileVersionMinor); + yield return new KeyValuePair(nameof(FileVersionBuild), FileVersionBuild); + yield return new KeyValuePair(nameof(FileVersionPrivate), FileVersionPrivate); + yield return new KeyValuePair(nameof(SessionId), SessionId); } + + public override string ToString() + => $"{options} {salt} {fileVersionMajor} {fileVersionMinor} {fileVersionBuild} {fileVersionPrivate} {sessionId}"; } diff --git a/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs index a1c471a450d..aba1874ef95 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs @@ -76,7 +76,7 @@ internal class OutOfProcTaskHostNode : MarshalByRefObject, INodePacketFactory, I /// /// The saved environment for the process. /// - private IDictionary _savedEnvironment; + private Dictionary _savedEnvironment; /// /// The event which is set when we should shut down. @@ -734,10 +734,10 @@ private void RunTask(object state) /// environment somewhat to account for expected environment differences between, /// e.g. parent processes and task hosts of different bitnesses. /// - private void SetTaskHostEnvironment(IDictionary environment) + private void SetTaskHostEnvironment(Dictionary environment) { ErrorUtilities.VerifyThrowInternalNull(s_mismatchedEnvironmentValues, "mismatchedEnvironmentValues"); - IDictionary updatedEnvironment = null; + Dictionary updatedEnvironment = null; if (_updateEnvironment) { diff --git a/src/MSBuildTaskHost/Utilities/EnvironmentUtilities.cs b/src/MSBuildTaskHost/Utilities/EnvironmentUtilities.cs index ef04d7441ed..bd2ef36d277 100644 --- a/src/MSBuildTaskHost/Utilities/EnvironmentUtilities.cs +++ b/src/MSBuildTaskHost/Utilities/EnvironmentUtilities.cs @@ -10,6 +10,7 @@ internal static partial class EnvironmentUtilities { private static volatile int s_processId; private static volatile string? s_processPath; + private static int? s_processSessionId; /// Gets the unique identifier for the current process. public static int CurrentProcessId @@ -57,4 +58,18 @@ public static string? ProcessPath return (processPath?.Length != 0) ? processPath : null; } } + + public static int ProcessSessionId + { + get + { + return s_processSessionId ??= GetProcessSessionId(); + + static int GetProcessSessionId() + { + using Process currentProcess = Process.GetCurrentProcess(); + return currentProcess.SessionId; + } + } + } } From d81c65feb59e86bc9f662aed169d98d56c2bc78f Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 9 Feb 2026 08:44:37 -0800 Subject: [PATCH 125/136] MSBuildTaskHost: Clean up LoadedType - File-scoped namespace --- src/MSBuildTaskHost/LoadedType.cs | 95 +++++++++++++++---------------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/src/MSBuildTaskHost/LoadedType.cs b/src/MSBuildTaskHost/LoadedType.cs index 2ef10ee0c51..456193f1887 100644 --- a/src/MSBuildTaskHost/LoadedType.cs +++ b/src/MSBuildTaskHost/LoadedType.cs @@ -6,67 +6,66 @@ using Microsoft.Build.Framework; using Microsoft.Build.TaskHost.Utilities; -namespace Microsoft.Build.TaskHost +namespace Microsoft.Build.TaskHost; + +/// +/// This class packages information about a type loaded from an assembly: for example, +/// the GenerateResource task class type or the ConsoleLogger logger class type. +/// +internal sealed class LoadedType { /// - /// This class packages information about a type loaded from an assembly: for example, - /// the GenerateResource task class type or the ConsoleLogger logger class type. + /// Creates an instance of this class for the given type. /// - internal sealed class LoadedType + /// The Type to be loaded + /// The assembly file path used to load the assembly. + /// The assembly which has been loaded, if any. + internal LoadedType(Type type, string assemblyFilePath, Assembly loadedAssembly) { - /// - /// Creates an instance of this class for the given type. - /// - /// The Type to be loaded - /// The assembly file path used to load the assembly - /// The assembly which has been loaded, if any - internal LoadedType(Type type, string assemblyFilePath, Assembly loadedAssembly) - { - ErrorUtilities.VerifyThrow(type != null, "We must have the type."); - ErrorUtilities.VerifyThrow(assemblyFilePath != null, "We must have the assembly file path the type was loaded from."); - ErrorUtilities.VerifyThrow(loadedAssembly is not null, "The assembly should always be loaded even if only by MetadataLoadContext."); + ErrorUtilities.VerifyThrow(type != null, "We must have the type."); + ErrorUtilities.VerifyThrow(assemblyFilePath != null, "We must have the assembly file path the type was loaded from."); + ErrorUtilities.VerifyThrow(loadedAssembly is not null, "The assembly should always be loaded even if only by MetadataLoadContext."); - Type = type; - AssemblyFilePath = assemblyFilePath; + Type = type; + AssemblyFilePath = assemblyFilePath; - LoadedAssemblyName = loadedAssembly.GetName(); + LoadedAssemblyName = loadedAssembly.GetName(); - // For inline tasks loaded from bytes, Assembly.Location is empty, so use the original path - Path = string.IsNullOrEmpty(loadedAssembly.Location) - ? assemblyFilePath - : loadedAssembly.Location; + // For inline tasks loaded from bytes, Assembly.Location is empty, so use the original path + Path = string.IsNullOrEmpty(loadedAssembly.Location) + ? assemblyFilePath + : loadedAssembly.Location; - LoadedAssembly = loadedAssembly; - HasLoadInSeparateAppDomainAttribute = Type.IsDefined(typeof(LoadInSeparateAppDomainAttribute), inherit: true); - IsMarshalByRef = Type.IsMarshalByRef; - } + LoadedAssembly = loadedAssembly; + HasLoadInSeparateAppDomainAttribute = Type.IsDefined(typeof(LoadInSeparateAppDomainAttribute), inherit: true); + IsMarshalByRef = Type.IsMarshalByRef; + } - /// - /// Gets whether there's a LoadInSeparateAppDomain attribute on this type. - /// - public bool HasLoadInSeparateAppDomainAttribute { get; } + /// + /// Gets whether there's a LoadInSeparateAppDomain attribute on this type. + /// + public bool HasLoadInSeparateAppDomainAttribute { get; } - /// - /// Gets whether this type implements MarshalByRefObject. - /// - public bool IsMarshalByRef { get; } + /// + /// Gets whether this type implements MarshalByRefObject. + /// + public bool IsMarshalByRef { get; } - /// - /// Gets the type that was loaded from an assembly. - /// - /// The loaded type. - internal Type Type { get; } + /// + /// Gets the type that was loaded from an assembly. + /// + /// The loaded type. + internal Type Type { get; } - internal AssemblyName LoadedAssemblyName { get; } + internal AssemblyName LoadedAssemblyName { get; } - internal string Path { get; } + internal string Path { get; } - /// - /// If we loaded an assembly for this type. - /// We use this information to help created AppDomains to resolve types that it could not load successfully - /// - internal Assembly LoadedAssembly { get; } + /// + /// If we loaded an assembly for this type. + /// We use this information to help created AppDomains to resolve types that it could not load successfully. + /// + internal Assembly LoadedAssembly { get; } - internal string AssemblyFilePath { get; } - } + internal string AssemblyFilePath { get; } } From a6c1e0a8afd7ca76b78d20287313957eb4b5e2df Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 9 Feb 2026 08:53:50 -0800 Subject: [PATCH 126/136] MSBuildTaskHost: Clean up OutOfProcTaskHost - File-scoped namespace - Expression-bodied members - Enable nullability - Add note about STA vs. MTA. --- src/MSBuildTaskHost/OutOfProcTaskHost.cs | 199 +++++++++++------------ 1 file changed, 99 insertions(+), 100 deletions(-) diff --git a/src/MSBuildTaskHost/OutOfProcTaskHost.cs b/src/MSBuildTaskHost/OutOfProcTaskHost.cs index c6f4d280f06..3a58f68770b 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskHost.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskHost.cs @@ -7,128 +7,127 @@ using Microsoft.Build.TaskHost.Exceptions; using Microsoft.Build.TaskHost.Utilities; -#nullable disable - -namespace Microsoft.Build.TaskHost +namespace Microsoft.Build.TaskHost; + +/// +/// This is the Out-Of-Proc Task Host for supporting Cross-Targeting tasks. +/// +/// +/// It will be responsible for: +/// - Task execution +/// - Communicating with the MSBuildApp process, specifically the TaskHostFactory +/// (Logging messages, receiving Tasks from TaskHostFactory, sending results and other messages). +/// +public static class OutOfProcTaskHost { /// - /// This is the Out-Of-Proc Task Host for supporting Cross-Targeting tasks. + /// Enumeration of the various ways in which the MSBuildTaskHost.exe application can exit. /// - /// - /// It will be responsible for: - /// - Task execution - /// - Communicating with the MSBuildApp process, specifically the TaskHostFactory - /// (Logging messages, receiving Tasks from TaskHostFactory, sending results and other messages) - /// - public static class OutOfProcTaskHost + internal enum ExitType { /// - /// Enumeration of the various ways in which the MSBuildTaskHost.exe application can exit. + /// The application executed successfully. /// - internal enum ExitType - { - /// - /// The application executed successfully. - /// - Success, - - /// - /// We received a request from MSBuild.exe to terminate - /// - TerminateRequest, - - /// - /// A logger aborted the build. - /// - LoggerAbort, - - /// - /// A logger failed unexpectedly. - /// - LoggerFailure, - - /// - /// The Task Host Node did not terminate gracefully - /// - TaskHostNodeFailed, - - /// - /// An unexpected failure - /// - Unexpected - } + Success, /// - /// Main Entry Point + /// We received a request from MSBuild.exe to terminate. /// - /// - /// We won't execute any tasks in the main thread, so we don't need to be in an STA - /// - [MTAThread] - public static int Main() - { - int exitCode = Execute() == ExitType.Success ? 0 : 1; - return exitCode; - } + TerminateRequest, + + /// + /// A logger aborted the build. + /// + LoggerAbort, /// - /// Orchestrates the execution of the application. - /// Also responsible for top-level error handling. + /// A logger failed unexpectedly. /// - /// - /// A value of Success if the bootstrapping succeeds - /// - internal static ExitType Execute() + LoggerFailure, + + /// + /// The Task Host Node did not terminate gracefully. + /// + TaskHostNodeFailed, + + /// + /// An unexpected failure. + /// + Unexpected + } + + /// + /// Main Entry Point. + /// + /// + /// We won't execute any tasks in the main thread, so we don't need to be in an STA. + /// + // UNDONE: Setting [MTAThread] is almost certainly incorrect for MSBuildTaskHost. + // Prior to .NET Framework 4.0, all of MSBuild ran in an STA. However, the change + // that makes MSBuildTaskHost run in an MTA was made over 10 years ago. + [MTAThread] + public static int Main() + => Execute() == ExitType.Success ? 0 : 1; + + /// + /// Orchestrates the execution of the application. + /// Also responsible for top-level error handling. + /// + /// + /// A value of Success if the bootstrapping succeeds. + /// + internal static ExitType Execute() + { + switch (Environment.GetEnvironmentVariable("MSBUILDDEBUGONSTART")) { - switch (Environment.GetEnvironmentVariable("MSBUILDDEBUGONSTART")) - { + #if FEATURE_DEBUG_LAUNCH - case "1": - Debugger.Launch(); - break; + case "1": + Debugger.Launch(); + break; #endif - case "2": - // Sometimes easier to attach rather than deal with JIT prompt - Console.WriteLine($"Waiting for debugger to attach ({EnvironmentUtilities.ProcessPath} PID {EnvironmentUtilities.CurrentProcessId}). Press enter to continue..."); - Console.ReadLine(); - break; - case "3": - // Value "3" skips debugging for TaskHost processes but debugs the main MSBuild process - // This is useful when you want to debug MSBuild but not the child TaskHost processes - break; - } - - bool restart = false; - do - { - OutOfProcTaskHostNode oopTaskHostNode = new OutOfProcTaskHostNode(); - Exception taskHostShutDownException = null; - NodeEngineShutdownReason taskHostShutDownReason = oopTaskHostNode.Run(out taskHostShutDownException); + case "2": + // Sometimes easier to attach rather than deal with JIT prompt + Console.WriteLine($"Waiting for debugger to attach ({EnvironmentUtilities.ProcessPath} PID {EnvironmentUtilities.CurrentProcessId}). Press enter to continue..."); - if (taskHostShutDownException != null) - { - return ExitType.TaskHostNodeFailed; - } + Console.ReadLine(); + break; - switch (taskHostShutDownReason) - { - case NodeEngineShutdownReason.BuildComplete: - return ExitType.Success; + case "3": + // Value "3" skips debugging for TaskHost processes but debugs the main MSBuild process + // This is useful when you want to debug MSBuild but not the child TaskHost processes + break; + } - case NodeEngineShutdownReason.BuildCompleteReuse: - restart = true; - break; + bool restart = false; + do + { + var oopTaskHostNode = new OutOfProcTaskHostNode(); + NodeEngineShutdownReason taskHostShutDownReason = oopTaskHostNode.Run(out Exception? taskHostShutDownException); - default: - return ExitType.TaskHostNodeFailed; - } + if (taskHostShutDownException != null) + { + return ExitType.TaskHostNodeFailed; } - while (restart); - // Should not happen - ErrorUtilities.ThrowInternalErrorUnreachable(); - return ExitType.Unexpected; + switch (taskHostShutDownReason) + { + case NodeEngineShutdownReason.BuildComplete: + return ExitType.Success; + + case NodeEngineShutdownReason.BuildCompleteReuse: + restart = true; + break; + + default: + return ExitType.TaskHostNodeFailed; + } } + while (restart); + + // Should not happen + ErrorUtilities.ThrowInternalErrorUnreachable(); + return ExitType.Unexpected; } } From dceb52c99d65a2fb694aee3cea57cc476a03ac6f Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 9 Feb 2026 09:19:55 -0800 Subject: [PATCH 127/136] MSBuildTaskHost: Clean up OutOfProcTaskHostNode - File-scoped namespace - Expression-bodied members - Enable nullability --- .../BackEnd/TaskHostConfiguration.cs | 16 +- .../Collections/CollectionExtensions.cs | 6 +- .../CommunicationsUtilities.cs | 15 +- src/MSBuildTaskHost/OutOfProcTaskHostNode.cs | 1529 ++++++++--------- 4 files changed, 754 insertions(+), 812 deletions(-) diff --git a/src/MSBuildTaskHost/BackEnd/TaskHostConfiguration.cs b/src/MSBuildTaskHost/BackEnd/TaskHostConfiguration.cs index f532b01c7d4..d9c8172228d 100644 --- a/src/MSBuildTaskHost/BackEnd/TaskHostConfiguration.cs +++ b/src/MSBuildTaskHost/BackEnd/TaskHostConfiguration.cs @@ -196,12 +196,12 @@ private TaskHostConfiguration() /// /// Gets the startup directory. /// - public string? StartupDirectory => _startupDirectory; + public string StartupDirectory => _startupDirectory!; /// /// Gets the process environment. /// - public Dictionary? BuildProcessEnvironment => _buildProcessEnvironment; + public Dictionary BuildProcessEnvironment => _buildProcessEnvironment!; /// /// Gets the culture. @@ -217,7 +217,7 @@ private TaskHostConfiguration() /// Gets the AppDomain configuration bytes that we may want to use to initialize /// AppDomainIsolated tasks. /// - public AppDomainSetup? AppDomainSetup => _appDomainSetup; + public AppDomainSetup AppDomainSetup => _appDomainSetup!; /// /// Gets the line number where the instance of this task is defined. @@ -247,17 +247,17 @@ private TaskHostConfiguration() /// /// Gets the project file where the instance of this task is defined. /// - public string? ProjectFileOfTask => _projectFileOfTask; + public string ProjectFileOfTask => _projectFileOfTask!; /// /// Gets the name of the task to execute. /// - public string? TaskName => _taskName; + public string TaskName => _taskName!; /// /// Gets the path to the assembly to load the task from. /// - public string? TaskLocation => _taskLocation; + public string TaskLocation => _taskLocation!; /// /// Returns if the build is configured to log all task inputs. @@ -267,7 +267,7 @@ private TaskHostConfiguration() /// /// Gets the parameters to set on the instantiated task prior to execution. /// - public Dictionary? TaskParameters => _taskParameters; + public Dictionary TaskParameters => _taskParameters!; /// /// Gets the global properties for the current project. @@ -308,7 +308,7 @@ public void Translate(ITranslator translator) // Set the configuration bytes just before serialization in case the SetConfigurationBytes was invoked during lifetime of this instance. if (translator.Mode == TranslationDirection.WriteToStream) { - appDomainConfigBytes = _appDomainSetup?.GetConfigurationBytes(); + appDomainConfigBytes = _appDomainSetup!.GetConfigurationBytes(); } translator.Translate(ref appDomainConfigBytes); diff --git a/src/MSBuildTaskHost/Collections/CollectionExtensions.cs b/src/MSBuildTaskHost/Collections/CollectionExtensions.cs index 3869475f23d..fd5af45bd37 100644 --- a/src/MSBuildTaskHost/Collections/CollectionExtensions.cs +++ b/src/MSBuildTaskHost/Collections/CollectionExtensions.cs @@ -21,10 +21,10 @@ internal static class CollectionExtensions /// the specified value; otherwise, . /// internal static bool HasValue( - this Dictionary dictionary, + this Dictionary dictionary, string key, - string value, + string? value, StringComparison comparison) - => dictionary.TryGetValue(key, out string existingValue) + => dictionary.TryGetValue(key, out string? existingValue) && string.Equals(value, existingValue, comparison); } diff --git a/src/MSBuildTaskHost/CommunicationsUtilities.cs b/src/MSBuildTaskHost/CommunicationsUtilities.cs index ddfef8abcb5..0ffec614712 100644 --- a/src/MSBuildTaskHost/CommunicationsUtilities.cs +++ b/src/MSBuildTaskHost/CommunicationsUtilities.cs @@ -330,7 +330,7 @@ internal static void SetEnvironmentVariable(string name, string? value) /// /// Copied from the BCL implementation to eliminate some expensive security asserts on .NET Framework. /// - internal static Dictionary GetEnvironmentVariables() + internal static Dictionary GetEnvironmentVariables() { unsafe { @@ -353,7 +353,8 @@ internal static Dictionary GetEnvironmentVariables() long stringBlockLength = pEnvironmentBlockEnd - pEnvironmentBlock; - Dictionary table = new(capacity: 200, StringComparer.OrdinalIgnoreCase); // Razzle has 150 environment variables + // Razzle has 150 environment variables + Dictionary table = new(capacity: 200, StringComparer.OrdinalIgnoreCase); // Copy strings out, parsing into pairs and inserting into the table. // The first few environment variable entries start with an '='! @@ -430,7 +431,7 @@ internal static Dictionary GetEnvironmentVariables() /// /// Updates the environment to match the provided dictionary. /// - internal static void SetEnvironment(Dictionary? newEnvironment) + internal static void SetEnvironment(Dictionary? newEnvironment) { if (newEnvironment == null) { @@ -438,8 +439,8 @@ internal static void SetEnvironment(Dictionary? newEnvironment) } // First, delete all no longer set variables - Dictionary currentEnvironment = GetEnvironmentVariables(); - foreach (KeyValuePair entry in currentEnvironment) + Dictionary currentEnvironment = GetEnvironmentVariables(); + foreach (KeyValuePair entry in currentEnvironment) { if (!newEnvironment.ContainsKey(entry.Key)) { @@ -448,9 +449,9 @@ internal static void SetEnvironment(Dictionary? newEnvironment) } // Then, make sure the new ones have their new values. - foreach (KeyValuePair entry in newEnvironment) + foreach (KeyValuePair entry in newEnvironment) { - if (!currentEnvironment.TryGetValue(entry.Key, out string currentValue) || currentValue != entry.Value) + if (!currentEnvironment.TryGetValue(entry.Key, out string? currentValue) || currentValue != entry.Value) { SetEnvironmentVariable(entry.Key, entry.Value); } diff --git a/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs b/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs index aba1874ef95..1c853ff09e0 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskHostNode.cs @@ -13,973 +13,914 @@ using Microsoft.Build.TaskHost.Resources; using Microsoft.Build.TaskHost.Utilities; -#nullable disable +namespace Microsoft.Build.TaskHost; -namespace Microsoft.Build.TaskHost +/// +/// This class represents an implementation of INode for out-of-proc node for hosting tasks. +/// +internal class OutOfProcTaskHostNode : INodePacketFactory, INodePacketHandler, IBuildEngine2 { /// - /// This class represents an implementation of INode for out-of-proc node for hosting tasks. + /// Keeps a record of all environment variables that, on startup of the task host, have a different + /// value from those that are passed to the task host in the configuration packet for the first task. + /// These environments are assumed to be effectively identical, so the only difference between the + /// two sets of values should be any environment variables that differ between e.g. a 32-bit and a 64-bit + /// process. Those are the variables that this dictionary should store. + /// + /// - The key into the dictionary is the name of the environment variable. + /// - The Key of the KeyValuePair is the value of the variable in the parent process -- the value that we + /// wish to ensure is replaced by whatever the correct value in our current process is. + /// - The Value of the KeyValuePair is the value of the variable in the current process -- the value that + /// we wish to replay the Key value with in the environment that we receive from the parent before + /// applying it to the current process. + /// + /// Note that either value in the KeyValuePair can be null, as it is completely possible to have an + /// environment variable that is set in 32-bit processes but not in 64-bit, or vice versa. + /// + /// This dictionary must be static because otherwise, if a node is sitting around waiting for reuse, it will + /// have inherited the environment from the previous build, and any differences between the two will be seen + /// as "legitimate". There is no way for us to know what the differences between the startup environment of + /// the previous build and the environment of the first task run in the task host in this build -- so we + /// must assume that the 4ish system environment variables that this is really meant to catch haven't + /// somehow magically changed between two builds spaced no more than 15 minutes apart. /// - internal class OutOfProcTaskHostNode : MarshalByRefObject, INodePacketFactory, INodePacketHandler, IBuildEngine2 - { - /// - /// Keeps a record of all environment variables that, on startup of the task host, have a different - /// value from those that are passed to the task host in the configuration packet for the first task. - /// These environments are assumed to be effectively identical, so the only difference between the - /// two sets of values should be any environment variables that differ between e.g. a 32-bit and a 64-bit - /// process. Those are the variables that this dictionary should store. - /// - /// - The key into the dictionary is the name of the environment variable. - /// - The Key of the KeyValuePair is the value of the variable in the parent process -- the value that we - /// wish to ensure is replaced by whatever the correct value in our current process is. - /// - The Value of the KeyValuePair is the value of the variable in the current process -- the value that - /// we wish to replay the Key value with in the environment that we receive from the parent before - /// applying it to the current process. - /// - /// Note that either value in the KeyValuePair can be null, as it is completely possible to have an - /// environment variable that is set in 32-bit processes but not in 64-bit, or vice versa. - /// - /// This dictionary must be static because otherwise, if a node is sitting around waiting for reuse, it will - /// have inherited the environment from the previous build, and any differences between the two will be seen - /// as "legitimate". There is no way for us to know what the differences between the startup environment of - /// the previous build and the environment of the first task run in the task host in this build -- so we - /// must assume that the 4ish system environment variables that this is really meant to catch haven't - /// somehow magically changed between two builds spaced no more than 15 minutes apart. - /// - private static IDictionary> s_mismatchedEnvironmentValues; - - /// - /// The endpoint used to talk to the host. - /// - private NodeEndpointOutOfProcTaskHost _nodeEndpoint; - - /// - /// The packet factory. - /// - private NodePacketFactory _packetFactory; - - /// - /// The event which is set when we receive packets. - /// - private AutoResetEvent _packetReceivedEvent; - - /// - /// The queue of packets we have received but which have not yet been processed. - /// - private Queue _receivedPackets; - - /// - /// The current configuration for this task host. - /// - private TaskHostConfiguration _currentConfiguration; - - /// - /// The saved environment for the process. - /// - private Dictionary _savedEnvironment; - - /// - /// The event which is set when we should shut down. - /// - private ManualResetEvent _shutdownEvent; - - /// - /// The reason we are shutting down. - /// - private NodeEngineShutdownReason _shutdownReason; - - /// - /// We set this flag to track a currently executing task - /// - private bool _isTaskExecuting; - - /// - /// The event which is set when a task has completed. - /// - private AutoResetEvent _taskCompleteEvent; - - /// - /// Packet containing all the information relating to the - /// completed state of the task. - /// - private TaskHostTaskComplete _taskCompletePacket; - - /// - /// Object used to synchronize access to taskCompletePacket - /// - private object _taskCompleteLock = new(); - - /// - /// The event which is set when a task is cancelled - /// - private ManualResetEvent _taskCancelledEvent; - - /// - /// The thread currently executing user task in the TaskRunner - /// - private Thread _taskRunnerThread; - - /// - /// This is the wrapper for the user task to be executed. - /// We are providing a wrapper to create a possibility of executing the task in a separate AppDomain - /// - private OutOfProcTaskAppDomainWrapper _taskWrapper; - - /// - /// Flag indicating if we should debug communications or not. - /// - private bool _debugCommunications; - - /// - /// Flag indicating whether we should modify the environment based on any differences we find between that of the - /// task host at startup and the environment passed to us in our initial task configuration packet. - /// - private bool _updateEnvironment; - - /// - /// An interim step between MSBuildTaskHostDoNotUpdateEnvironment=1 and the default update behavior: go ahead and - /// do all the updates that we would otherwise have done by default, but log any updates that are made (at low - /// importance) so that the user is aware. - /// - private bool _updateEnvironmentAndLog; - - /// - /// Constructor. - /// - public OutOfProcTaskHostNode() - { - // We don't know what the current build thinks this variable should be until RunTask(), but as a fallback in case there are - // communications before we get the configuration set up, just go with what was already in the environment from when this node - // was initially launched. - _debugCommunications = Traits.Instance.DebugNodeCommunication; + private static Dictionary>? s_mismatchedEnvironmentValues; - _receivedPackets = new Queue(); + /// + /// The endpoint used to talk to the host. + /// + private NodeEndpointOutOfProcTaskHost? _nodeEndpoint; - // These WaitHandles are disposed in HandleShutDown() - _packetReceivedEvent = new AutoResetEvent(false); - _shutdownEvent = new ManualResetEvent(false); - _taskCompleteEvent = new AutoResetEvent(false); - _taskCancelledEvent = new ManualResetEvent(false); + /// + /// The packet factory. + /// + private readonly NodePacketFactory _packetFactory; - _packetFactory = new NodePacketFactory(); + /// + /// The event which is set when we receive packets. + /// + private readonly AutoResetEvent _packetReceivedEvent; - RegisterPacketHandler(NodePacketType.TaskHostConfiguration, TaskHostConfiguration.FactoryForDeserialization, this); - RegisterPacketHandler(NodePacketType.TaskHostTaskCancelled, TaskHostTaskCancelled.FactoryForDeserialization, this); - RegisterPacketHandler(NodePacketType.NodeBuildComplete, NodeBuildComplete.FactoryForDeserialization, this); - } + /// + /// The queue of packets we have received but which have not yet been processed. + /// + private readonly Queue _receivedPackets; - #region IBuildEngine Implementation (Properties) + /// + /// The current configuration for this task host. + /// + private TaskHostConfiguration? _currentConfiguration; - /// - /// Returns the value of ContinueOnError for the currently executing task. - /// - bool IBuildEngine.ContinueOnError - { - get - { - ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration during a BuildEngine callback!"); - return _currentConfiguration.ContinueOnError; - } - } + /// + /// The saved environment for the process. + /// + private Dictionary? _savedEnvironment; - /// - /// Returns the line number of the location in the project file of the currently executing task. - /// - public int LineNumberOfTaskNode - { - get - { - ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration during a BuildEngine callback!"); - return _currentConfiguration.LineNumberOfTask; - } - } + /// + /// The event which is set when we should shut down. + /// + private readonly ManualResetEvent _shutdownEvent; - /// - /// Returns the column number of the location in the project file of the currently executing task. - /// - public int ColumnNumberOfTaskNode - { - get - { - ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration during a BuildEngine callback!"); - return _currentConfiguration.ColumnNumberOfTask; - } - } + /// + /// The reason we are shutting down. + /// + private NodeEngineShutdownReason _shutdownReason; - /// - /// Returns the project file of the currently executing task. - /// - public string ProjectFileOfTaskNode - { - get - { - ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration during a BuildEngine callback!"); - return _currentConfiguration.ProjectFileOfTask; - } - } + /// + /// We set this flag to track a currently executing task. + /// + private bool _isTaskExecuting; - #endregion // IBuildEngine Implementation (Properties) + /// + /// The event which is set when a task has completed. + /// + private readonly AutoResetEvent _taskCompleteEvent; - #region IBuildEngine2 Implementation (Properties) + /// + /// Packet containing all the information relating to the + /// completed state of the task. + /// + private TaskHostTaskComplete? _taskCompletePacket; - /// - /// Stub implementation of IBuildEngine2.IsRunningMultipleNodes. The task host does not support this sort of - /// IBuildEngine callback, so error. - /// - bool IBuildEngine2.IsRunningMultipleNodes - { - get - { - LogErrorFromResource("BuildEngineCallbacksInTaskHostUnsupported"); - return false; - } - } + /// + /// Object used to synchronize access to taskCompletePacket. + /// + private readonly object _taskCompleteLock = new(); - #endregion // IBuildEngine2 Implementation (Properties) + /// + /// The event which is set when a task is cancelled. + /// + private readonly ManualResetEvent _taskCancelledEvent; + + /// + /// The thread currently executing user task in the TaskRunner. + /// + private Thread? _taskRunnerThread; + + /// + /// This is the wrapper for the user task to be executed. + /// We are providing a wrapper to create a possibility of executing the task in a separate AppDomain. + /// + private OutOfProcTaskAppDomainWrapper? _taskWrapper; + + /// + /// Flag indicating if we should debug communications or not. + /// + private bool _debugCommunications; + + /// + /// Flag indicating whether we should modify the environment based on any differences we find between that of the + /// task host at startup and the environment passed to us in our initial task configuration packet. + /// + private bool _updateEnvironment; + + /// + /// An interim step between MSBuildTaskHostDoNotUpdateEnvironment=1 and the default update behavior: go ahead and + /// do all the updates that we would otherwise have done by default, but log any updates that are made (at low + /// importance) so that the user is aware. + /// + private bool _updateEnvironmentAndLog; + + public OutOfProcTaskHostNode() + { + // We don't know what the current build thinks this variable should be until RunTask(), but as a fallback in case there are + // communications before we get the configuration set up, just go with what was already in the environment from when this node + // was initially launched. + _debugCommunications = Traits.Instance.DebugNodeCommunication; - #region IBuildEngine Implementation (Methods) + _receivedPackets = new Queue(); - /// - /// Sends the provided error back to the parent node to be logged, tagging it with - /// the parent node's ID so that, as far as anyone is concerned, it might as well have - /// just come from the parent node to begin with. - /// - public void LogErrorEvent(BuildErrorEventArgs e) + // These WaitHandles are disposed in HandleShutDown() + _packetReceivedEvent = new AutoResetEvent(false); + _shutdownEvent = new ManualResetEvent(false); + _taskCompleteEvent = new AutoResetEvent(false); + _taskCancelledEvent = new ManualResetEvent(false); + + _packetFactory = new NodePacketFactory(); + + RegisterPacketHandler(NodePacketType.TaskHostConfiguration, TaskHostConfiguration.FactoryForDeserialization, this); + RegisterPacketHandler(NodePacketType.TaskHostTaskCancelled, TaskHostTaskCancelled.FactoryForDeserialization, this); + RegisterPacketHandler(NodePacketType.NodeBuildComplete, NodeBuildComplete.FactoryForDeserialization, this); + } + + /// + /// Returns the value of ContinueOnError for the currently executing task. + /// + bool IBuildEngine.ContinueOnError + { + get { - SendBuildEvent(e); + ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration during a BuildEngine callback!"); + return _currentConfiguration.ContinueOnError; } + } - /// - /// Sends the provided warning back to the parent node to be logged, tagging it with - /// the parent node's ID so that, as far as anyone is concerned, it might as well have - /// just come from the parent node to begin with. - /// - public void LogWarningEvent(BuildWarningEventArgs e) + /// + /// Returns the line number of the location in the project file of the currently executing task. + /// + public int LineNumberOfTaskNode + { + get { - SendBuildEvent(e); + ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration during a BuildEngine callback!"); + return _currentConfiguration.LineNumberOfTask; } + } - /// - /// Sends the provided message back to the parent node to be logged, tagging it with - /// the parent node's ID so that, as far as anyone is concerned, it might as well have - /// just come from the parent node to begin with. - /// - public void LogMessageEvent(BuildMessageEventArgs e) + /// + /// Returns the column number of the location in the project file of the currently executing task. + /// + public int ColumnNumberOfTaskNode + { + get { - SendBuildEvent(e); + ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration during a BuildEngine callback!"); + return _currentConfiguration.ColumnNumberOfTask; } + } - /// - /// Sends the provided custom event back to the parent node to be logged, tagging it with - /// the parent node's ID so that, as far as anyone is concerned, it might as well have - /// just come from the parent node to begin with. - /// - public void LogCustomEvent(CustomBuildEventArgs e) + /// + /// Returns the project file of the currently executing task. + /// + public string ProjectFileOfTaskNode + { + get { - SendBuildEvent(e); + ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration during a BuildEngine callback!"); + return _currentConfiguration.ProjectFileOfTask; } + } - /// - /// Stub implementation of IBuildEngine.BuildProjectFile. The task host does not support IBuildEngine - /// callbacks for the purposes of building projects, so error. - /// - bool IBuildEngine.BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs) + /// + /// Stub implementation of IBuildEngine2.IsRunningMultipleNodes. The task host does not support this sort of + /// IBuildEngine callback, so error. + /// + bool IBuildEngine2.IsRunningMultipleNodes + { + get { LogErrorFromResource("BuildEngineCallbacksInTaskHostUnsupported"); return false; } + } - #endregion // IBuildEngine Implementation (Methods) - - #region IBuildEngine2 Implementation (Methods) + /// + /// Sends the provided error back to the parent node to be logged, tagging it with + /// the parent node's ID so that, as far as anyone is concerned, it might as well have + /// just come from the parent node to begin with. + /// + public void LogErrorEvent(BuildErrorEventArgs e) + => SendBuildEvent(e); - /// - /// Stub implementation of IBuildEngine2.BuildProjectFile. The task host does not support IBuildEngine - /// callbacks for the purposes of building projects, so error. - /// - bool IBuildEngine2.BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion) - { - LogErrorFromResource(SR.BuildEngineCallbacksInTaskHostUnsupported); - return false; - } + /// + /// Sends the provided warning back to the parent node to be logged, tagging it with + /// the parent node's ID so that, as far as anyone is concerned, it might as well have + /// just come from the parent node to begin with. + /// + public void LogWarningEvent(BuildWarningEventArgs e) + => SendBuildEvent(e); - /// - /// Stub implementation of IBuildEngine2.BuildProjectFilesInParallel. The task host does not support IBuildEngine - /// callbacks for the purposes of building projects, so error. - /// - bool IBuildEngine2.BuildProjectFilesInParallel(string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IDictionary[] targetOutputsPerProject, string[] toolsVersion, bool useResultsCache, bool unloadProjectsOnCompletion) - { - LogErrorFromResource(SR.BuildEngineCallbacksInTaskHostUnsupported); - return false; - } + /// + /// Sends the provided message back to the parent node to be logged, tagging it with + /// the parent node's ID so that, as far as anyone is concerned, it might as well have + /// just come from the parent node to begin with. + /// + public void LogMessageEvent(BuildMessageEventArgs e) + => SendBuildEvent(e); - #endregion // IBuildEngine2 Implementation (Methods) + /// + /// Sends the provided custom event back to the parent node to be logged, tagging it with + /// the parent node's ID so that, as far as anyone is concerned, it might as well have + /// just come from the parent node to begin with. + /// + public void LogCustomEvent(CustomBuildEventArgs e) + => SendBuildEvent(e); - #region INodePacketFactory Members + /// + /// Stub implementation of IBuildEngine.BuildProjectFile. The task host does not support IBuildEngine + /// callbacks for the purposes of building projects, so error. + /// + bool IBuildEngine.BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs) + { + LogErrorFromResource("BuildEngineCallbacksInTaskHostUnsupported"); + return false; + } - /// - /// Registers the specified handler for a particular packet type. - /// - /// The packet type. - /// The factory for packets of the specified type. - /// The handler to be called when packets of the specified type are received. - public void RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory, INodePacketHandler handler) - { - _packetFactory.RegisterPacketHandler(packetType, factory, handler); - } + /// + /// Stub implementation of IBuildEngine2.BuildProjectFile. The task host does not support IBuildEngine + /// callbacks for the purposes of building projects, so error. + /// + bool IBuildEngine2.BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion) + { + LogErrorFromResource(SR.BuildEngineCallbacksInTaskHostUnsupported); + return false; + } - /// - /// Unregisters a packet handler. - /// - /// The packet type. - public void UnregisterPacketHandler(NodePacketType packetType) - { - _packetFactory.UnregisterPacketHandler(packetType); - } + /// + /// Stub implementation of IBuildEngine2.BuildProjectFilesInParallel. The task host does not support IBuildEngine + /// callbacks for the purposes of building projects, so error. + /// + bool IBuildEngine2.BuildProjectFilesInParallel(string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IDictionary[] targetOutputsPerProject, string[] toolsVersion, bool useResultsCache, bool unloadProjectsOnCompletion) + { + LogErrorFromResource(SR.BuildEngineCallbacksInTaskHostUnsupported); + return false; + } - /// - /// Takes a serializer, deserializes the packet and routes it to the appropriate handler. - /// - /// The node from which the packet was received. - /// The packet type. - /// The translator containing the data from which the packet should be reconstructed. - public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITranslator translator) - { - _packetFactory.DeserializeAndRoutePacket(nodeId, packetType, translator); - } + /// + /// Registers the specified handler for a particular packet type. + /// + /// The packet type. + /// The factory for packets of the specified type. + /// The handler to be called when packets of the specified type are received. + public void RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory, INodePacketHandler handler) + => _packetFactory.RegisterPacketHandler(packetType, factory, handler); - /// - /// Takes a serializer and deserializes the packet. - /// - /// The packet type. - /// The translator containing the data from which the packet should be reconstructed. - public INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator) - { - return _packetFactory.DeserializePacket(packetType, translator); - } + /// + /// Unregisters a packet handler. + /// + /// The packet type. + public void UnregisterPacketHandler(NodePacketType packetType) + => _packetFactory.UnregisterPacketHandler(packetType); - /// - /// Routes the specified packet - /// - /// The node from which the packet was received. - /// The packet to route. - public void RoutePacket(int nodeId, INodePacket packet) - { - _packetFactory.RoutePacket(nodeId, packet); - } + /// + /// Takes a serializer, deserializes the packet and routes it to the appropriate handler. + /// + /// The node from which the packet was received. + /// The packet type. + /// The translator containing the data from which the packet should be reconstructed. + public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITranslator translator) + => _packetFactory.DeserializeAndRoutePacket(nodeId, packetType, translator); - #endregion // INodePacketFactory Members + /// + /// Takes a serializer and deserializes the packet. + /// + /// The packet type. + /// The translator containing the data from which the packet should be reconstructed. + public INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator) + => _packetFactory.DeserializePacket(packetType, translator); - #region INodePacketHandler Members + /// + /// Routes the specified packet. + /// + /// The node from which the packet was received. + /// The packet to route. + public void RoutePacket(int nodeId, INodePacket packet) + => _packetFactory.RoutePacket(nodeId, packet); - /// - /// This method is invoked by the NodePacketRouter when a packet is received and is intended for - /// this recipient. - /// - /// The node from which the packet was received. - /// The packet. - public void PacketReceived(int node, INodePacket packet) + /// + /// This method is invoked by the NodePacketRouter when a packet is received and is intended for + /// this recipient. + /// + /// The node from which the packet was received. + /// The packet. + public void PacketReceived(int node, INodePacket packet) + { + lock (_receivedPackets) { - lock (_receivedPackets) - { - _receivedPackets.Enqueue(packet); - _packetReceivedEvent.Set(); - } + _receivedPackets.Enqueue(packet); + _packetReceivedEvent.Set(); } + } - #endregion // INodePacketHandler Members - - #region INode Members - - /// - /// Starts up the node and processes messages until the node is requested to shut down. - /// - /// The exception which caused shutdown, if any. - /// The reason for shutting down. - public NodeEngineShutdownReason Run(out Exception shutdownException, byte parentPacketVersion = 1) - { - shutdownException = null; + /// + /// Starts up the node and processes messages until the node is requested to shut down. + /// + /// The exception which caused shutdown, if any. + /// The reason for shutting down. + public NodeEngineShutdownReason Run(out Exception? shutdownException, byte parentPacketVersion = 1) + { + shutdownException = null; - // Snapshot the current environment - _savedEnvironment = CommunicationsUtilities.GetEnvironmentVariables(); + // Snapshot the current environment + _savedEnvironment = CommunicationsUtilities.GetEnvironmentVariables(); - _nodeEndpoint = new NodeEndpointOutOfProcTaskHost(parentPacketVersion); - _nodeEndpoint.OnLinkStatusChanged += new LinkStatusChangedDelegate(OnLinkStatusChanged); - _nodeEndpoint.Listen(this); + _nodeEndpoint = new NodeEndpointOutOfProcTaskHost(parentPacketVersion); + _nodeEndpoint.OnLinkStatusChanged += new LinkStatusChangedDelegate(OnLinkStatusChanged); + _nodeEndpoint.Listen(this); - WaitHandle[] waitHandles = [_shutdownEvent, _packetReceivedEvent, _taskCompleteEvent, _taskCancelledEvent]; + WaitHandle[] waitHandles = [_shutdownEvent, _packetReceivedEvent, _taskCompleteEvent, _taskCancelledEvent]; - while (true) + while (true) + { + int index = WaitHandle.WaitAny(waitHandles); + switch (index) { - int index = WaitHandle.WaitAny(waitHandles); - switch (index) - { - case 0: // shutdownEvent - NodeEngineShutdownReason shutdownReason = HandleShutdown(); - return shutdownReason; + case 0: // shutdownEvent + NodeEngineShutdownReason shutdownReason = HandleShutdown(); + return shutdownReason; - case 1: // packetReceivedEvent - INodePacket packet = null; + case 1: // packetReceivedEvent + int packetCount = _receivedPackets.Count; - int packetCount = _receivedPackets.Count; + while (packetCount > 0) + { + INodePacket? packet = null; - while (packetCount > 0) + lock (_receivedPackets) { - lock (_receivedPackets) + if (_receivedPackets.Count > 0) { - if (_receivedPackets.Count > 0) - { - packet = _receivedPackets.Dequeue(); - } - else - { - break; - } + packet = _receivedPackets.Dequeue(); } - - if (packet != null) + else { - HandlePacket(packet); + break; } } - break; - case 2: // taskCompleteEvent - CompleteTask(); - break; - case 3: // taskCancelledEvent - CancelTask(); - break; - } - } - - // UNREACHABLE - } - #endregion + if (packet != null) + { + HandlePacket(packet); + } + } - /// - /// Dispatches the packet to the correct handler. - /// - private void HandlePacket(INodePacket packet) - { - switch (packet.Type) - { - case NodePacketType.TaskHostConfiguration: - HandleTaskHostConfiguration(packet as TaskHostConfiguration); break; - case NodePacketType.TaskHostTaskCancelled: - _taskCancelledEvent.Set(); + + case 2: // taskCompleteEvent + CompleteTask(); break; - case NodePacketType.NodeBuildComplete: - HandleNodeBuildComplete(packet as NodeBuildComplete); + + case 3: // taskCancelledEvent + CancelTask(); break; } } + } - /// - /// Configure the task host according to the information received in the - /// configuration packet - /// - private void HandleTaskHostConfiguration(TaskHostConfiguration taskHostConfiguration) + /// + /// Dispatches the packet to the correct handler. + /// + private void HandlePacket(INodePacket packet) + { + switch (packet.Type) { - ErrorUtilities.VerifyThrow(!_isTaskExecuting, "Why are we getting a TaskHostConfiguration packet while we're still executing a task?"); - _currentConfiguration = taskHostConfiguration; + case NodePacketType.TaskHostConfiguration: + HandleTaskHostConfiguration((TaskHostConfiguration)packet); + break; + + case NodePacketType.TaskHostTaskCancelled: + _taskCancelledEvent.Set(); + break; - // Kick off the task running thread. - _taskRunnerThread = new Thread(new ParameterizedThreadStart(RunTask)); - _taskRunnerThread.Name = "Task runner for task " + taskHostConfiguration.TaskName; - _taskRunnerThread.Start(taskHostConfiguration); + case NodePacketType.NodeBuildComplete: + HandleNodeBuildComplete((NodeBuildComplete)packet); + break; } + } + + /// + /// Configure the task host according to the information received in the configuration packet. + /// + private void HandleTaskHostConfiguration(TaskHostConfiguration taskHostConfiguration) + { + ErrorUtilities.VerifyThrow(!_isTaskExecuting, "Why are we getting a TaskHostConfiguration packet while we're still executing a task?"); + _currentConfiguration = taskHostConfiguration; - /// - /// The task has been completed - /// - private void CompleteTask() + // Kick off the task running thread. + _taskRunnerThread = new Thread(new ParameterizedThreadStart(RunTask)) { - ErrorUtilities.VerifyThrow(!_isTaskExecuting, "The task should be done executing before CompleteTask."); - if (_nodeEndpoint.LinkStatus == LinkStatus.Active) - { - TaskHostTaskComplete taskCompletePacketToSend; + Name = "Task runner for task " + taskHostConfiguration.TaskName + }; - lock (_taskCompleteLock) - { - ErrorUtilities.VerifyThrowInternalNull(_taskCompletePacket, "taskCompletePacket"); - taskCompletePacketToSend = _taskCompletePacket; - _taskCompletePacket = null; - } + _taskRunnerThread.Start(taskHostConfiguration); + } - _nodeEndpoint.SendData(taskCompletePacketToSend); - } + /// + /// The task has been completed. + /// + private void CompleteTask() + { + ErrorUtilities.VerifyThrow(!_isTaskExecuting, "The task should be done executing before CompleteTask."); + ErrorUtilities.VerifyThrow(_nodeEndpoint != null, $"{nameof(_nodeEndpoint)} is null."); - _currentConfiguration = null; + if (_nodeEndpoint.LinkStatus == LinkStatus.Active) + { + TaskHostTaskComplete taskCompletePacketToSend; - // If the task has been canceled, the event will still be set. - // If so, now that we've completed the task, we want to shut down - // this node -- with no reuse, since we don't know whether the - // task we canceled left the node in a good state or not. - if (_taskCancelledEvent.WaitOne(0)) + lock (_taskCompleteLock) { - _shutdownReason = NodeEngineShutdownReason.BuildComplete; - _shutdownEvent.Set(); + ErrorUtilities.VerifyThrowInternalNull(_taskCompletePacket, "taskCompletePacket"); + taskCompletePacketToSend = _taskCompletePacket; + _taskCompletePacket = null; } - } - /// - /// This task has been cancelled. Attempt to cancel the task - /// - private void CancelTask() - { - // Create a possibility for the task to be aborted if the user really wants it dropped dead asap - if (Environment.GetEnvironmentVariable("MSBUILDTASKHOSTABORTTASKONCANCEL") == "1") - { - // Don't bother aborting the task if it has passed the actual user task Execute() - // It means we're already in the process of shutting down - Wait for the taskCompleteEvent to be set instead. - if (_isTaskExecuting) - { - // The thread will be terminated crudely so our environment may be trashed but it's ok since we are - // shutting down ASAP. - _taskRunnerThread.Abort(); - } - } + _nodeEndpoint.SendData(taskCompletePacketToSend); } - /// - /// Handles the NodeBuildComplete packet. - /// - private void HandleNodeBuildComplete(NodeBuildComplete buildComplete) - { - ErrorUtilities.VerifyThrow(!_isTaskExecuting, "We should never have a task in the process of executing when we receive NodeBuildComplete."); - - // TaskHostNodes lock assemblies with custom tasks produced by build scripts if NodeReuse is on. This causes failures if the user builds twice. - _shutdownReason = buildComplete.PrepareForReuse && Traits.Instance.EscapeHatches.ReuseTaskHostNodes - ? NodeEngineShutdownReason.BuildCompleteReuse - : NodeEngineShutdownReason.BuildComplete; + _currentConfiguration = null; + // If the task has been canceled, the event will still be set. + // If so, now that we've completed the task, we want to shut down + // this node -- with no reuse, since we don't know whether the + // task we canceled left the node in a good state or not. + if (_taskCancelledEvent.WaitOne(0)) + { + _shutdownReason = NodeEngineShutdownReason.BuildComplete; _shutdownEvent.Set(); } + } - /// - /// Perform necessary actions to shut down the node. - /// - private NodeEngineShutdownReason HandleShutdown() + /// + /// This task has been cancelled. Attempt to cancel the task. + /// + private void CancelTask() + { + // Create a possibility for the task to be aborted if the user really wants it dropped dead asap + if (Environment.GetEnvironmentVariable("MSBUILDTASKHOSTABORTTASKONCANCEL") == "1") { - // Wait for the RunTask task runner thread before shutting down so that we can cleanly dispose all WaitHandles. - _taskRunnerThread?.Join(); + // Don't bother aborting the task if it has passed the actual user task Execute() + // It means we're already in the process of shutting down - Wait for the taskCompleteEvent to be set instead. + if (_isTaskExecuting) + { + // The thread will be terminated crudely so our environment may be trashed but it's ok since we are + // shutting down ASAP. + _taskRunnerThread?.Abort(); + } + } + } - using StreamWriter debugWriter = _debugCommunications - ? File.CreateText(Path.Combine(FileUtilities.TempFileDirectory, $"MSBuild_NodeShutdown_{EnvironmentUtilities.CurrentProcessId}.txt")) - : null; + /// + /// Handles the NodeBuildComplete packet. + /// + private void HandleNodeBuildComplete(NodeBuildComplete buildComplete) + { + ErrorUtilities.VerifyThrow(!_isTaskExecuting, "We should never have a task in the process of executing when we receive NodeBuildComplete."); - debugWriter?.WriteLine("Node shutting down with reason {0}.", _shutdownReason); + // TaskHostNodes lock assemblies with custom tasks produced by build scripts if NodeReuse is on. This causes failures if the user builds twice. + _shutdownReason = buildComplete.PrepareForReuse && Traits.Instance.EscapeHatches.ReuseTaskHostNodes + ? NodeEngineShutdownReason.BuildCompleteReuse + : NodeEngineShutdownReason.BuildComplete; - // On Windows, a process holds a handle to the current directory, - // so reset it away from a user-requested folder that may get deleted. - NativeMethods.SetCurrentDirectory(FileUtilities.MSBuildTaskHostDirectory); + _shutdownEvent.Set(); + } - // Restore the original environment, best effort. - try - { - CommunicationsUtilities.SetEnvironment(_savedEnvironment); - } - catch (Exception ex) - { - debugWriter?.WriteLine("Failed to restore the original environment: {0}.", ex); - } + /// + /// Perform necessary actions to shut down the node. + /// + private NodeEngineShutdownReason HandleShutdown() + { + ErrorUtilities.VerifyThrow(_nodeEndpoint != null, $"{nameof(_nodeEndpoint)} is null."); - if (_nodeEndpoint.LinkStatus == LinkStatus.Active) - { - // Notify the BuildManager that we are done. - _nodeEndpoint.SendData(new NodeShutdown(_shutdownReason == NodeEngineShutdownReason.Error ? NodeShutdownReason.Error : NodeShutdownReason.Requested)); + // Wait for the RunTask task runner thread before shutting down so that we can cleanly dispose all WaitHandles. + _taskRunnerThread?.Join(); - // Flush all packets to the pipe and close it down. This blocks until the shutdown is complete. - _nodeEndpoint.OnLinkStatusChanged -= new LinkStatusChangedDelegate(OnLinkStatusChanged); - } + using StreamWriter? debugWriter = _debugCommunications + ? File.CreateText(Path.Combine(FileUtilities.TempFileDirectory, $"MSBuild_NodeShutdown_{EnvironmentUtilities.CurrentProcessId}.txt")) + : null; - _nodeEndpoint.Disconnect(); + debugWriter?.WriteLine("Node shutting down with reason {0}.", _shutdownReason); - // Dispose these WaitHandles - _packetReceivedEvent.Close(); - _shutdownEvent.Close(); - _taskCompleteEvent.Close(); - _taskCancelledEvent.Close(); + // On Windows, a process holds a handle to the current directory, + // so reset it away from a user-requested folder that may get deleted. + NativeMethods.SetCurrentDirectory(FileUtilities.MSBuildTaskHostDirectory); - return _shutdownReason; + // Restore the original environment, best effort. + try + { + CommunicationsUtilities.SetEnvironment(_savedEnvironment); } - - /// - /// Event handler for the node endpoint's LinkStatusChanged event. - /// - private void OnLinkStatusChanged(INodeEndpoint endpoint, LinkStatus status) + catch (Exception ex) { - switch (status) - { - case LinkStatus.ConnectionFailed: - case LinkStatus.Failed: - _shutdownReason = NodeEngineShutdownReason.ConnectionFailed; - _shutdownEvent.Set(); - break; + debugWriter?.WriteLine("Failed to restore the original environment: {0}.", ex); + } - case LinkStatus.Inactive: - break; + if (_nodeEndpoint.LinkStatus == LinkStatus.Active) + { + // Notify the BuildManager that we are done. + _nodeEndpoint.SendData(new NodeShutdown(_shutdownReason == NodeEngineShutdownReason.Error ? NodeShutdownReason.Error : NodeShutdownReason.Requested)); - default: - break; - } + // Flush all packets to the pipe and close it down. This blocks until the shutdown is complete. + _nodeEndpoint.OnLinkStatusChanged -= new LinkStatusChangedDelegate(OnLinkStatusChanged); } - /// - /// Task runner method - /// - private void RunTask(object state) + _nodeEndpoint.Disconnect(); + + // Dispose these WaitHandles + _packetReceivedEvent.Close(); + _shutdownEvent.Close(); + _taskCompleteEvent.Close(); + _taskCancelledEvent.Close(); + + return _shutdownReason; + } + + /// + /// Event handler for the node endpoint's LinkStatusChanged event. + /// + private void OnLinkStatusChanged(INodeEndpoint endpoint, LinkStatus status) + { + switch (status) { - _isTaskExecuting = true; - OutOfProcTaskHostTaskResult taskResult = null; - TaskHostConfiguration taskConfiguration = state as TaskHostConfiguration; + case LinkStatus.ConnectionFailed: + case LinkStatus.Failed: + _shutdownReason = NodeEngineShutdownReason.ConnectionFailed; + _shutdownEvent.Set(); + break; - // We only really know the values of these variables for sure once we see what we received from our parent - // environment -- otherwise if this was a completely new build, we could lose out on expected environment - // variables. - _debugCommunications = taskConfiguration.BuildProcessEnvironment.HasValue("MSBUILDDEBUGCOMM", "1", StringComparison.OrdinalIgnoreCase); - _updateEnvironment = !taskConfiguration.BuildProcessEnvironment.HasValue("MSBuildTaskHostDoNotUpdateEnvironment", "1", StringComparison.OrdinalIgnoreCase); - _updateEnvironmentAndLog = taskConfiguration.BuildProcessEnvironment.HasValue("MSBuildTaskHostUpdateEnvironmentAndLog", "1", StringComparison.OrdinalIgnoreCase); + case LinkStatus.Inactive: + break; - try - { - // Change to the startup directory - NativeMethods.SetCurrentDirectory(taskConfiguration.StartupDirectory); + default: + break; + } + } - if (_updateEnvironment) - { - InitializeMismatchedEnvironmentTable(taskConfiguration.BuildProcessEnvironment); - } + /// + /// Task runner method. + /// + private void RunTask(object state) + { + _isTaskExecuting = true; + OutOfProcTaskHostTaskResult? taskResult = null; + TaskHostConfiguration taskConfiguration = (TaskHostConfiguration)state; - // Now set the new environment - SetTaskHostEnvironment(taskConfiguration.BuildProcessEnvironment); - - // Set culture - Thread.CurrentThread.CurrentCulture = taskConfiguration.Culture; - Thread.CurrentThread.CurrentUICulture = taskConfiguration.UICulture; - - // We will not create an appdomain now because of a bug - // As a fix, we will create the class directly without wrapping it in a domain - _taskWrapper = new OutOfProcTaskAppDomainWrapper(); - - taskResult = _taskWrapper.ExecuteTask( - buildEngine: this, - taskConfiguration.TaskName, - taskConfiguration.TaskLocation, - taskConfiguration.ProjectFileOfTask, - taskConfiguration.LineNumberOfTask, - taskConfiguration.ColumnNumberOfTask, - taskConfiguration.AppDomainSetup, - taskConfiguration.TaskParameters); - } - catch (ThreadAbortException) - { - // This thread was aborted as part of Cancellation, we will return a failure task result - taskResult = OutOfProcTaskHostTaskResult.Failure(); - } - catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + // We only really know the values of these variables for sure once we see what we received from our parent + // environment -- otherwise if this was a completely new build, we could lose out on expected environment + // variables. + _debugCommunications = taskConfiguration.BuildProcessEnvironment.HasValue("MSBUILDDEBUGCOMM", "1", StringComparison.OrdinalIgnoreCase); + _updateEnvironment = !taskConfiguration.BuildProcessEnvironment.HasValue("MSBuildTaskHostDoNotUpdateEnvironment", "1", StringComparison.OrdinalIgnoreCase); + _updateEnvironmentAndLog = taskConfiguration.BuildProcessEnvironment.HasValue("MSBuildTaskHostUpdateEnvironmentAndLog", "1", StringComparison.OrdinalIgnoreCase); + + try + { + // Change to the startup directory + NativeMethods.SetCurrentDirectory(taskConfiguration.StartupDirectory); + + if (_updateEnvironment) { - taskResult = OutOfProcTaskHostTaskResult.CrashedDuringExecution(e); + InitializeMismatchedEnvironmentTable(taskConfiguration.BuildProcessEnvironment); } - finally - { - try - { - _isTaskExecuting = false; - Dictionary currentEnvironment = CommunicationsUtilities.GetEnvironmentVariables(); - currentEnvironment = UpdateEnvironmentForMainNode(currentEnvironment); + // Now set the new environment + SetTaskHostEnvironment(taskConfiguration.BuildProcessEnvironment); - taskResult ??= OutOfProcTaskHostTaskResult.Failure(); + // Set culture + Thread.CurrentThread.CurrentCulture = taskConfiguration.Culture; + Thread.CurrentThread.CurrentUICulture = taskConfiguration.UICulture; - lock (_taskCompleteLock) - { - _taskCompletePacket = new TaskHostTaskComplete(taskResult, currentEnvironment); - } + // We will not create an appdomain now because of a bug + // As a fix, we will create the class directly without wrapping it in a domain + _taskWrapper = new OutOfProcTaskAppDomainWrapper(); - foreach (TaskParameter param in taskConfiguration.TaskParameters.Values) - { - // Tell remoting to forget connections to the parameter - RemotingServices.Disconnect(param); - } + taskResult = _taskWrapper.ExecuteTask( + buildEngine: this, + taskConfiguration.TaskName, + taskConfiguration.TaskLocation, + taskConfiguration.ProjectFileOfTask, + taskConfiguration.LineNumberOfTask, + taskConfiguration.ColumnNumberOfTask, + taskConfiguration.AppDomainSetup, + taskConfiguration.TaskParameters); + } + catch (ThreadAbortException) + { + // This thread was aborted as part of Cancellation, we will return a failure task result + taskResult = OutOfProcTaskHostTaskResult.Failure(); + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + taskResult = OutOfProcTaskHostTaskResult.CrashedDuringExecution(e); + } + finally + { + try + { + _isTaskExecuting = false; - // Restore the original clean environment - CommunicationsUtilities.SetEnvironment(_savedEnvironment); - } - catch (Exception e) + Dictionary currentEnvironment = CommunicationsUtilities.GetEnvironmentVariables(); + currentEnvironment = UpdateEnvironmentForMainNode(currentEnvironment); + + taskResult ??= OutOfProcTaskHostTaskResult.Failure(); + + lock (_taskCompleteLock) { - lock (_taskCompleteLock) - { - // Create a minimal taskCompletePacket to carry the exception so that the TaskHostTask does not hang while waiting - _taskCompletePacket = new TaskHostTaskComplete( - OutOfProcTaskHostTaskResult.CrashedAfterExecution(e), - buildProcessEnvironment: null); - } + _taskCompletePacket = new TaskHostTaskComplete(taskResult, currentEnvironment); } - finally + + foreach (TaskParameter param in taskConfiguration.TaskParameters.Values) { - // Call Dispose to unload any AppDomains and other necessary cleanup in the taskWrapper - _taskWrapper.Dispose(); + // Tell remoting to forget connections to the parameter + RemotingServices.Disconnect(param); + } - // The task has now fully completed executing - _taskCompleteEvent.Set(); + // Restore the original clean environment + CommunicationsUtilities.SetEnvironment(_savedEnvironment); + } + catch (Exception e) + { + lock (_taskCompleteLock) + { + // Create a minimal taskCompletePacket to carry the exception so that the TaskHostTask does not hang while waiting + _taskCompletePacket = new TaskHostTaskComplete( + OutOfProcTaskHostTaskResult.CrashedAfterExecution(e), + buildProcessEnvironment: null); } } + finally + { + // Call Dispose to unload any AppDomains and other necessary cleanup in the taskWrapper + _taskWrapper?.Dispose(); + + // The task has now fully completed executing + _taskCompleteEvent.Set(); + } } + } - /// - /// Set the environment for the task host -- includes possibly munging the given - /// environment somewhat to account for expected environment differences between, - /// e.g. parent processes and task hosts of different bitnesses. - /// - private void SetTaskHostEnvironment(Dictionary environment) - { - ErrorUtilities.VerifyThrowInternalNull(s_mismatchedEnvironmentValues, "mismatchedEnvironmentValues"); - Dictionary updatedEnvironment = null; + /// + /// Set the environment for the task host -- includes possibly munging the given + /// environment somewhat to account for expected environment differences between, + /// e.g. parent processes and task hosts of different bitnesses. + /// + private void SetTaskHostEnvironment(Dictionary environment) + { + ErrorUtilities.VerifyThrowInternalNull(s_mismatchedEnvironmentValues, "mismatchedEnvironmentValues"); + Dictionary? updatedEnvironment = null; - if (_updateEnvironment) + if (_updateEnvironment) + { + foreach (KeyValuePair> variable in s_mismatchedEnvironmentValues) { - foreach (KeyValuePair> variable in s_mismatchedEnvironmentValues) - { - string oldValue = variable.Value.Key; - string newValue = variable.Value.Value; + string oldValue = variable.Value.Key; + string? newValue = variable.Value.Value; - // We don't check the return value, because having the variable not exist == be - // null is perfectly valid, and mismatchedEnvironmentValues stores those values - // as null as well, so the String.Equals should still return that they are equal. - string environmentValue = null; - environment.TryGetValue(variable.Key, out environmentValue); + // We don't check the return value, because having the variable not exist == be + // null is perfectly valid, and mismatchedEnvironmentValues stores those values + // as null as well, so the String.Equals should still return that they are equal. + environment.TryGetValue(variable.Key, out string? environmentValue); - if (String.Equals(environmentValue, oldValue, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(environmentValue, oldValue, StringComparison.OrdinalIgnoreCase)) + { + if (updatedEnvironment == null) { - if (updatedEnvironment == null) + if (_updateEnvironmentAndLog) { - if (_updateEnvironmentAndLog) - { - LogMessageFromResource(MessageImportance.Low, SR.ModifyingTaskHostEnvironmentHeader); - } - - updatedEnvironment = new Dictionary(environment, StringComparer.OrdinalIgnoreCase); + LogMessageFromResource(MessageImportance.Low, SR.ModifyingTaskHostEnvironmentHeader); } - if (newValue != null) - { - if (_updateEnvironmentAndLog) - { - LogMessageFromResource(MessageImportance.Low, string.Format(SR.ModifyingTaskHostEnvironmentVariable, variable.Key, newValue, environmentValue ?? string.Empty)); - } + updatedEnvironment = new Dictionary(environment, StringComparer.OrdinalIgnoreCase); + } - updatedEnvironment[variable.Key] = newValue; - } - else + if (newValue != null) + { + if (_updateEnvironmentAndLog) { - updatedEnvironment.Remove(variable.Key); + LogMessageFromResource(MessageImportance.Low, string.Format(SR.ModifyingTaskHostEnvironmentVariable, variable.Key, newValue, environmentValue ?? string.Empty)); } + + updatedEnvironment[variable.Key] = newValue; + } + else + { + updatedEnvironment.Remove(variable.Key); } } } + } - // if it's still null here, there were no changes necessary -- so just - // set it to what was already passed in. - if (updatedEnvironment == null) - { - updatedEnvironment = environment; - } + // if it's still null here, there were no changes necessary -- so just + // set it to what was already passed in. + updatedEnvironment ??= environment; - CommunicationsUtilities.SetEnvironment(updatedEnvironment); - } + CommunicationsUtilities.SetEnvironment(updatedEnvironment); + } - /// - /// Given the environment of the task host at the end of task execution, make sure that any - /// processor-specific variables have been re-applied in the correct form for the main node, - /// so that when we pass this dictionary back to the main node, all it should have to do - /// is just set it. - /// - private Dictionary UpdateEnvironmentForMainNode(Dictionary environment) - { - ErrorUtilities.VerifyThrowInternalNull(s_mismatchedEnvironmentValues, "mismatchedEnvironmentValues"); - Dictionary updatedEnvironment = null; + /// + /// Given the environment of the task host at the end of task execution, make sure that any + /// processor-specific variables have been re-applied in the correct form for the main node, + /// so that when we pass this dictionary back to the main node, all it should have to do + /// is just set it. + /// + private Dictionary UpdateEnvironmentForMainNode(Dictionary environment) + { + ErrorUtilities.VerifyThrowInternalNull(s_mismatchedEnvironmentValues, "mismatchedEnvironmentValues"); + Dictionary? updatedEnvironment = null; - if (_updateEnvironment) + if (_updateEnvironment) + { + foreach (KeyValuePair> variable in s_mismatchedEnvironmentValues) { - foreach (KeyValuePair> variable in s_mismatchedEnvironmentValues) + // Since this is munging the property list for returning to the parent process, + // then the value we wish to replace is the one that is in this process, and the + // replacement value is the one that originally came from the parent process, + // instead of the other way around. + string? oldValue = variable.Value.Value; + string newValue = variable.Value.Key; + + // We don't check the return value, because having the variable not exist == be + // null is perfectly valid, and mismatchedEnvironmentValues stores those values + // as null as well, so the String.Equals should still return that they are equal. + environment.TryGetValue(variable.Key, out string? environmentValue); + + if (String.Equals(environmentValue, oldValue, StringComparison.OrdinalIgnoreCase)) { - // Since this is munging the property list for returning to the parent process, - // then the value we wish to replace is the one that is in this process, and the - // replacement value is the one that originally came from the parent process, - // instead of the other way around. - string oldValue = variable.Value.Value; - string newValue = variable.Value.Key; - - // We don't check the return value, because having the variable not exist == be - // null is perfectly valid, and mismatchedEnvironmentValues stores those values - // as null as well, so the String.Equals should still return that they are equal. - string environmentValue = null; - environment.TryGetValue(variable.Key, out environmentValue); - - if (String.Equals(environmentValue, oldValue, StringComparison.OrdinalIgnoreCase)) - { - updatedEnvironment ??= new Dictionary(environment, StringComparer.OrdinalIgnoreCase); + updatedEnvironment ??= new Dictionary(environment, StringComparer.OrdinalIgnoreCase); - if (newValue != null) - { - updatedEnvironment[variable.Key] = newValue; - } - else - { - updatedEnvironment.Remove(variable.Key); - } + if (newValue != null) + { + updatedEnvironment[variable.Key] = newValue; + } + else + { + updatedEnvironment.Remove(variable.Key); } } } + } - // if it's still null here, there were no changes necessary -- so just - // set it to what was already passed in. - if (updatedEnvironment == null) - { - updatedEnvironment = environment; - } + // if it's still null here, there were no changes necessary -- so just + // set it to what was already passed in. + updatedEnvironment ??= environment; - return updatedEnvironment; - } + return updatedEnvironment; + } - /// - /// Make sure the mismatchedEnvironmentValues table has been populated. Note that this should - /// only do actual work on the very first run of a task in the task host -- otherwise, it should - /// already have been populated. - /// - private void InitializeMismatchedEnvironmentTable(IDictionary environment) + /// + /// Make sure the mismatchedEnvironmentValues table has been populated. Note that this should + /// only do actual work on the very first run of a task in the task host -- otherwise, it should + /// already have been populated. + /// + private void InitializeMismatchedEnvironmentTable(Dictionary environment) + { + if (s_mismatchedEnvironmentValues == null) { - if (s_mismatchedEnvironmentValues == null) + // This is the first time that we have received a TaskHostConfiguration packet, so we + // need to construct the mismatched environment table based on our current environment + // (assumed to be effectively identical to startup) and the environment we were given + // via the task host configuration, assumed to be effectively identical to the startup + // environment of the task host, given that the configuration packet is sent immediately + // after the node is launched. + s_mismatchedEnvironmentValues = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + foreach (KeyValuePair variable in _savedEnvironment!) { - // This is the first time that we have received a TaskHostConfiguration packet, so we - // need to construct the mismatched environment table based on our current environment - // (assumed to be effectively identical to startup) and the environment we were given - // via the task host configuration, assumed to be effectively identical to the startup - // environment of the task host, given that the configuration packet is sent immediately - // after the node is launched. - s_mismatchedEnvironmentValues = new Dictionary>(StringComparer.OrdinalIgnoreCase); - - foreach (KeyValuePair variable in _savedEnvironment) + string? oldValue = variable.Value; + if (!environment.TryGetValue(variable.Key, out string? newValue)) { - string oldValue = variable.Value; - string newValue; - if (!environment.TryGetValue(variable.Key, out newValue)) - { - s_mismatchedEnvironmentValues[variable.Key] = new KeyValuePair(null, oldValue); - } - else - { - if (!String.Equals(oldValue, newValue, StringComparison.OrdinalIgnoreCase)) - { - s_mismatchedEnvironmentValues[variable.Key] = new KeyValuePair(newValue, oldValue); - } - } + s_mismatchedEnvironmentValues[variable.Key] = new KeyValuePair(null!, oldValue); } - - foreach (KeyValuePair variable in environment) + else if (!string.Equals(oldValue, newValue, StringComparison.OrdinalIgnoreCase)) { - string newValue = variable.Value; - string oldValue; - if (!_savedEnvironment.TryGetValue(variable.Key, out oldValue)) - { - s_mismatchedEnvironmentValues[variable.Key] = new KeyValuePair(newValue, null); - } - else - { - if (!String.Equals(oldValue, newValue, StringComparison.OrdinalIgnoreCase)) - { - s_mismatchedEnvironmentValues[variable.Key] = new KeyValuePair(newValue, oldValue); - } - } + s_mismatchedEnvironmentValues[variable.Key] = new KeyValuePair(newValue!, oldValue); } } - } - /// - /// Sends the requested packet across to the main node. - /// - private void SendBuildEvent(BuildEventArgs e) - { - if (_nodeEndpoint?.LinkStatus == LinkStatus.Active) + foreach (KeyValuePair variable in environment) { -#pragma warning disable SYSLIB0050 - // Types which are not serializable and are not IExtendedBuildEventArgs as - // those always implement custom serialization by WriteToStream and CreateFromStream. - if (!e.GetType().IsSerializable) -#pragma warning disable SYSLIB0050 + string? newValue = variable.Value; + if (!_savedEnvironment.TryGetValue(variable.Key, out string? oldValue)) { - // log a warning and bail. This will end up re-calling SendBuildEvent, but we know for a fact - // that the warning that we constructed is serializable, so everything should be good. - LogWarningFromResource(string.Format(SR.ExpectedEventToBeSerializable, e.GetType().Name)); - return; + s_mismatchedEnvironmentValues[variable.Key] = new KeyValuePair(newValue!, null); + } + else if (!string.Equals(oldValue, newValue, StringComparison.OrdinalIgnoreCase)) + { + s_mismatchedEnvironmentValues[variable.Key] = new KeyValuePair(newValue!, oldValue); } - - LogMessagePacketBase logMessage = new(new KeyValuePair(_currentConfiguration.NodeId, e)); - _nodeEndpoint.SendData(logMessage); } } + } - /// - /// Generates the message event corresponding to a particular resource string and set of args - /// - private void LogMessageFromResource(MessageImportance importance, string message) + /// + /// Sends the requested packet across to the main node. + /// + private void SendBuildEvent(BuildEventArgs e) + { + if (_nodeEndpoint?.LinkStatus == LinkStatus.Active) { - ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration when we're trying to log messages!"); + ErrorUtilities.VerifyThrow(_currentConfiguration != null, $"{nameof(_currentConfiguration)} is null."); - BuildMessageEventArgs messageArgs = new( - message, - helpKeyword: null, - _currentConfiguration.TaskName, - importance); + // Types which are not serializable and are not IExtendedBuildEventArgs as + // those always implement custom serialization by WriteToStream and CreateFromStream. + if (!e.GetType().IsSerializable) + { + // log a warning and bail. This will end up re-calling SendBuildEvent, but we know for a fact + // that the warning that we constructed is serializable, so everything should be good. + LogWarningFromResource(string.Format(SR.ExpectedEventToBeSerializable, e.GetType().Name)); + return; + } - LogMessageEvent(messageArgs); + LogMessagePacketBase logMessage = new(new KeyValuePair(_currentConfiguration.NodeId, e)); + _nodeEndpoint.SendData(logMessage); } + } - /// - /// Generates the error event corresponding to a particular resource string and set of args - /// - private void LogWarningFromResource(string message) - { - ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration when we're trying to log warnings!"); - - BuildWarningEventArgs warningArgs = new( - subcategory: null, - code: null, - file: ProjectFileOfTaskNode, - lineNumber: LineNumberOfTaskNode, - columnNumber: ColumnNumberOfTaskNode, - endLineNumber: 0, - endColumnNumber: 0, - message: message, - helpKeyword: null, - senderName: _currentConfiguration.TaskName); - - LogWarningEvent(warningArgs); - } + /// + /// Generates the message event corresponding to a particular resource string and set of args. + /// + private void LogMessageFromResource(MessageImportance importance, string message) + { + ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration when we're trying to log messages!"); - /// - /// Generates the error event corresponding to a particular resource string and set of args - /// - private void LogErrorFromResource(string message) - { - ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration when we're trying to log errors!"); - - BuildErrorEventArgs errorArgs = new( - subcategory: null, - code: null, - file: ProjectFileOfTaskNode, - lineNumber: LineNumberOfTaskNode, - columnNumber: ColumnNumberOfTaskNode, - endLineNumber: 0, - endColumnNumber: 0, - message: message, - helpKeyword: null, - senderName: _currentConfiguration.TaskName); - - LogErrorEvent(errorArgs); - } + BuildMessageEventArgs messageArgs = new( + message, + helpKeyword: null, + _currentConfiguration.TaskName, + importance); + + LogMessageEvent(messageArgs); + } + + /// + /// Generates the error event corresponding to a particular resource string and set of args. + /// + private void LogWarningFromResource(string message) + { + ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration when we're trying to log warnings!"); + + BuildWarningEventArgs warningArgs = new( + subcategory: null, + code: null, + file: ProjectFileOfTaskNode, + lineNumber: LineNumberOfTaskNode, + columnNumber: ColumnNumberOfTaskNode, + endLineNumber: 0, + endColumnNumber: 0, + message: message, + helpKeyword: null, + senderName: _currentConfiguration.TaskName); + + LogWarningEvent(warningArgs); + } + + /// + /// Generates the error event corresponding to a particular resource string and set of args. + /// + private void LogErrorFromResource(string message) + { + ErrorUtilities.VerifyThrow(_currentConfiguration != null, "We should never have a null configuration when we're trying to log errors!"); + + BuildErrorEventArgs errorArgs = new( + subcategory: null, + code: null, + file: ProjectFileOfTaskNode, + lineNumber: LineNumberOfTaskNode, + columnNumber: ColumnNumberOfTaskNode, + endLineNumber: 0, + endColumnNumber: 0, + message: message, + helpKeyword: null, + senderName: _currentConfiguration.TaskName); + + LogErrorEvent(errorArgs); } } From e9d019871a6c0c0f07121b89172e3f320c9f423f Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 9 Feb 2026 09:22:05 -0800 Subject: [PATCH 128/136] MSBuildTaskHost: Clean up TaskLoader - File-scoped namespace - Expression-bodied members --- src/MSBuildTaskHost/TaskLoader.cs | 246 +++++++++++++++--------------- 1 file changed, 121 insertions(+), 125 deletions(-) diff --git a/src/MSBuildTaskHost/TaskLoader.cs b/src/MSBuildTaskHost/TaskLoader.cs index 8accf1ff911..bea7bfb123c 100644 --- a/src/MSBuildTaskHost/TaskLoader.cs +++ b/src/MSBuildTaskHost/TaskLoader.cs @@ -7,164 +7,160 @@ using Microsoft.Build.TaskHost.Resources; using Microsoft.Build.TaskHost.Utilities; -namespace Microsoft.Build.TaskHost +namespace Microsoft.Build.TaskHost; + +/// +/// Class for loading tasks. +/// +internal static class TaskLoader { /// - /// Class for loading tasks + /// For saving the assembly that was loaded by the TypeLoader + /// We only use this when the assembly failed to load properly into the appdomain. + /// + private static LoadedType? s_resolverLoadedType; + + /// + /// Delegate for logging task loading errors. /// - internal static class TaskLoader + internal delegate void LogError(string taskLocation, int taskLine, int taskColumn, string message); + + /// + /// Checks if the given type is a task factory. + /// + /// This method is used as a type filter delegate. + /// true, if specified type is a task + internal static bool IsTaskClass(Type type, object unused) + => type.IsClass && !type.IsAbstract && type.GetInterface("Microsoft.Build.Framework.ITask") != null; + + /// + /// Creates an ITask instance and returns it. + /// + internal static ITask? CreateTask( + LoadedType loadedType, + string taskName, + string taskLocation, + int taskLine, + int taskColumn, + LogError logError, + AppDomainSetup appDomainSetup, + Action? appDomainCreated, + bool isOutOfProc, + out AppDomain? taskAppDomain) { - /// - /// For saving the assembly that was loaded by the TypeLoader - /// We only use this when the assembly failed to load properly into the appdomain - /// - private static LoadedType? s_resolverLoadedType; - - /// - /// Delegate for logging task loading errors. - /// - internal delegate void LogError(string taskLocation, int taskLine, int taskColumn, string message); - - /// - /// Checks if the given type is a task factory. - /// - /// This method is used as a type filter delegate. - /// true, if specified type is a task - internal static bool IsTaskClass(Type type, object unused) - { - return type.IsClass && !type.IsAbstract && ( - type.GetInterface("Microsoft.Build.Framework.ITask") != null); - } + bool separateAppDomain = loadedType.HasLoadInSeparateAppDomainAttribute; + s_resolverLoadedType = null; + taskAppDomain = null; + ITask? taskInstanceInOtherAppDomain = null; - /// - /// Creates an ITask instance and returns it. - /// - internal static ITask? CreateTask( - LoadedType loadedType, - string taskName, - string taskLocation, - int taskLine, - int taskColumn, - LogError logError, - AppDomainSetup appDomainSetup, - Action? appDomainCreated, - bool isOutOfProc, - out AppDomain? taskAppDomain) + try { - bool separateAppDomain = loadedType.HasLoadInSeparateAppDomainAttribute; - s_resolverLoadedType = null; - taskAppDomain = null; - ITask? taskInstanceInOtherAppDomain = null; - - try + if (separateAppDomain) { - if (separateAppDomain) + if (!loadedType.IsMarshalByRef) { - if (!loadedType.IsMarshalByRef) - { - logError(taskLocation, taskLine, taskColumn, string.Format(SR.TaskNotMarshalByRef, taskName)); - return null; - } - else - { - // Our task depend on this name to be precisely that, so if you change it make sure - // you also change the checks in the tasks run in separate AppDomains. Better yet, just don't change it. - - // Make sure we copy the appdomain configuration and send it to the appdomain we create so that if the creator of the current appdomain - // has done the binding redirection in code, that we will get those settings as well. - AppDomainSetup appDomainInfo = new AppDomainSetup(); + logError(taskLocation, taskLine, taskColumn, string.Format(SR.TaskNotMarshalByRef, taskName)); + return null; + } + else + { + // Our task depend on this name to be precisely that, so if you change it make sure + // you also change the checks in the tasks run in separate AppDomains. Better yet, just don't change it. - // Get the current app domain setup settings - byte[] currentAppdomainBytes = appDomainSetup.GetConfigurationBytes(); + // Make sure we copy the appdomain configuration and send it to the appdomain we create so that if the creator of the current appdomain + // has done the binding redirection in code, that we will get those settings as well. + AppDomainSetup appDomainInfo = new AppDomainSetup(); - // Apply the appdomain settings to the new appdomain before creating it - appDomainInfo.SetConfigurationBytes(currentAppdomainBytes); + // Get the current app domain setup settings + byte[] currentAppdomainBytes = appDomainSetup.GetConfigurationBytes(); - AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolver; - s_resolverLoadedType = loadedType; + // Apply the appdomain settings to the new appdomain before creating it + appDomainInfo.SetConfigurationBytes(currentAppdomainBytes); - taskAppDomain = AppDomain.CreateDomain(isOutOfProc ? "taskAppDomain (out-of-proc)" : "taskAppDomain (in-proc)", null, appDomainInfo); + AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolver; + s_resolverLoadedType = loadedType; - if (loadedType.LoadedAssembly != null) - { - taskAppDomain.Load(loadedType.LoadedAssemblyName); - } + taskAppDomain = AppDomain.CreateDomain(isOutOfProc ? "taskAppDomain (out-of-proc)" : "taskAppDomain (in-proc)", null, appDomainInfo); - // Hook up last minute dumping of any exceptions - taskAppDomain.UnhandledException += ExceptionHandling.UnhandledExceptionHandler; - appDomainCreated?.Invoke(taskAppDomain); + if (loadedType.LoadedAssembly != null) + { + taskAppDomain.Load(loadedType.LoadedAssemblyName); } + + // Hook up last minute dumping of any exceptions + taskAppDomain.UnhandledException += ExceptionHandling.UnhandledExceptionHandler; + appDomainCreated?.Invoke(taskAppDomain); } - else - { - // perf improvement for the same appdomain case - we already have the type object - // and don't want to go through reflection to recreate it from the name. - return (ITask?)Activator.CreateInstance(loadedType.Type); - } + } + else + { + // perf improvement for the same appdomain case - we already have the type object + // and don't want to go through reflection to recreate it from the name. + return (ITask?)Activator.CreateInstance(loadedType.Type); + } - if (loadedType.AssemblyFilePath != null) - { - taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceFromAndUnwrap(loadedType.AssemblyFilePath, loadedType.Type.FullName); + if (loadedType.AssemblyFilePath != null) + { + taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceFromAndUnwrap(loadedType.AssemblyFilePath, loadedType.Type.FullName); - // this will force evaluation of the task class type and try to load the task assembly - Type taskType = taskInstanceInOtherAppDomain.GetType(); + // this will force evaluation of the task class type and try to load the task assembly + Type taskType = taskInstanceInOtherAppDomain.GetType(); - // If the types don't match, we have a problem. It means that our AppDomain was able to load - // a task assembly using Load, and loaded a different one. I don't see any other choice than - // to fail here. - if (taskType != loadedType.Type) - { - logError(taskLocation, taskLine, taskColumn, string.Format(SR.ConflictingTaskAssembly, loadedType.AssemblyFilePath, loadedType.Type.Assembly.Location)); - taskInstanceInOtherAppDomain = null; - } - } - else + // If the types don't match, we have a problem. It means that our AppDomain was able to load + // a task assembly using Load, and loaded a different one. I don't see any other choice than + // to fail here. + if (taskType != loadedType.Type) { - taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceAndUnwrap(loadedType.Type.Assembly.FullName, loadedType.Type.FullName); + logError(taskLocation, taskLine, taskColumn, string.Format(SR.ConflictingTaskAssembly, loadedType.AssemblyFilePath, loadedType.Type.Assembly.Location)); + taskInstanceInOtherAppDomain = null; } - - return taskInstanceInOtherAppDomain; } - finally + else { - // Don't leave appdomains open - if (taskAppDomain != null && taskInstanceInOtherAppDomain == null) - { - AppDomain.Unload(taskAppDomain); - RemoveAssemblyResolver(); - } + taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceAndUnwrap(loadedType.Type.Assembly.FullName, loadedType.Type.FullName); } - } - /// - /// This is a resolver to help created AppDomains when they are unable to load an assembly into their domain we will help - /// them succeed by providing the already loaded one in the currentdomain so that they can derive AssemblyName info from it - /// - private static Assembly? AssemblyResolver(object sender, ResolveEventArgs args) + return taskInstanceInOtherAppDomain; + } + finally { - if (args.Name.Equals(s_resolverLoadedType?.LoadedAssemblyName?.FullName, StringComparison.OrdinalIgnoreCase)) + // Don't leave appdomains open + if (taskAppDomain != null && taskInstanceInOtherAppDomain == null) { - if (s_resolverLoadedType == null || s_resolverLoadedType.Path == null) - { - return null; - } - - return s_resolverLoadedType.LoadedAssembly ?? Assembly.Load(s_resolverLoadedType.Path); + AppDomain.Unload(taskAppDomain); + RemoveAssemblyResolver(); } - - return null; } + } - /// - /// Check if we added a resolver and remove it - /// - internal static void RemoveAssemblyResolver() + /// + /// This is a resolver to help created AppDomains when they are unable to load an assembly into their domain we will help + /// them succeed by providing the already loaded one in the currentdomain so that they can derive AssemblyName info from it + /// + private static Assembly? AssemblyResolver(object sender, ResolveEventArgs args) + { + if (args.Name.Equals(s_resolverLoadedType?.LoadedAssemblyName.FullName, StringComparison.OrdinalIgnoreCase)) { - if (s_resolverLoadedType != null) + if (s_resolverLoadedType == null || s_resolverLoadedType.Path == null) { - AppDomain.CurrentDomain.AssemblyResolve -= AssemblyResolver; - s_resolverLoadedType = null; + return null; } + + return s_resolverLoadedType.LoadedAssembly ?? Assembly.Load(s_resolverLoadedType.Path); + } + + return null; + } + + /// + /// Check if we added a resolver and remove it + /// + internal static void RemoveAssemblyResolver() + { + if (s_resolverLoadedType != null) + { + AppDomain.CurrentDomain.AssemblyResolve -= AssemblyResolver; + s_resolverLoadedType = null; } } } From e110c06c29f0c0714584a6531e3319946fdf9192 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 9 Feb 2026 09:24:04 -0800 Subject: [PATCH 129/136] MSBuildTaskHost: Clean up Traits - File-scoped namespace - Seal classes --- src/MSBuildTaskHost/Traits.cs | 55 +++++++++++++++++------------------ 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/src/MSBuildTaskHost/Traits.cs b/src/MSBuildTaskHost/Traits.cs index a17263de39c..0cd33e79dde 100644 --- a/src/MSBuildTaskHost/Traits.cs +++ b/src/MSBuildTaskHost/Traits.cs @@ -3,39 +3,38 @@ using System; -namespace Microsoft.Build.TaskHost +namespace Microsoft.Build.TaskHost; + +/// +/// Represents toggleable features of the MSBuild engine. +/// +internal sealed class Traits { - /// - /// Represents toggleable features of the MSBuild engine. - /// - internal class Traits + public static Traits Instance { get; } = new Traits(); + + private Traits() { - public static Traits Instance { get; } = new Traits(); + EscapeHatches = new EscapeHatches(); - private Traits() - { - EscapeHatches = new EscapeHatches(); + DebugEngine = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildDebugEngine")); + DebugNodeCommunication = DebugEngine || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDDEBUGCOMM")); + } - DebugEngine = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildDebugEngine")); - DebugNodeCommunication = DebugEngine || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDDEBUGCOMM")); - } + public EscapeHatches EscapeHatches { get; } - public EscapeHatches EscapeHatches { get; } + public readonly bool DebugEngine; + public readonly bool DebugNodeCommunication; +} - public readonly bool DebugEngine; - public readonly bool DebugNodeCommunication; - } +internal sealed class EscapeHatches +{ + /// + /// Allow node reuse of TaskHost nodes. This results in task assemblies locked past the build lifetime, preventing them from being rebuilt if custom tasks change, but may improve performance. + /// + public readonly bool ReuseTaskHostNodes = Environment.GetEnvironmentVariable("MSBUILDREUSETASKHOSTNODES") == "1"; - internal class EscapeHatches - { - /// - /// Allow node reuse of TaskHost nodes. This results in task assemblies locked past the build lifetime, preventing them from being rebuilt if custom tasks change, but may improve performance. - /// - public readonly bool ReuseTaskHostNodes = Environment.GetEnvironmentVariable("MSBUILDREUSETASKHOSTNODES") == "1"; - - /// - /// Disable the use of paths longer than Windows MAX_PATH limits (260 characters) when running on a long path enabled OS. - /// - public readonly bool DisableLongPaths = Environment.GetEnvironmentVariable("MSBUILDDISABLELONGPATHS") == "1"; - } + /// + /// Disable the use of paths longer than Windows MAX_PATH limits (260 characters) when running on a long path enabled OS. + /// + public readonly bool DisableLongPaths = Environment.GetEnvironmentVariable("MSBUILDDISABLELONGPATHS") == "1"; } From e6812e5883bce14dcab6bee5fb352325ca30f5e5 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 9 Feb 2026 09:34:22 -0800 Subject: [PATCH 130/136] MSBuildTaskHost: Clean up TypeLoader - File-scoped namespace - Expression-bodied members - Enable nullability - Remove unused parameters - Seal types --- .../OutOfProcTaskAppDomainWrapper.cs | 12 +- src/MSBuildTaskHost/OutOfProcTaskHost.cs | 1 - src/MSBuildTaskHost/TypeLoader.cs | 429 +++++++++--------- 3 files changed, 206 insertions(+), 236 deletions(-) diff --git a/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs b/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs index 815201fcc1b..315b9848d7e 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskAppDomainWrapper.cs @@ -6,7 +6,6 @@ using System.Reflection; using Microsoft.Build.Framework; using Microsoft.Build.TaskHost.BackEnd; -using Microsoft.Build.TaskHost.Resources; using Microsoft.Build.TaskHost.Utilities; namespace Microsoft.Build.TaskHost; @@ -52,16 +51,11 @@ public OutOfProcTaskHostTaskResult ExecuteTask( { _taskAppDomain = null; - LoadedType taskType; + LoadedType? taskType; try { TypeLoader typeLoader = new(TaskLoader.IsTaskClass); - taskType = typeLoader.Load( - taskName, - taskLocation, - logWarning: (format, args) => { }, - useTaskHost: false, - taskHostParamsMatchCurrentProc: true); + taskType = typeLoader.Load(taskName, taskLocation); } catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) { @@ -73,7 +67,7 @@ public OutOfProcTaskHostTaskResult ExecuteTask( return InstantiateAndExecuteTask( buildEngine, - taskType, + taskType!, taskName, taskLocation, taskFile, diff --git a/src/MSBuildTaskHost/OutOfProcTaskHost.cs b/src/MSBuildTaskHost/OutOfProcTaskHost.cs index 3a58f68770b..a0422afb7f0 100644 --- a/src/MSBuildTaskHost/OutOfProcTaskHost.cs +++ b/src/MSBuildTaskHost/OutOfProcTaskHost.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics; using Microsoft.Build.TaskHost.BackEnd; -using Microsoft.Build.TaskHost.Exceptions; using Microsoft.Build.TaskHost.Utilities; namespace Microsoft.Build.TaskHost; diff --git a/src/MSBuildTaskHost/TypeLoader.cs b/src/MSBuildTaskHost/TypeLoader.cs index 71ab01054cc..9e8eeb598f8 100644 --- a/src/MSBuildTaskHost/TypeLoader.cs +++ b/src/MSBuildTaskHost/TypeLoader.cs @@ -6,291 +6,268 @@ using System.IO; using System.Reflection; using System.Threading; -using Microsoft.Build.Framework; using Microsoft.Build.TaskHost.Collections; using Microsoft.Build.TaskHost.Utilities; -#nullable disable +namespace Microsoft.Build.TaskHost; -namespace Microsoft.Build.TaskHost +/// +/// This class is used to load types from their assemblies. +/// +internal sealed class TypeLoader { /// - /// This class is used to load types from their assemblies. + /// Cache to keep track of the assemblyLoadInfos based on a given typeFilter. /// - internal class TypeLoader - { - /// - /// Cache to keep track of the assemblyLoadInfos based on a given typeFilter. - /// - private static ConcurrentDictionary> s_cacheOfLoadedTypesByFilter = new(); + private static readonly ConcurrentDictionary> s_cacheOfLoadedTypesByFilter = new(); - /// - /// Typefilter for this typeloader - /// - private TypeFilter _isDesiredType; + /// + /// Typefilter for this typeloader + /// + private readonly TypeFilter _isDesiredType; - /// - /// Constructor. - /// - internal TypeLoader(TypeFilter isDesiredType) - { - ErrorUtilities.VerifyThrow(isDesiredType != null, "need a type filter"); + /// + /// Constructor. + /// + internal TypeLoader(TypeFilter isDesiredType) + { + ErrorUtilities.VerifyThrow(isDesiredType != null, "need a type filter"); - _isDesiredType = isDesiredType; - } + _isDesiredType = isDesiredType; + } - /// - /// Given two type names, looks for a partial match between them. A partial match is considered valid only if it occurs on - /// the right side (tail end) of the name strings, and at the start of a class or namespace name. - /// - /// - /// 1) Matches are case-insensitive. - /// 2) .NET conventions regarding namespaces and nested classes are respected, including escaping of reserved characters. - /// - /// - /// "Csc" and "csc" ==> exact match - /// "Microsoft.Build.Tasks.Csc" and "Microsoft.Build.Tasks.Csc" ==> exact match - /// "Microsoft.Build.Tasks.Csc" and "Csc" ==> partial match - /// "Microsoft.Build.Tasks.Csc" and "Tasks.Csc" ==> partial match - /// "MyTasks.ATask+NestedTask" and "NestedTask" ==> partial match - /// "MyTasks.ATask\\+NestedTask" and "NestedTask" ==> partial match - /// "MyTasks.CscTask" and "Csc" ==> no match - /// "MyTasks.MyCsc" and "Csc" ==> no match - /// "MyTasks.ATask\.Csc" and "Csc" ==> no match - /// "MyTasks.ATask\\\.Csc" and "Csc" ==> no match - /// - /// true, if the type names match exactly or partially; false, if there is no match at all - internal static bool IsPartialTypeNameMatch(string typeName1, string typeName2) + /// + /// Given two type names, looks for a partial match between them. A partial match is considered valid only if it occurs on + /// the right side (tail end) of the name strings, and at the start of a class or namespace name. + /// + /// + /// 1) Matches are case-insensitive. + /// 2) .NET conventions regarding namespaces and nested classes are respected, including escaping of reserved characters. + /// + /// + /// "Csc" and "csc" ==> exact match + /// "Microsoft.Build.Tasks.Csc" and "Microsoft.Build.Tasks.Csc" ==> exact match + /// "Microsoft.Build.Tasks.Csc" and "Csc" ==> partial match + /// "Microsoft.Build.Tasks.Csc" and "Tasks.Csc" ==> partial match + /// "MyTasks.ATask+NestedTask" and "NestedTask" ==> partial match + /// "MyTasks.ATask\\+NestedTask" and "NestedTask" ==> partial match + /// "MyTasks.CscTask" and "Csc" ==> no match + /// "MyTasks.MyCsc" and "Csc" ==> no match + /// "MyTasks.ATask\.Csc" and "Csc" ==> no match + /// "MyTasks.ATask\\\.Csc" and "Csc" ==> no match + /// + /// true, if the type names match exactly or partially; false, if there is no match at all + internal static bool IsPartialTypeNameMatch(string typeName1, string typeName2) + { + bool isPartialMatch = false; + + // if the type names are the same length, a partial match is impossible + if (typeName1.Length != typeName2.Length) { - bool isPartialMatch = false; + string longerTypeName; + string shorterTypeName; - // if the type names are the same length, a partial match is impossible - if (typeName1.Length != typeName2.Length) + // figure out which type name is longer + if (typeName1.Length > typeName2.Length) + { + longerTypeName = typeName1; + shorterTypeName = typeName2; + } + else { - string longerTypeName; - string shorterTypeName; + longerTypeName = typeName2; + shorterTypeName = typeName1; + } - // figure out which type name is longer - if (typeName1.Length > typeName2.Length) - { - longerTypeName = typeName1; - shorterTypeName = typeName2; - } - else - { - longerTypeName = typeName2; - shorterTypeName = typeName1; - } + // if the shorter type name matches the end of the longer one + if (longerTypeName.EndsWith(shorterTypeName, StringComparison.OrdinalIgnoreCase)) + { + int matchIndex = longerTypeName.Length - shorterTypeName.Length; - // if the shorter type name matches the end of the longer one - if (longerTypeName.EndsWith(shorterTypeName, StringComparison.OrdinalIgnoreCase)) + // if the matched sub-string looks like the start of a namespace or class name + if ((longerTypeName[matchIndex - 1] == '.') || (longerTypeName[matchIndex - 1] == '+')) { - int matchIndex = longerTypeName.Length - shorterTypeName.Length; + int precedingBackslashes = 0; - // if the matched sub-string looks like the start of a namespace or class name - if ((longerTypeName[matchIndex - 1] == '.') || (longerTypeName[matchIndex - 1] == '+')) + // confirm there are zero, or an even number of \'s preceding it... + for (int i = matchIndex - 2; i >= 0; i--) { - int precedingBackslashes = 0; - - // confirm there are zero, or an even number of \'s preceding it... - for (int i = matchIndex - 2; i >= 0; i--) + if (longerTypeName[i] == '\\') { - if (longerTypeName[i] == '\\') - { - precedingBackslashes++; - } - else - { - break; - } + precedingBackslashes++; } - - if ((precedingBackslashes % 2) == 0) + else { - isPartialMatch = true; + break; } } + + if ((precedingBackslashes % 2) == 0) + { + isPartialMatch = true; + } } } - else - { - isPartialMatch = (String.Equals(typeName1, typeName2, StringComparison.OrdinalIgnoreCase)); - } - - return isPartialMatch; } + else + { + isPartialMatch = string.Equals(typeName1, typeName2, StringComparison.OrdinalIgnoreCase); + } + + return isPartialMatch; + } + /// + /// Loads the specified type if it exists in the given assembly. If the type name is fully qualified, then a match (if + /// any) is unambiguous; otherwise, if there are multiple types with the same name in different namespaces, the first type + /// found will be returned. + /// + internal LoadedType? Load(string typeName, string assemblyFilePath) + => GetLoadedType(s_cacheOfLoadedTypesByFilter, typeName, assemblyFilePath); + + /// + /// Loads the specified type if it exists in the given assembly. If the type name is fully qualified, then a match (if + /// any) is unambiguous; otherwise, if there are multiple types with the same name in different namespaces, the first type + /// found will be returned. + /// + private LoadedType? GetLoadedType( + ConcurrentDictionary> cache, + string typeName, + string assemblyFilePath) + { + // A given type filter have been used on a number of assemblies, Based on the type filter + // we will get another dictionary which will map a specific assembly file path to a + // AssemblyInfoToLoadedTypes class which knows how to find a typeName in a given assembly. + var loadInfoToType = cache.GetOrAdd(_isDesiredType, _ => new(StringComparer.OrdinalIgnoreCase)); + + // Get an object which is able to take a type name and determine if it is in the assembly pointed to by the AssemblyInfo. + var typeNameToType = loadInfoToType.GetOrAdd(assemblyFilePath, assemblyFilePath => new(_isDesiredType, assemblyFilePath)); + + return typeNameToType.GetLoadedTypeByTypeName(typeName); + } + + /// + /// Given a type filter and an asssemblyInfo object keep track of what types in a given assembly which match the typefilter. + /// Also, use this information to determine if a given TypeName is in the assembly which is pointed to by the AssemblyLoadInfo object. + /// + /// This type represents a combination of a type filter and an assemblyInfo object. + /// + private sealed class TypeCache + { /// - /// Delegate used to log warning messages with formatted string support. + /// Lock to prevent two threads from using this object at the same time. + /// Since we fill up internal structures with what is in the assembly. /// - /// A composite format string for the warning message. - /// An array of objects to format into the warning message. - internal delegate void LogWarningDelegate(string format, params object[] args); + private readonly object _lockObject = new(); /// - /// Loads the specified type if it exists in the given assembly. If the type name is fully qualified, then a match (if - /// any) is unambiguous; otherwise, if there are multiple types with the same name in different namespaces, the first type - /// found will be returned. - /// The unusued bool is to match the signature of the Shared copy of TypeLoader. + /// Type filter to pick the correct types out of an assembly. /// - internal LoadedType Load( - string typeName, - string assemblyFilePath, - LogWarningDelegate logWarning, - bool useTaskHost = false, - bool taskHostParamsMatchCurrentProc = true) - { - return GetLoadedType(s_cacheOfLoadedTypesByFilter, typeName, assemblyFilePath); - } + private readonly TypeFilter _isDesiredType; /// - /// Loads the specified type if it exists in the given assembly. If the type name is fully qualified, then a match (if - /// any) is unambiguous; otherwise, if there are multiple types with the same name in different namespaces, the first type - /// found will be returned. + /// Assembly file path so we can load an assembly. /// - private LoadedType GetLoadedType( - ConcurrentDictionary> cache, - string typeName, - string assemblyFilePath) - { - // A given type filter have been used on a number of assemblies, Based on the type filter - // we will get another dictionary which will map a specific assembly file path to a - // AssemblyInfoToLoadedTypes class which knows how to find a typeName in a given assembly. - var loadInfoToType = cache.GetOrAdd(_isDesiredType, _ => new(StringComparer.OrdinalIgnoreCase)); + private readonly string _assemblyFilePath; + + /// + /// What is the type for the given type name, this may be null if the typeName does not map to a type. + /// + private readonly ConcurrentDictionary _typeNameToType; - // Get an object which is able to take a typename and determine if it is in the assembly pointed to by the AssemblyInfo. - var typeNameToType = loadInfoToType.GetOrAdd(assemblyFilePath, assemblyFilePath => new(_isDesiredType, assemblyFilePath)); + /// + /// List of public types in the assembly which match the typefilter and their corresponding types. + /// + private readonly Dictionary _publicTypeNameToType; - return typeNameToType.GetLoadedTypeByTypeName(typeName); - } + /// + /// Have we scanned the public types for this assembly yet. + /// + private long _haveScannedPublicTypes; /// - /// Given a type filter and an asssemblyInfo object keep track of what types in a given assembly which match the typefilter. - /// Also, use this information to determine if a given TypeName is in the assembly which is pointed to by the AssemblyLoadInfo object. - /// - /// This type represents a combination of a type filter and an assemblyInfo object. + /// If we loaded an assembly for this type. + /// We use this information to set the LoadedType.LoadedAssembly so that this object can be used + /// to help created AppDomains to resolve those that it could not load successfully. /// - private class AssemblyInfoToLoadedTypes - { - /// - /// Lock to prevent two threads from using this object at the same time. - /// Since we fill up internal structures with what is in the assembly - /// - private readonly object _lockObject = new(); - - /// - /// Type filter to pick the correct types out of an assembly - /// - private TypeFilter _isDesiredType; - - /// - /// Assembly file path so we can load an assembly - /// - private string _assemblyFilePath; - - /// - /// What is the type for the given type name, this may be null if the typeName does not map to a type. - /// - private ConcurrentDictionary _typeNameToType; - - /// - /// List of public types in the assembly which match the typefilter and their corresponding types - /// - private Dictionary _publicTypeNameToType; - - /// - /// Have we scanned the public types for this assembly yet. - /// - private long _haveScannedPublicTypes; - - /// - /// If we loaded an assembly for this type. - /// We use this information to set the LoadedType.LoadedAssembly so that this object can be used - /// to help created AppDomains to resolve those that it could not load successfuly - /// - private Assembly _loadedAssembly; - - /// - /// Given a type filter, and an assembly to load the type information from determine if a given type name is in the assembly or not. - /// - internal AssemblyInfoToLoadedTypes(TypeFilter typeFilter, string assemblyFilePath) - { - ErrorUtilities.VerifyThrowArgumentNull(typeFilter); - ErrorUtilities.VerifyThrowArgumentNull(assemblyFilePath); + private Assembly? _loadedAssembly; - _isDesiredType = typeFilter; - _assemblyFilePath = assemblyFilePath; - _typeNameToType = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - _publicTypeNameToType = new Dictionary(StringComparer.OrdinalIgnoreCase); - } + internal TypeCache(TypeFilter typeFilter, string assemblyFilePath) + { + ErrorUtilities.VerifyThrowArgumentNull(typeFilter); + ErrorUtilities.VerifyThrowArgumentNull(assemblyFilePath); - /// - /// Determine if a given type name is in the assembly or not. Return null if the type is not in the assembly - /// - internal LoadedType GetLoadedTypeByTypeName(string typeName) - { - ErrorUtilities.VerifyThrowArgumentNull(typeName); + _isDesiredType = typeFilter; + _assemblyFilePath = assemblyFilePath; + _typeNameToType = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + _publicTypeNameToType = new Dictionary(StringComparer.OrdinalIgnoreCase); + } - // Only one thread should be doing operations on this instance of the object at a time. + /// + /// Determine if a given type name is in the assembly or not. Return null if the type is not in the assembly + /// + internal LoadedType? GetLoadedTypeByTypeName(string typeName) + { + ErrorUtilities.VerifyThrowArgumentNull(typeName); - Type type = _typeNameToType.GetOrAdd(typeName, (key) => + // Only one thread should be doing operations on this instance of the object at a time. + Type? type = _typeNameToType.GetOrAdd(typeName, (key) => + { + if (Interlocked.Read(ref _haveScannedPublicTypes) == 0) { - if (Interlocked.Read(ref _haveScannedPublicTypes) == 0) + lock (_lockObject) { - lock (_lockObject) + if (Interlocked.Read(ref _haveScannedPublicTypes) == 0) { - if (Interlocked.Read(ref _haveScannedPublicTypes) == 0) - { - ScanAssemblyForPublicTypes(); - Interlocked.Exchange(ref _haveScannedPublicTypes, ~0); - } + ScanAssemblyForPublicTypes(); + Interlocked.Exchange(ref _haveScannedPublicTypes, ~0); } } + } - foreach (KeyValuePair desiredTypeInAssembly in _publicTypeNameToType) + foreach (KeyValuePair desiredTypeInAssembly in _publicTypeNameToType) + { + // if type matches partially on its name + if (typeName.Length == 0 || IsPartialTypeNameMatch(desiredTypeInAssembly.Key, typeName)) { - // if type matches partially on its name - if (typeName.Length == 0 || IsPartialTypeNameMatch(desiredTypeInAssembly.Key, typeName)) - { - return desiredTypeInAssembly.Value; - } + return desiredTypeInAssembly.Value; } + } - return null; - }); + return null; + }); - return type != null - ? new LoadedType(type, _assemblyFilePath, _loadedAssembly ?? type.Assembly) - : null; - } + return type != null + ? new LoadedType(type, _assemblyFilePath, _loadedAssembly ?? type.Assembly) + : null; + } - /// - /// Scan the assembly pointed to by the assemblyLoadInfo for public types. We will use these public types to do partial name matching on - /// to find tasks, loggers, and task factories. - /// - private void ScanAssemblyForPublicTypes() + /// + /// Scan the assembly pointed to by the assemblyLoadInfo for public types. We will use these public types to do partial name matching on + /// to find tasks, loggers, and task factories. + /// + private void ScanAssemblyForPublicTypes() + { + // we need to search the assembly for the type... + try { - // we need to search the assembly for the type... - try - { - _loadedAssembly = Assembly.LoadFrom(_assemblyFilePath); - } - catch (ArgumentException e) - { - // Assembly.Load() and Assembly.LoadFrom() will throw an ArgumentException if the assembly name is invalid - // convert to a FileNotFoundException because it's more meaningful - // NOTE: don't use ErrorUtilities.VerifyThrowFileExists() here because that will hit the disk again - throw new FileNotFoundException(null, _assemblyFilePath, e); - } + _loadedAssembly = Assembly.LoadFrom(_assemblyFilePath); + } + catch (ArgumentException e) + { + // Assembly.Load() and Assembly.LoadFrom() will throw an ArgumentException if the assembly name is invalid + // convert to a FileNotFoundException because it's more meaningful + // NOTE: don't use ErrorUtilities.VerifyThrowFileExists() here because that will hit the disk again + throw new FileNotFoundException(message: null, _assemblyFilePath, e); + } - // only look at public types - Type[] allPublicTypesInAssembly = _loadedAssembly.GetExportedTypes(); - foreach (Type publicType in allPublicTypesInAssembly) + // only look at public types + Type[] allPublicTypesInAssembly = _loadedAssembly.GetExportedTypes(); + foreach (Type publicType in allPublicTypesInAssembly) + { + if (_isDesiredType(publicType, filterCriteria: null)) { - if (_isDesiredType(publicType, null)) - { - _publicTypeNameToType.Add(publicType.FullName, publicType); - } + _publicTypeNameToType.Add(publicType.FullName, publicType); } } } From b0c015fd7a467e37817492d4b8db5cd3a62492c7 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 9 Feb 2026 10:54:12 -0800 Subject: [PATCH 131/136] Add test that builds .NET Framework 3.5 WinForms project --- src/Build.UnitTests/MSBuildTaskHostTests.cs | 57 ++++++ .../Microsoft.Build.Engine.UnitTests.csproj | 16 +- .../TestAssets/Net35WinFormsApp/App.config | 6 + .../Net35WinFormsApp/Form1.Designer.cs | 56 ++++++ .../TestAssets/Net35WinFormsApp/Form1.cs | 12 ++ .../TestAssets/Net35WinFormsApp/Form1.resx | 167 ++++++++++++++++++ .../TestAssets/Net35WinFormsApp/Program.cs | 19 ++ .../Properties/AssemblyInfo.cs | 33 ++++ .../Properties/Resources.Designer.cs | 63 +++++++ .../Properties/Resources.resx | 117 ++++++++++++ .../Properties/Settings.Designer.cs | 26 +++ .../Properties/Settings.settings | 7 + .../Net35WinFormsApp/TestNet35WinForms.csproj | 83 +++++++++ .../WindowsNet35OnlyFactAttribute.cs | 27 +++ 14 files changed, 685 insertions(+), 4 deletions(-) create mode 100644 src/Build.UnitTests/MSBuildTaskHostTests.cs create mode 100644 src/Build.UnitTests/TestAssets/Net35WinFormsApp/App.config create mode 100644 src/Build.UnitTests/TestAssets/Net35WinFormsApp/Form1.Designer.cs create mode 100644 src/Build.UnitTests/TestAssets/Net35WinFormsApp/Form1.cs create mode 100644 src/Build.UnitTests/TestAssets/Net35WinFormsApp/Form1.resx create mode 100644 src/Build.UnitTests/TestAssets/Net35WinFormsApp/Program.cs create mode 100644 src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/AssemblyInfo.cs create mode 100644 src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/Resources.Designer.cs create mode 100644 src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/Resources.resx create mode 100644 src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/Settings.Designer.cs create mode 100644 src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/Settings.settings create mode 100644 src/Build.UnitTests/TestAssets/Net35WinFormsApp/TestNet35WinForms.csproj create mode 100644 src/UnitTests.Shared/WindowsNet35OnlyFactAttribute.cs diff --git a/src/Build.UnitTests/MSBuildTaskHostTests.cs b/src/Build.UnitTests/MSBuildTaskHostTests.cs new file mode 100644 index 00000000000..3cbd0c88177 --- /dev/null +++ b/src/Build.UnitTests/MSBuildTaskHostTests.cs @@ -0,0 +1,57 @@ +// 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.Diagnostics; +using System.IO; +using System.Reflection; +using Microsoft.Build.UnitTests; +using Microsoft.Build.UnitTests.Shared; +using Shouldly; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.Build.Engine.UnitTests; + +public class MSBuildTaskHostTests(ITestOutputHelper testOutput) : IDisposable +{ + private static string AssemblyLocation + => field ??= Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? AppContext.BaseDirectory); + + private static string TestAssetsRootPath + => field ??= Path.Combine(AssemblyLocation, "TestAssets"); + + private readonly TestEnvironment _environment = TestEnvironment.Create(testOutput); + + public void Dispose() + => _environment.Dispose(); + + [WindowsNet35OnlyFact] + public void CompileNet35WinFormsApp() + { + TransientTestFolder testFolder = _environment.CreateFolder(createFolder: true); + + CopyFilesRecursively(Path.Combine(TestAssetsRootPath, "Net35WinFormsApp"), testFolder.Path); + string projectFilePath = Path.Combine(testFolder.Path, "TestNet35WinForms.csproj"); + + string output = RunnerUtilities.ExecBootstrapedMSBuild(projectFilePath, out bool success, outputHelper: testOutput); + success.ShouldBeTrue(); + + output.ShouldContain("Build succeeded."); + } + + private static void CopyFilesRecursively(string sourcePath, string targetPath) + { + // First Create all directories + foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories)) + { + Directory.CreateDirectory(dirPath.Replace(sourcePath, targetPath)); + } + + // Then copy all the files & Replaces any files with the same name + foreach (string newPath in Directory.GetFiles(sourcePath, "*", SearchOption.AllDirectories)) + { + File.Copy(newPath, newPath.Replace(sourcePath, targetPath), overwrite: true); + } + } +} diff --git a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj index 0d284cca584..1b015454ab5 100644 --- a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj +++ b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj @@ -8,12 +8,12 @@ $(DefineConstants);MICROSOFT_BUILD_ENGINE_UNITTESTS - + $(DefineConstants);NO_MSBUILDTASKHOST true - + $(NoWarn);MSB3270 @@ -30,7 +30,7 @@ all - + @@ -54,6 +54,13 @@ + + + + + + + @@ -86,6 +93,7 @@ PreserveNewest + PreserveNewest @@ -122,7 +130,7 @@ PreserveNewest - + diff --git a/src/Build.UnitTests/TestAssets/Net35WinFormsApp/App.config b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/App.config new file mode 100644 index 00000000000..343984d02a5 --- /dev/null +++ b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Form1.Designer.cs b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Form1.Designer.cs new file mode 100644 index 00000000000..7444d24def3 --- /dev/null +++ b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Form1.Designer.cs @@ -0,0 +1,56 @@ +namespace TestNet35WinForms +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1)); + this.button1 = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + // + // Form1 + // + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.button1); + this.Name = "Form1"; + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button button1; + } +} + diff --git a/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Form1.cs b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Form1.cs new file mode 100644 index 00000000000..573a142d2d5 --- /dev/null +++ b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Form1.cs @@ -0,0 +1,12 @@ +using System.Windows.Forms; + +namespace TestNet35WinForms +{ + public partial class Form1 : Form + { + public Form1() + { + InitializeComponent(); + } + } +} diff --git a/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Form1.resx b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Form1.resx new file mode 100644 index 00000000000..c4dc6763d4b --- /dev/null +++ b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Form1.resx @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 0 + + + 0 + + + + 222, 133 + + + button1 + + + Form1 + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 6, 13 + + + 800, 450 + + + 223, 124 + + + System.Windows.Forms.Form, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Form1 + + + $this + + + True + + + fr + + \ No newline at end of file diff --git a/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Program.cs b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Program.cs new file mode 100644 index 00000000000..ad80aff934a --- /dev/null +++ b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Program.cs @@ -0,0 +1,19 @@ +using System; +using System.Windows.Forms; + +namespace TestNet35WinForms +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} diff --git a/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/AssemblyInfo.cs b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..7d73c627448 --- /dev/null +++ b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TestNet35WinForms")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("HP Inc.")] +[assembly: AssemblyProduct("TestNet35WinForms")] +[assembly: AssemblyCopyright("Copyright © HP Inc. 2026")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f8f9dbb8-cf1f-43fa-a6be-5200d903b1d8")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/Resources.Designer.cs b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/Resources.Designer.cs new file mode 100644 index 00000000000..fc045239cee --- /dev/null +++ b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace TestNet35WinForms.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TestNet35WinForms.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/Resources.resx b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/Resources.resx new file mode 100644 index 00000000000..af7dbebbace --- /dev/null +++ b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/Settings.Designer.cs b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/Settings.Designer.cs new file mode 100644 index 00000000000..8534fdac326 --- /dev/null +++ b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace TestNet35WinForms.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/Settings.settings b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/Settings.settings new file mode 100644 index 00000000000..39645652af6 --- /dev/null +++ b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Build.UnitTests/TestAssets/Net35WinFormsApp/TestNet35WinForms.csproj b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/TestNet35WinForms.csproj new file mode 100644 index 00000000000..0bc3550a967 --- /dev/null +++ b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/TestNet35WinForms.csproj @@ -0,0 +1,83 @@ + + + + + Debug + AnyCPU + {F8F9DBB8-CF1F-43FA-A6BE-5200D903B1D8} + WinExe + TestNet35WinForms + TestNet35WinForms + v3.5 + 512 + true + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + Form + + + Form1.cs + + + + + Form1.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + \ No newline at end of file diff --git a/src/UnitTests.Shared/WindowsNet35OnlyFactAttribute.cs b/src/UnitTests.Shared/WindowsNet35OnlyFactAttribute.cs new file mode 100644 index 00000000000..3c238c25392 --- /dev/null +++ b/src/UnitTests.Shared/WindowsNet35OnlyFactAttribute.cs @@ -0,0 +1,27 @@ +// 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.Runtime.InteropServices; +using Microsoft.Build.Shared; +using Xunit; + +namespace Microsoft.Build.UnitTests.Shared; + +public class WindowsNet35OnlyFactAttribute : FactAttribute +{ + private const string Message = "This test only runs on Windows under .NET Framework when .NET Framework 3.5 is installed."; + + public WindowsNet35OnlyFactAttribute(string? additionalMessage = null) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || + !RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework", StringComparison.OrdinalIgnoreCase) || + FrameworkLocationHelper.GetPathToDotNetFrameworkV35(DotNetFrameworkArchitecture.Current) == null) + { + Skip = SkipMessage(additionalMessage); + } + } + + private static string SkipMessage(string? additionalMessage = null) + => !string.IsNullOrWhiteSpace(additionalMessage) ? $"{Message} {additionalMessage}" : Message; +} From db644592fbca3f948f41b3bfe856f5b24d01279b Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 9 Feb 2026 16:46:31 -0800 Subject: [PATCH 132/136] Apply Copilot CR feedback - Fix TaskHostLaunchArgs to exclude empty quotes at front of command-line args. - Fix XML doc comment - Don't assert for non-localized string in test. - Remove company and copyright text from Net35WinFormsApp test assets. - Fix logic error in BuildExceptionSerializationHelper.DeserializeException. - Make a few fields read-only. --- src/Build.UnitTests/MSBuildTaskHostTests.cs | 4 +--- .../Net35WinFormsApp/Properties/AssemblyInfo.cs | 4 ++-- .../Components/Communications/TaskHostLaunchArgs.cs | 5 +---- src/MSBuildTaskHost/Collections/ConcurrentDictionary.cs | 2 +- src/MSBuildTaskHost/Collections/ConcurrentQueue.cs | 2 +- .../Exceptions/BuildExceptionSerializationHelper.cs | 4 ++-- src/MSBuildTaskHost/Utilities/FileUtilities.cs | 9 +++------ src/Shared/INodePacket.cs | 2 +- 8 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/Build.UnitTests/MSBuildTaskHostTests.cs b/src/Build.UnitTests/MSBuildTaskHostTests.cs index 3cbd0c88177..9d045f1975f 100644 --- a/src/Build.UnitTests/MSBuildTaskHostTests.cs +++ b/src/Build.UnitTests/MSBuildTaskHostTests.cs @@ -34,10 +34,8 @@ public void CompileNet35WinFormsApp() CopyFilesRecursively(Path.Combine(TestAssetsRootPath, "Net35WinFormsApp"), testFolder.Path); string projectFilePath = Path.Combine(testFolder.Path, "TestNet35WinForms.csproj"); - string output = RunnerUtilities.ExecBootstrapedMSBuild(projectFilePath, out bool success, outputHelper: testOutput); + _ = RunnerUtilities.ExecBootstrapedMSBuild(projectFilePath, out bool success, outputHelper: testOutput); success.ShouldBeTrue(); - - output.ShouldContain("Build succeeded."); } private static void CopyFilesRecursively(string sourcePath, string targetPath) diff --git a/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/AssemblyInfo.cs b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/AssemblyInfo.cs index 7d73c627448..3714740b839 100644 --- a/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/AssemblyInfo.cs +++ b/src/Build.UnitTests/TestAssets/Net35WinFormsApp/Properties/AssemblyInfo.cs @@ -8,9 +8,9 @@ [assembly: AssemblyTitle("TestNet35WinForms")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("HP Inc.")] +[assembly: AssemblyCompany("")] [assembly: AssemblyProduct("TestNet35WinForms")] -[assembly: AssemblyCopyright("Copyright © HP Inc. 2026")] +[assembly: AssemblyCopyright("")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/src/Build/BackEnd/Components/Communications/TaskHostLaunchArgs.cs b/src/Build/BackEnd/Components/Communications/TaskHostLaunchArgs.cs index 6047e8bfd37..e804b6254c9 100644 --- a/src/Build/BackEnd/Components/Communications/TaskHostLaunchArgs.cs +++ b/src/Build/BackEnd/Components/Communications/TaskHostLaunchArgs.cs @@ -77,7 +77,6 @@ public static bool TryCreate( } #if FEATURE_NET35_TASKHOST - if (Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.CLR2)) { // The .NET 3.5 task host uses the directory of its EXE when calculating salt for the handshake. @@ -94,10 +93,8 @@ public static bool TryCreate( nodeReuse = Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.NodeReuse); - // If the runtime host path is null it means we don't have MSBuild.dll path resolved and - // there is no need to include it in the command line arguments. commandLineArgs = $""" - "" /nologo {NodeModeHelper.ToCommandLineArgument(NodeMode.OutOfProcTaskHostNode)} /nodereuse:{nodeReuse.ToString().ToLower()} /low:{buildParameters.LowPriority.ToString().ToLower()} /parentpacketversion:{NodePacketTypeExtensions.PacketVersion} + /nologo {NodeModeHelper.ToCommandLineArgument(NodeMode.OutOfProcTaskHostNode)} /nodereuse:{nodeReuse.ToString().ToLower()} /low:{buildParameters.LowPriority.ToString().ToLower()} /parentpacketversion:{NodePacketTypeExtensions.PacketVersion} """; handshake = new Handshake(hostContext); diff --git a/src/MSBuildTaskHost/Collections/ConcurrentDictionary.cs b/src/MSBuildTaskHost/Collections/ConcurrentDictionary.cs index 4f589978edc..6c17edb64ec 100644 --- a/src/MSBuildTaskHost/Collections/ConcurrentDictionary.cs +++ b/src/MSBuildTaskHost/Collections/ConcurrentDictionary.cs @@ -36,7 +36,7 @@ internal Tables(Node[] buckets, object[] locks, int[] countPerLock) } private volatile Tables _tables; // Internal tables of the dictionary - private IEqualityComparer _comparer; // Key equality comparer + private readonly IEqualityComparer _comparer; // Key equality comparer private readonly bool _growLockArray; // Whether to dynamically increase the size of the striped lock private int _budget; // The maximum number of elements per lock before a resize operation is triggered diff --git a/src/MSBuildTaskHost/Collections/ConcurrentQueue.cs b/src/MSBuildTaskHost/Collections/ConcurrentQueue.cs index 5310a53fd46..140c4c767e2 100644 --- a/src/MSBuildTaskHost/Collections/ConcurrentQueue.cs +++ b/src/MSBuildTaskHost/Collections/ConcurrentQueue.cs @@ -45,7 +45,7 @@ internal class ConcurrentQueue /// Lock used to protect cross-segment operations, including any updates to or /// and any operations that need to get a consistent view of them. /// - private object _crossSegmentLock; + private readonly object _crossSegmentLock; /// The current tail segment. private volatile Segment _tail; diff --git a/src/MSBuildTaskHost/Exceptions/BuildExceptionSerializationHelper.cs b/src/MSBuildTaskHost/Exceptions/BuildExceptionSerializationHelper.cs index 1f5603b0fee..c2be08cbf6e 100644 --- a/src/MSBuildTaskHost/Exceptions/BuildExceptionSerializationHelper.cs +++ b/src/MSBuildTaskHost/Exceptions/BuildExceptionSerializationHelper.cs @@ -8,7 +8,7 @@ namespace Microsoft.Build.TaskHost.Exceptions; internal static class BuildExceptionSerializationHelper { - private static Dictionary> s_exceptionFactories = new() + private static readonly Dictionary> s_exceptionFactories = new() { { GetSerializationKey(), InternalErrorException.CreateFromRemote } }; @@ -25,7 +25,7 @@ public static string GetSerializationKey(Type exceptionType) public static BuildExceptionBase DeserializeException(string serializationType, string message, Exception? innerException) { - if (s_exceptionFactories.TryGetValue(serializationType, out var factory)) + if (!s_exceptionFactories.TryGetValue(serializationType, out var factory)) { factory = s_defaultFactory; } diff --git a/src/MSBuildTaskHost/Utilities/FileUtilities.cs b/src/MSBuildTaskHost/Utilities/FileUtilities.cs index 96dce0caa28..3392272a243 100644 --- a/src/MSBuildTaskHost/Utilities/FileUtilities.cs +++ b/src/MSBuildTaskHost/Utilities/FileUtilities.cs @@ -275,13 +275,10 @@ private static string GetFullPath(string fileSpec, string currentDirectory, bool fullPath = EscapingUtilities.Escape(fullPath); } - if (!EndsWithSlash(fullPath)) + if (!EndsWithSlash(fullPath) && (IsDrivePattern(fileSpec) || IsUncPattern(fullPath))) { - if (IsDrivePattern(fileSpec) || IsUncPattern(fullPath)) - { - // append trailing slash if Path.GetFullPath failed to (this happens with drive-specs and UNC shares) - fullPath += Path.DirectorySeparatorChar; - } + // append trailing slash if Path.GetFullPath failed to (this happens with drive-specs and UNC shares) + fullPath += Path.DirectorySeparatorChar; } return fullPath; diff --git a/src/Shared/INodePacket.cs b/src/Shared/INodePacket.cs index c49c228d511..21eb14b06dd 100644 --- a/src/Shared/INodePacket.cs +++ b/src/Shared/INodePacket.cs @@ -19,7 +19,7 @@ namespace Microsoft.Build.BackEnd /// Several of these values must be kept in sync with MSBuildTaskHost's NodePacketType. /// The values shared with MSBuildTaskHost are , /// , , , - /// , and ."/>. + /// , and . /// internal enum NodePacketType : byte { From 57622aa223baa00baa4a675e17f3243a4ccdab71 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 10 Feb 2026 11:38:58 -0800 Subject: [PATCH 133/136] Set in Microsoft.Build.Engine.UnitTests.csproj The App.config in the Net35WinFormsApp test project conflicts with the shared App.config that is linked into MS.Build.Engine.UnitTests. However, that breaks most of those tests since the "Current" toolset is read from the MS.Build.Engine.UnitTests.dll.config. To fix this, add ..\Shared\UnitTests\App.config to MS.Build.Engine.UnitTests.csproj. --- src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj index 1b015454ab5..5265c80743a 100644 --- a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj +++ b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj @@ -1,4 +1,4 @@ - + $(RuntimeOutputTargetFrameworks) @@ -15,6 +15,8 @@ $(NoWarn);MSB3270 + + ..\Shared\UnitTests\App.config From 069a045c45e2a13689e9fc5af948756b005e839f Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 10 Feb 2026 12:45:29 -0800 Subject: [PATCH 134/136] Ensure .NET 3.5 test only runs on valid bootstrap layout Depending on what runtime MSBuild create the bootstrap layout with, it may have the net10.0 or net472 version Microsoft.NET.Build.Extensions. A .NET 3.5 project will only build when the net472 version is available. So, WindowsNet35OnlyFactAttribute has been tweaked to check the bootstrap folder layout and ensure the net4* version is present. --- src/Build.UnitTests/MSBuildTaskHostTests.cs | 2 +- .../WindowsNet35OnlyFactAttribute.cs | 48 ++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/Build.UnitTests/MSBuildTaskHostTests.cs b/src/Build.UnitTests/MSBuildTaskHostTests.cs index 9d045f1975f..2891387495f 100644 --- a/src/Build.UnitTests/MSBuildTaskHostTests.cs +++ b/src/Build.UnitTests/MSBuildTaskHostTests.cs @@ -34,7 +34,7 @@ public void CompileNet35WinFormsApp() CopyFilesRecursively(Path.Combine(TestAssetsRootPath, "Net35WinFormsApp"), testFolder.Path); string projectFilePath = Path.Combine(testFolder.Path, "TestNet35WinForms.csproj"); - _ = RunnerUtilities.ExecBootstrapedMSBuild(projectFilePath, out bool success, outputHelper: testOutput); + _ = RunnerUtilities.ExecBootstrapedMSBuild($"{projectFilePath}", out bool success, outputHelper: testOutput); success.ShouldBeTrue(); } diff --git a/src/UnitTests.Shared/WindowsNet35OnlyFactAttribute.cs b/src/UnitTests.Shared/WindowsNet35OnlyFactAttribute.cs index 3c238c25392..625c5387df8 100644 --- a/src/UnitTests.Shared/WindowsNet35OnlyFactAttribute.cs +++ b/src/UnitTests.Shared/WindowsNet35OnlyFactAttribute.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.IO; using System.Runtime.InteropServices; using Microsoft.Build.Shared; using Xunit; @@ -16,7 +17,8 @@ public WindowsNet35OnlyFactAttribute(string? additionalMessage = null) { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework", StringComparison.OrdinalIgnoreCase) || - FrameworkLocationHelper.GetPathToDotNetFrameworkV35(DotNetFrameworkArchitecture.Current) == null) + !IsNetFramework35Installed() || + !BootstrapHasNetFxMicrosoftNetBuildExtensions()) { Skip = SkipMessage(additionalMessage); } @@ -24,4 +26,48 @@ public WindowsNet35OnlyFactAttribute(string? additionalMessage = null) private static string SkipMessage(string? additionalMessage = null) => !string.IsNullOrWhiteSpace(additionalMessage) ? $"{Message} {additionalMessage}" : Message; + + private static bool IsNetFramework35Installed() + => FrameworkLocationHelper.GetPathToDotNetFrameworkV35(DotNetFrameworkArchitecture.Current) != null; + + /// + /// Checks to see if the .NET Framework version of Microsoft.NET.Build.Extensions is installed. + /// If it isn't, building building for .NET Framework 3.5 will fail. + /// + private static bool BootstrapHasNetFxMicrosoftNetBuildExtensions() + { + var binDir = new DirectoryInfo(RunnerUtilities.BootstrapMsBuildBinaryLocation); + + // The bin directory should be something like, D:\repo\msbuild\artifacts\bin\bootstrap\net472\MSBuild\Current\Bin + // Walk up three levels to get the TFM folder name. + // Then, look for .\bootstrap\TFM\MSBuild\Microsoft\Microsoft.NET.Build.Extensions\tools\TFM + if (binDir == null || !"Bin".Equals(binDir.Name, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + var currentDir = binDir.Parent; + if (currentDir == null || !"Current".Equals(currentDir.Name, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + var msbuildDir = currentDir.Parent; + if (msbuildDir == null || !"MSBuild".Equals(msbuildDir.Name, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + var tfmDir = msbuildDir.Parent; + string? tfm = tfmDir?.Name; + + if (tfm == null || !tfm.StartsWith("net4", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + var directories = msbuildDir.GetDirectories(@"Microsoft\Microsoft.NET.Build.Extensions\tools\net4*"); + + return Array.Exists(directories, x => x.Name == tfm); + } } From 46709a54010edf73214b1dda421016ca3676ab7c Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 20 Feb 2026 09:47:30 -0800 Subject: [PATCH 135/136] Fix issue with MSBuildTaskHost handshake salt produced by client When launching the CLR2 task host (i.e. MSBuildTaskHost.exe), TaskHostLaunchArgs passes a predefinedToolsDirectory for the handshake salt when creating a Handshake. However, Handshake's constructor ignores that parameter unless it's being created for .NET task host (and compiled for .NET Framework). This change updates the Handshake constructor to always use the 'toolsDirectory' parameter for the salt if it's provided. However, VerifyThrow methods have been added to ensure that the toolsDirectory parameter is always null it is for the .NET or CLR2 TaskHost (and compiled for .NET Framework). --- .../Communications/TaskHostLaunchArgs.cs | 2 +- src/Shared/CommunicationsUtilities.cs | 64 ++++++++++++------- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/Build/BackEnd/Components/Communications/TaskHostLaunchArgs.cs b/src/Build/BackEnd/Components/Communications/TaskHostLaunchArgs.cs index e804b6254c9..a2270474563 100644 --- a/src/Build/BackEnd/Components/Communications/TaskHostLaunchArgs.cs +++ b/src/Build/BackEnd/Components/Communications/TaskHostLaunchArgs.cs @@ -60,7 +60,7 @@ public static bool TryCreate( "{msbuildLocation}" /nologo {NodeModeHelper.ToCommandLineArgument(NodeMode.OutOfProcTaskHostNode)} /nodereuse:{nodeReuse.ToString().ToLower()} /low:{buildParameters.LowPriority.ToString().ToLower()} /parentpacketversion:{NodePacketTypeExtensions.PacketVersion} """; - handshake = new Handshake(hostContext, predefinedToolsDirectory: msbuildAssemblyDirectory); + handshake = new Handshake(hostContext, toolsDirectory: msbuildAssemblyDirectory); result = new TaskHostLaunchArgs(runtimeHostPath, commandLineArgs, handshake, usingDotNetExe: true); return true; diff --git a/src/Shared/CommunicationsUtilities.cs b/src/Shared/CommunicationsUtilities.cs index 119464d22dd..a80c985bff0 100644 --- a/src/Shared/CommunicationsUtilities.cs +++ b/src/Shared/CommunicationsUtilities.cs @@ -223,21 +223,29 @@ internal class Handshake protected readonly HandshakeComponents _handshakeComponents; /// - /// Initializes a new instance of the class with the specified node type - /// and optional predefined tools directory. + /// Initializes a new instance of the class with the specified node type. /// /// - /// The that specifies the type of node and configuration options for the handshake operation. + /// The that specifies the type of node and configuration options for the handshake operation. /// - /// - /// An optional directory path used for .NET TaskHost handshake salt calculation (only on .NET Framework). - /// When specified for .NET TaskHost nodes, this directory path is included in the handshake salt - /// to ensure the child dotnet process connects with the expected tools directory context. - /// For non-.NET TaskHost nodes or on .NET Core, the MSBuildToolsDirectoryRoot is used instead. - /// This parameter is ignored when not running .NET TaskHost on .NET Framework. + public Handshake(HandshakeOptions nodeType) + : this(nodeType, includeSessionId: true, toolsDirectory: null) + { + } + + /// + /// Initializes a new instance of the class with the specified node type + /// and optional predefined tools directory. + /// + /// + /// The that specifies the type of node and configuration options for the handshake operation. /// - internal Handshake(HandshakeOptions nodeType, string predefinedToolsDirectory = null) - : this(nodeType, includeSessionId: true, predefinedToolsDirectory) + /// + /// The directory path to use for handshake salt calculation. For some task hosts, notably the .NET TaskHost (on .NET Framework) + /// and the CLR2 TaskHost, this is needed to ensure the child process connects with the expected tools directory context. + /// + public Handshake(HandshakeOptions nodeType, string toolsDirectory) + : this(nodeType, includeSessionId: true, toolsDirectory) { } @@ -247,19 +255,30 @@ internal Handshake(HandshakeOptions nodeType, string predefinedToolsDirectory = // Source options of the handshake. internal HandshakeOptions HandshakeOptions { get; } - protected Handshake(HandshakeOptions nodeType, bool includeSessionId, string predefinedToolsDirectory) + protected Handshake(HandshakeOptions nodeType, bool includeSessionId, string toolsDirectory) { HandshakeOptions = nodeType; +#if NETFRAMEWORK + ErrorUtilities.VerifyThrow( + toolsDirectory is null || IsNetTaskHost || IsClr2TaskHost, + $"{toolsDirectory} should only be provided for .NET or CLR2 TaskHost nodes (and only when running on .NET Framework)."); +#else + ErrorUtilities.VerifyThrow( + toolsDirectory is null, + $"{toolsDirectory} should not have been provided."); +#endif + + toolsDirectory ??= BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot; + // Build handshake options with version in upper bits const int handshakeVersion = (int)CommunicationsUtilities.handshakeVersion; var options = (int)nodeType | (handshakeVersion << 24); CommunicationsUtilities.Trace("Building handshake for node type {0}, (version {1}): options {2}.", nodeType, handshakeVersion, options); // Calculate salt from environment and tools directory - bool isNetTaskHost = IsHandshakeOptionEnabled(nodeType, HandshakeOptions.NET | HandshakeOptions.TaskHost); string handshakeSalt = Environment.GetEnvironmentVariable("MSBUILDNODEHANDSHAKESALT") ?? ""; - string toolsDirectory = GetToolsDirectory(isNetTaskHost, predefinedToolsDirectory); + int salt = CommunicationsUtilities.GetHashCode($"{handshakeSalt}{toolsDirectory}"); CommunicationsUtilities.Trace("Handshake salt is {0}", handshakeSalt); @@ -273,20 +292,17 @@ protected Handshake(HandshakeOptions nodeType, bool includeSessionId, string pre sessionId = currentProcess.SessionId; } - _handshakeComponents = isNetTaskHost + _handshakeComponents = IsNetTaskHost ? CreateNetTaskHostComponents(options, salt, sessionId) : CreateStandardComponents(options, salt, sessionId); } - private string GetToolsDirectory(bool isNetTaskHost, string predefinedToolsDirectory) => -#if NETFRAMEWORK - isNetTaskHost + private bool IsNetTaskHost + => IsHandshakeOptionEnabled(HandshakeOptions, HandshakeOptions.NET | HandshakeOptions.TaskHost); - // For .NET TaskHost assembly directory we set the expectation for the child dotnet process to connect to. - ? predefinedToolsDirectory - : BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot; -#else - BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot; +#if NETFRAMEWORK + private bool IsClr2TaskHost + => IsHandshakeOptionEnabled(HandshakeOptions, HandshakeOptions.CLR2 | HandshakeOptions.TaskHost); #endif private static HandshakeComponents CreateNetTaskHostComponents(int options, int salt, int sessionId) => new( @@ -336,7 +352,7 @@ internal sealed class ServerNodeHandshake : Handshake public override byte? ExpectedVersionInFirstByte => null; internal ServerNodeHandshake(HandshakeOptions nodeType) - : base(nodeType, includeSessionId: false, predefinedToolsDirectory: null) + : base(nodeType, includeSessionId: false, toolsDirectory: null) { } From ea520311ed8a90bbab4001e37db3957e81ee2c94 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 27 Feb 2026 12:34:36 -0800 Subject: [PATCH 136/136] Change TaskHostLaunchArgs to more closely match NodeLaunchData I had introduced TaskHostLaunchArgs to consolidate some of the logic used to gather the arguments need to launch nodes. However, this will conflict with another change that is currently out for review: https://github.com/dotnet/msbuild/pull/13175. This change replaces TaskHostLaunchArgs with a NodeLaunchData type that looks more like the one in #13175. It is not identical, but it should make it a bit easier to merge the two changes. --- .../Communications/INodeLauncher.cs | 23 ++++ .../NodeProviderOutOfProcTaskHost.cs | 79 +++++++++++-- .../Communications/TaskHostLaunchArgs.cs | 105 ------------------ src/Build/Microsoft.Build.csproj | 1 - 4 files changed, 95 insertions(+), 113 deletions(-) delete mode 100644 src/Build/BackEnd/Components/Communications/TaskHostLaunchArgs.cs diff --git a/src/Build/BackEnd/Components/Communications/INodeLauncher.cs b/src/Build/BackEnd/Components/Communications/INodeLauncher.cs index c409c856c0b..e1e7414c8fd 100644 --- a/src/Build/BackEnd/Components/Communications/INodeLauncher.cs +++ b/src/Build/BackEnd/Components/Communications/INodeLauncher.cs @@ -2,9 +2,32 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using Microsoft.Build.Internal; namespace Microsoft.Build.BackEnd { + /// + /// Represents the configuration data needed to launch a node process. + /// + /// + /// The path to the MSBuild binary to launch (e.g., MSBuild.exe, MSBuild.dll or MSBuildTaskHost.exe). + /// If is passed, will + /// be used as the default MSBuild location. + /// + /// The command line arguments to pass to the executable. + /// The handshake data used to establish communication with the node process. + /// + /// if the dotnet.exe should be used to launch the MSBuild assembly; + /// if the MSBuild executable should be launched directly. + /// + internal readonly record struct NodeLaunchData( + string? MSBuildLocation, + string CommandLineArgs, + Handshake Handshake, + bool UsingDotNetExe = false) + { + } + internal interface INodeLauncher { Process Start(string msbuildLocation, string commandLineArgs, int nodeId); diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs index 46bcb09434b..81bc379ad4f 100644 --- a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs +++ b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Threading; using Microsoft.Build.Exceptions; +using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.Internal; using Microsoft.Build.Shared; @@ -669,27 +670,29 @@ internal bool CreateNode(TaskHostNodeKey nodeKey, INodePacketFactory factory, IN // Create callbacks that capture the TaskHostNodeKey void OnNodeContextCreated(NodeContext context) => NodeContextCreated(context, nodeKey); - if (!TaskHostLaunchArgs.TryCreate(in taskHostParameters, ComponentHost.BuildParameters, hostContext, out TaskHostLaunchArgs launchArgs)) + NodeLaunchData nodeLaunchData = ResolveNodeLaunchConfiguration(hostContext, in taskHostParameters); + + if (nodeLaunchData.MSBuildLocation == null) { return false; } - if (launchArgs.UsingDotNetExe) + if (nodeLaunchData.UsingDotNetExe) { - CommunicationsUtilities.Trace("For a host context of {0}, spawning dotnet.exe from {1}.", hostContext.ToString(), launchArgs.ExePath); + CommunicationsUtilities.Trace("For a host context of {0}, spawning dotnet.exe from {1}.", hostContext.ToString(), nodeLaunchData.MSBuildLocation); } else { - CommunicationsUtilities.Trace("For a host context of {0}, spawning executable from {1}.", hostContext.ToString(), launchArgs.ExePath); + CommunicationsUtilities.Trace("For a host context of {0}, spawning executable from {1}.", hostContext.ToString(), nodeLaunchData.MSBuildLocation); } // There is always one task host per host context so we always create just 1 one task host node here. IList nodeContexts = GetNodes( - launchArgs.ExePath, - launchArgs.CommandLineArgs, + nodeLaunchData.MSBuildLocation, + nodeLaunchData.CommandLineArgs, communicationNodeId, factory: this, - launchArgs.Handshake, + nodeLaunchData.Handshake, OnNodeContextCreated, NodeContextTerminated, numberOfNodesToCreate: 1); @@ -697,6 +700,68 @@ internal bool CreateNode(TaskHostNodeKey nodeKey, INodePacketFactory factory, IN return nodeContexts.Count == 1; } + private NodeLaunchData ResolveNodeLaunchConfiguration(HandshakeOptions hostContext, ref readonly TaskHostParameters taskHostParameters) + { + string msbuildLocation; + string commandLineArgs; + Handshake handshake; + bool nodeReuse; + + BuildParameters buildParameters = ComponentHost.BuildParameters; + +#if NETFRAMEWORK + + // Handle scenario where a .NET task host is launched from .NET Framework + if (Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.NET)) + { + (string runtimeHostPath, string msbuildAssemblyDirectory) = GetMSBuildLocationForNETRuntime(hostContext, taskHostParameters); + + msbuildLocation = Path.Combine(msbuildAssemblyDirectory, Constants.MSBuildAssemblyName); + nodeReuse = Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.NodeReuse); + + commandLineArgs = $""" + "{msbuildLocation}" /nologo {NodeModeHelper.ToCommandLineArgument(NodeMode.OutOfProcTaskHostNode)} /nodereuse:{nodeReuse.ToString().ToLower()} /low:{buildParameters.LowPriority.ToString().ToLower()} /parentpacketversion:{NodePacketTypeExtensions.PacketVersion} + """; + + handshake = new Handshake(hostContext, toolsDirectory: msbuildAssemblyDirectory); + + return new NodeLaunchData(runtimeHostPath, commandLineArgs, handshake, UsingDotNetExe: true); + } +#endif + + msbuildLocation = GetMSBuildExecutablePathForNonNETRuntimes(hostContext); + + // we couldn't even figure out the location we're trying to launch ... just go ahead and fail. + if (msbuildLocation == null) + { + return default; + } + +#if FEATURE_NET35_TASKHOST + if (Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.CLR2)) + { + // The .NET 3.5 task host uses the directory of its EXE when calculating salt for the handshake. + string toolsDirectory = Path.GetDirectoryName(msbuildLocation) ?? string.Empty; + + // MSBuildTaskHost doesn't use command-line arguments. + commandLineArgs = ""; + handshake = new Handshake(hostContext, toolsDirectory); + + return new NodeLaunchData(msbuildLocation, commandLineArgs, handshake); + } +#endif + + nodeReuse = Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.NodeReuse); + + commandLineArgs = $""" + /nologo {NodeModeHelper.ToCommandLineArgument(NodeMode.OutOfProcTaskHostNode)} /nodereuse:{nodeReuse.ToString().ToLower()} /low:{buildParameters.LowPriority.ToString().ToLower()} /parentpacketversion:{NodePacketTypeExtensions.PacketVersion} + """; + + handshake = new Handshake(hostContext); + + return new NodeLaunchData(msbuildLocation, commandLineArgs, handshake); + } + /// /// Method called when a context created. /// diff --git a/src/Build/BackEnd/Components/Communications/TaskHostLaunchArgs.cs b/src/Build/BackEnd/Components/Communications/TaskHostLaunchArgs.cs deleted file mode 100644 index a2270474563..00000000000 --- a/src/Build/BackEnd/Components/Communications/TaskHostLaunchArgs.cs +++ /dev/null @@ -1,105 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics.CodeAnalysis; -using Microsoft.Build.Execution; -using Microsoft.Build.Framework; -using Microsoft.Build.Internal; - -#if NET -#else -using Microsoft.IO; -#endif - -namespace Microsoft.Build.BackEnd; - -internal sealed class TaskHostLaunchArgs -{ - public string ExePath { get; } - - public string CommandLineArgs { get; } - - public Handshake Handshake { get; } - - public bool UsingDotNetExe { get; } - - private TaskHostLaunchArgs( - string exePath, - string commandLineArgs, - Handshake handshake, - bool usingDotNetExe = false) - { - ExePath = exePath; - CommandLineArgs = commandLineArgs; - Handshake = handshake; - UsingDotNetExe = usingDotNetExe; - } - - public static bool TryCreate( - ref readonly TaskHostParameters taskHostParameters, - BuildParameters buildParameters, - HandshakeOptions hostContext, - [NotNullWhen(true)] out TaskHostLaunchArgs? result) - { - string msbuildLocation; - string commandLineArgs; - Handshake handshake; - bool nodeReuse; - -#if NETFRAMEWORK - - // Handle scenario where a .NET task host is launched from .NET Framework - if (Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.NET)) - { - (string runtimeHostPath, string msbuildAssemblyDirectory) = NodeProviderOutOfProcTaskHost.GetMSBuildLocationForNETRuntime(hostContext, taskHostParameters); - - msbuildLocation = Path.Combine(msbuildAssemblyDirectory, Constants.MSBuildAssemblyName); - nodeReuse = Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.NodeReuse); - - commandLineArgs = $""" - "{msbuildLocation}" /nologo {NodeModeHelper.ToCommandLineArgument(NodeMode.OutOfProcTaskHostNode)} /nodereuse:{nodeReuse.ToString().ToLower()} /low:{buildParameters.LowPriority.ToString().ToLower()} /parentpacketversion:{NodePacketTypeExtensions.PacketVersion} - """; - - handshake = new Handshake(hostContext, toolsDirectory: msbuildAssemblyDirectory); - - result = new TaskHostLaunchArgs(runtimeHostPath, commandLineArgs, handshake, usingDotNetExe: true); - return true; - } -#endif - - msbuildLocation = NodeProviderOutOfProcTaskHost.GetMSBuildExecutablePathForNonNETRuntimes(hostContext); - - // we couldn't even figure out the location we're trying to launch ... just go ahead and fail. - if (msbuildLocation == null) - { - result = null; - return false; - } - -#if FEATURE_NET35_TASKHOST - if (Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.CLR2)) - { - // The .NET 3.5 task host uses the directory of its EXE when calculating salt for the handshake. - string toolsDirectory = Path.GetDirectoryName(msbuildLocation) ?? string.Empty; - - // MSBuildTaskHost doesn't use command-line arguments. - commandLineArgs = ""; - handshake = new Handshake(hostContext, toolsDirectory); - - result = new TaskHostLaunchArgs(msbuildLocation, commandLineArgs, handshake); - return true; - } -#endif - - nodeReuse = Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.NodeReuse); - - commandLineArgs = $""" - /nologo {NodeModeHelper.ToCommandLineArgument(NodeMode.OutOfProcTaskHostNode)} /nodereuse:{nodeReuse.ToString().ToLower()} /low:{buildParameters.LowPriority.ToString().ToLower()} /parentpacketversion:{NodePacketTypeExtensions.PacketVersion} - """; - - handshake = new Handshake(hostContext); - - result = new TaskHostLaunchArgs(msbuildLocation, commandLineArgs, handshake); - return true; - } -} diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 02711c45ed1..a9812801ba5 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -179,7 +179,6 @@ -