From 3cf160becba22346b977305129757572d56d4349 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 25 Feb 2026 11:39:42 -0800 Subject: [PATCH 01/27] Remove all CLR2COMPATIBILITY code paths Now that all .NET 3.5 code has been consolidated in MSBuildTaskHost, the CLR2COMPATIBILITY conditional compiler constant can be removed entirely from the code base. --- .../BackEnd/BuildManager/BuildManager.cs | 2 - src/Framework/AssemblyUtilities.cs | 20 ------ src/Framework/BinaryTranslator.cs | 42 ------------- src/Framework/ITranslator.cs | 9 --- src/Framework/NativeMethods.cs | 61 +------------------ src/Framework/StringBuilderCache.cs | 8 +-- .../OutOfProcTaskAppDomainWrapperBase.cs | 4 -- src/MSBuild/OutOfProcTaskHostNode.cs | 55 ++--------------- src/Shared/BuildEnvironmentHelper.cs | 4 -- src/Shared/CollectionHelpers.cs | 2 - src/Shared/CommunicationsUtilities.cs | 34 +---------- src/Shared/ErrorUtilities.cs | 4 -- src/Shared/ExceptionHandling.cs | 29 ++------- src/Shared/FileSystem/FileSystems.cs | 4 -- src/Shared/FileUtilities.cs | 34 +---------- src/Shared/InterningBinaryReader.cs | 21 ++----- src/Shared/LogMessagePacketBase.cs | 8 --- src/Shared/Modifiers.cs | 15 ----- src/Shared/NamedPipeUtil.cs | 6 -- src/Shared/NodeEndpointOutOfProcBase.cs | 16 ++--- src/Shared/ReadOnlyEmptyDictionary.cs | 14 +---- src/Shared/TaskEngineAssemblyResolver.cs | 5 -- src/Shared/TaskHostConfiguration.cs | 12 ---- src/Shared/TaskHostTaskComplete.cs | 2 +- src/Shared/TempFileUtilities.cs | 6 +- src/Shared/XMakeAttributes.cs | 7 +-- 26 files changed, 33 insertions(+), 391 deletions(-) diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 9656669047a..8e7efc9c055 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -2933,9 +2933,7 @@ private void CheckAllSubmissionsComplete(BuildRequestDataFlags? flags) _buildParameters?.ProjectRootElementCache?.Clear(); FileMatcher.ClearCaches(); -#if !CLR2COMPATIBILITY FileUtilities.ClearFileExistenceCache(); -#endif } _noActiveSubmissionsEvent?.Set(); diff --git a/src/Framework/AssemblyUtilities.cs b/src/Framework/AssemblyUtilities.cs index 6b21b7d22eb..95246ed5b49 100644 --- a/src/Framework/AssemblyUtilities.cs +++ b/src/Framework/AssemblyUtilities.cs @@ -30,12 +30,8 @@ internal static class AssemblyUtilities 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) { @@ -55,22 +51,8 @@ public static string GetAssemblyLocation(Assembly 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. @@ -98,8 +80,6 @@ public static AssemblyName CloneIfPossible(this AssemblyName assemblyNameToClone #endif return name; -#endif - } #if !FEATURE_CULTUREINFO_GETCULTURES diff --git a/src/Framework/BinaryTranslator.cs b/src/Framework/BinaryTranslator.cs index b42cbddd95e..518b31ab30b 100644 --- a/src/Framework/BinaryTranslator.cs +++ b/src/Framework/BinaryTranslator.cs @@ -475,11 +475,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 /// @@ -499,7 +494,6 @@ public void Translate(ref BuildEventContext value) _reader.ReadInt32(), _reader.ReadInt32()); } -#endif /// /// Translates a CultureInfo @@ -509,39 +503,9 @@ 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. /// @@ -1337,11 +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 /// @@ -1360,7 +1319,6 @@ public void Translate(ref BuildEventContext value) _writer.Write(value.TargetId); _writer.Write(value.TaskId); } -#endif /// /// Translates a CultureInfo diff --git a/src/Framework/ITranslator.cs b/src/Framework/ITranslator.cs index 3b191f6503c..4196a66293b 100644 --- a/src/Framework/ITranslator.cs +++ b/src/Framework/ITranslator.cs @@ -267,14 +267,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 /// @@ -284,7 +276,6 @@ BinaryWriter Writer /// /// The context to be translated. void Translate(ref BuildEventContext value); -#endif /// /// Translates an enumeration. diff --git a/src/Framework/NativeMethods.cs b/src/Framework/NativeMethods.cs index 4ae854ba1b4..7195d8ee36f 100644 --- a/src/Framework/NativeMethods.cs +++ b/src/Framework/NativeMethods.cs @@ -10,14 +10,10 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Versioning; +using Microsoft.Build.Framework.Logging; 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 @@ -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; @@ -762,11 +750,7 @@ internal static bool IsUnixLike [SupportedOSPlatformGuard("linux")] internal static bool IsLinux { -#if CLR2COMPATIBILITY - get { return false; } -#else get { return RuntimeInformation.IsOSPlatform(OSPlatform.Linux); } -#endif } /// @@ -775,41 +759,30 @@ internal static bool IsLinux [SupportedOSPlatformGuard("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 @@ -817,15 +790,11 @@ internal static bool IsWindows [SupportedOSPlatformGuard("macos")] internal static bool IsOSX { -#if CLR2COMPATIBILITY - get { return false; } -#else get { _isOSX ??= RuntimeInformation.IsOSPlatform(OSPlatform.OSX); return _isOSX.Value; } -#endif } /// @@ -867,7 +836,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. @@ -890,7 +858,6 @@ internal static bool OSUsesCaseSensitivePaths }); internal static bool IsFileSystemCaseSensitive => s_isFileSystemCaseSensitive.Value; -#endif /// /// The base directory for all framework paths in Mono @@ -1179,7 +1146,7 @@ internal static bool MakeSymbolicLink(string newFileName, string existingFileNam /// internal static DateTime GetLastWriteFileUtcTime(string fullPath) { -#if !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS +#if !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS if (Traits.Instance.EscapeHatches.AlwaysDoImmutableFilesUpToDateCheck) { return LastWriteFileUtcTime(fullPath); @@ -1407,7 +1374,6 @@ internal static void KillTree(int processIdToKill) internal static int GetParentProcessId(int processId) { int ParentID = 0; -#if !CLR2COMPATIBILITY if (IsUnixLike) { string line = null; @@ -1442,7 +1408,6 @@ internal static int GetParentProcessId(int processId) } } else -#endif { using SafeProcessHandle hProcess = OpenProcess(eDesiredAccess.PROCESS_QUERY_INFORMATION, false, processId); { @@ -1581,24 +1546,7 @@ 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; - } - - 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) @@ -1611,7 +1559,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) @@ -1683,7 +1630,6 @@ internal static void RestoreConsoleMode(uint? originalConsoleMode, StreamHandleT _ = SetConsoleMode(stdOut, originalConsoleMode.Value); } } -#endif // !CLR2COMPATIBILITY #endregion @@ -1911,5 +1857,4 @@ internal static bool FileOrDirectoryExistsWindows(string path) } #endregion - } diff --git a/src/Framework/StringBuilderCache.cs b/src/Framework/StringBuilderCache.cs index 5fd67790e9d..929411cdddc 100644 --- a/src/Framework/StringBuilderCache.cs +++ b/src/Framework/StringBuilderCache.cs @@ -4,7 +4,7 @@ using System; using System.Diagnostics; using System.Text; -#if DEBUG && !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS +#if DEBUG && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS using Microsoft.Build.Eventing; #endif @@ -50,7 +50,7 @@ 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 +#if DEBUG && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS MSBuildEventSource.Log.ReusableStringBuilderFactoryStart(hash: sb.GetHashCode(), newCapacity: capacity, oldCapacity: sb.Capacity, type: "sbc-hit"); #endif return sb; @@ -59,7 +59,7 @@ public static StringBuilder Acquire(int capacity = 16 /*StringBuilder.DefaultCap } StringBuilder stringBuilder = new StringBuilder(capacity); -#if DEBUG && !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS +#if DEBUG && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS MSBuildEventSource.Log.ReusableStringBuilderFactoryStart(hash: stringBuilder.GetHashCode(), newCapacity: capacity, oldCapacity: stringBuilder.Capacity, type: "sbc-miss"); #endif return stringBuilder; @@ -92,7 +92,7 @@ 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 +#if DEBUG && !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/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs b/src/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs index 0ad4fc8fe6b..9fe51b52a9a 100644 --- a/src/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs +++ b/src/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs @@ -285,11 +285,7 @@ private OutOfProcTaskHostTaskResult InstantiateAndExecuteTaskInSTAThread( } finally { -#if CLR2COMPATIBILITY - taskRunnerFinished.Close(); -#else taskRunnerFinished.Dispose(); -#endif taskRunnerFinished = null; } diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index cc3e18e6525..dcb4be5d2f8 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -3,26 +3,17 @@ using System; using System.Collections; -#if !CLR2COMPATIBILITY using System.Collections.Concurrent; -#endif using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using System.Threading; -#if !CLR2COMPATIBILITY using System.Threading.Tasks; -#endif using Microsoft.Build.BackEnd; using Microsoft.Build.Execution; -#if !CLR2COMPATIBILITY -using Microsoft.Build.Exceptions; -#endif using Microsoft.Build.Framework; -#if !CLR2COMPATIBILITY using Microsoft.Build.Experimental.FileAccess; -#endif using Microsoft.Build.Internal; using Microsoft.Build.Shared; #if FEATURE_APPDOMAIN @@ -40,12 +31,7 @@ internal class OutOfProcTaskHostNode : #if FEATURE_APPDOMAIN MarshalByRefObject, #endif - INodePacketFactory, INodePacketHandler, -#if CLR2COMPATIBILITY - IBuildEngine3 -#else - IBuildEngine10 -#endif + INodePacketFactory, INodePacketHandler, IBuildEngine10 { /// /// Keeps a record of all environment variables that, on startup of the task host, have a different @@ -173,12 +159,10 @@ internal class OutOfProcTaskHostNode : /// private bool _nodeReuse; -#if !CLR2COMPATIBILITY /// /// The task object cache. /// private RegisteredTaskObjectCacheBase _registeredTaskObjectCache; -#endif #if FEATURE_REPORTFILEACCESSES /// @@ -187,7 +171,6 @@ internal class OutOfProcTaskHostNode : private List _fileAccessData = new List(); #endif -#if !CLR2COMPATIBILITY /// /// Counter for generating unique request IDs for callback correlation. /// @@ -216,7 +199,6 @@ internal class OutOfProcTaskHostNode : /// True if the worker node's packet version is high enough, or if the feature is force-enabled via env var. /// private bool CallbacksSupported => _parentPacketVersion >= CallbacksMinPacketVersion || Traits.Instance.EnableTaskHostCallbacks; -#endif /// /// Constructor. @@ -243,15 +225,10 @@ 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 thisINodePacketFactory.RegisterPacketHandler(NodePacketType.TaskHostIsRunningMultipleNodesResponse, TaskHostIsRunningMultipleNodesResponse.FactoryForDeserialization, this); thisINodePacketFactory.RegisterPacketHandler(NodePacketType.TaskHostCoresResponse, TaskHostCoresResponse.FactoryForDeserialization, this); -#endif -#if !CLR2COMPATIBILITY EngineServices = new EngineServicesImpl(this); -#endif } #region IBuildEngine Implementation (Properties) @@ -317,10 +294,6 @@ public bool IsRunningMultipleNodes { get { -#if CLR2COMPATIBILITY - LogErrorFromResource("BuildEngineCallbacksInTaskHostUnsupported"); - return false; -#else if (!CallbacksSupported) { LogErrorFromResource("BuildEngineCallbacksInTaskHostUnsupported"); @@ -330,7 +303,6 @@ public bool IsRunningMultipleNodes var request = new TaskHostIsRunningMultipleNodesRequest(); var response = SendCallbackRequestAndWaitForResponse(request); return response.IsRunningMultipleNodes; -#endif } } @@ -482,7 +454,6 @@ public void Reacquire() #endregion // IBuildEngine3 Implementation -#if !CLR2COMPATIBILITY #region IBuildEngine4 Implementation /// @@ -647,8 +618,6 @@ public void ReportFileAccess(FileAccessData fileAccessData) #endregion -#endif - #region INodePacketFactory Members /// @@ -732,10 +701,9 @@ 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(); _parentPacketVersion = parentPacketVersion; -#endif + shutdownException = null; // Snapshot the current environment @@ -813,17 +781,14 @@ private void HandlePacket(INodePacket packet) HandleNodeBuildComplete(packet as NodeBuildComplete); break; -#if !CLR2COMPATIBILITY // Callback response packets - route to pending request case NodePacketType.TaskHostIsRunningMultipleNodesResponse: case NodePacketType.TaskHostCoresResponse: HandleCallbackResponse(packet); break; -#endif } } -#if !CLR2COMPATIBILITY /// /// Handles a callback response packet by completing the pending request's TaskCompletionSource. /// This is called on the main thread and unblocks the task thread waiting for the response. @@ -902,7 +867,6 @@ private TResponse SendCallbackRequestAndWaitForResponse(ITaskHostCall _pendingCallbackRequests.TryRemove(requestId, out _); } } -#endif /// /// Configure the task host according to the information received in the @@ -1015,10 +979,8 @@ 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. @@ -1046,17 +1008,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; } @@ -1072,7 +1027,6 @@ private void OnLinkStatusChanged(INodeEndpoint endpoint, LinkStatus status) case LinkStatus.Failed: _shutdownReason = NodeEngineShutdownReason.ConnectionFailed; -#if !CLR2COMPATIBILITY // Fail all pending callback requests so task threads unblock immediately // instead of waiting indefinitely for responses that will never arrive. foreach (var kvp in _pendingCallbackRequests) @@ -1083,7 +1037,6 @@ private void OnLinkStatusChanged(INodeEndpoint endpoint, LinkStatus status) "TaskHost lost connection to owning worker node during callback.")); } } -#endif _shutdownEvent.Set(); break; @@ -1137,9 +1090,9 @@ 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/Shared/BuildEnvironmentHelper.cs b/src/Shared/BuildEnvironmentHelper.cs index 92c2416a835..c1e6259bac9 100644 --- a/src/Shared/BuildEnvironmentHelper.cs +++ b/src/Shared/BuildEnvironmentHelper.cs @@ -448,11 +448,7 @@ private static string GetExecutingAssemblyPath() 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) diff --git a/src/Shared/CollectionHelpers.cs b/src/Shared/CollectionHelpers.cs index 17265682d2a..624502e3196 100644 --- a/src/Shared/CollectionHelpers.cs +++ b/src/Shared/CollectionHelpers.cs @@ -49,7 +49,6 @@ internal static bool ContainsValueAndIsEqual(this Dictionary dic return false; } -#if !CLR2COMPATIBILITY internal static bool SetEquivalent(IEnumerable a, IEnumerable b) { return a.ToHashSet().SetEquals(b); @@ -77,6 +76,5 @@ internal static bool DictionaryEquals(IReadOnlyDictionary a, IReadOn return true; } -#endif } } diff --git a/src/Shared/CommunicationsUtilities.cs b/src/Shared/CommunicationsUtilities.cs index 708e7151a99..3261eb11b45 100644 --- a/src/Shared/CommunicationsUtilities.cs +++ b/src/Shared/CommunicationsUtilities.cs @@ -20,13 +20,11 @@ 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 @@ -435,13 +433,11 @@ 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. @@ -501,14 +497,12 @@ internal static void SetEnvironmentVariable(string name, string value) } #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 @@ -517,10 +511,6 @@ 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() { @@ -528,7 +518,6 @@ private static FrozenDictionary GetEnvironmentVariablesWindows() // 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 { @@ -550,7 +539,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); @@ -559,7 +547,6 @@ private static FrozenDictionary GetEnvironmentVariablesWindows() { return lastState.EnvironmentVariables; } -#endif Dictionary table = new(200, StringComparer.OrdinalIgnoreCase); // Razzle has 150 environment variables @@ -604,11 +591,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++; @@ -621,25 +604,17 @@ 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 { @@ -659,7 +634,6 @@ 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. @@ -720,7 +694,6 @@ internal static FrozenDictionary GetEnvironmentVariables() return newState.EnvironmentVariables; } -#endif /// /// Updates the environment to match the provided dictionary. @@ -1184,12 +1157,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 ??= DebugUtils.DebugPath; if (String.IsNullOrEmpty(s_debugDumpPath)) { diff --git a/src/Shared/ErrorUtilities.cs b/src/Shared/ErrorUtilities.cs index ed4a38b1650..81d609cee19 100644 --- a/src/Shared/ErrorUtilities.cs +++ b/src/Shared/ErrorUtilities.cs @@ -132,12 +132,10 @@ 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 } /// @@ -521,7 +519,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. @@ -546,7 +543,6 @@ internal static void VerifyThrowArgumentLengthIfNotNull([MaybeNull] IReadOnly ThrowArgumentLength(parameterName); } } -#endif [DoesNotReturn] private static void ThrowArgumentLength(string? parameterName) diff --git a/src/Shared/ExceptionHandling.cs b/src/Shared/ExceptionHandling.cs index 77383b611cd..163ab94ef02 100644 --- a/src/Shared/ExceptionHandling.cs +++ b/src/Shared/ExceptionHandling.cs @@ -20,7 +20,7 @@ namespace Microsoft.Build.AppxPackage.Shared using Microsoft.Build.Shared.FileSystem; using System.Xml.Schema; using System.Runtime.Serialization; -#if !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS +#if !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS using Microsoft.Build.Shared.Debugging; using Microsoft.Build.Framework.Telemetry; #endif @@ -44,26 +44,7 @@ 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 +#if MICROSOFT_BUILD_ENGINE_OM_UNITTESTS Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); #else DebugUtils.DebugPath; @@ -141,7 +122,6 @@ internal static bool IsCriticalException(Exception e) return true; } -#if !CLR2COMPATIBILITY // Check if any critical exceptions var aggregateException = e as AggregateException; @@ -153,7 +133,6 @@ internal static bool IsCriticalException(Exception e) return true; } } -#endif return false; } @@ -350,12 +329,12 @@ internal static void UnhandledExceptionHandler(object sender, UnhandledException { Exception ex = (Exception)e.ExceptionObject; DumpExceptionToFile(ex); -#if !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS +#if !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS RecordCrashTelemetryForUnhandledException(ex); #endif } -#if !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS +#if !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS /// /// Records and immediately flushes crash telemetry for an unhandled exception. /// Best effort - must never throw, as the process is already crashing. diff --git a/src/Shared/FileSystem/FileSystems.cs b/src/Shared/FileSystem/FileSystems.cs index a0338849c0e..8ada584ef2c 100644 --- a/src/Shared/FileSystem/FileSystems.cs +++ b/src/Shared/FileSystem/FileSystems.cs @@ -13,9 +13,6 @@ internal static class FileSystems private static IFileSystem GetFileSystem() { -#if CLR2COMPATIBILITY - return MSBuildTaskHostFileSystem.Singleton(); -#else if (NativeMethodsShared.IsWindows) { return MSBuildOnWindowsFileSystem.Singleton(); @@ -24,7 +21,6 @@ private static IFileSystem GetFileSystem() { return ManagedFileSystem.Singleton(); } -#endif } } } diff --git a/src/Shared/FileUtilities.cs b/src/Shared/FileUtilities.cs index dbb88c31088..8ecd4ddc34a 100644 --- a/src/Shared/FileUtilities.cs +++ b/src/Shared/FileUtilities.cs @@ -2,19 +2,15 @@ // 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.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Globalization; +using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; @@ -48,10 +44,6 @@ internal static partial class FileUtilities /// internal static string cacheDirectory = null; -#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 @@ -354,7 +346,6 @@ 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"); @@ -363,9 +354,6 @@ internal static string TruncatePathToTrailingSegments(string path, int trailingS 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) @@ -394,9 +382,7 @@ 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 @@ -410,9 +396,7 @@ 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); @@ -437,12 +421,10 @@ 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) { @@ -518,7 +500,6 @@ 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 @@ -619,14 +600,10 @@ private static Span RemoveQuotes(Span path) 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 @@ -656,7 +633,6 @@ internal static bool LooksLikeUnixFilePath(ReadOnlySpan value, string base return (shouldCheckDirectory && DefaultFileSystem.DirectoryExists(Path.Combine(baseDirectory, directory.ToString()))) || (shouldCheckFileOrDirectory && DefaultFileSystem.FileOrDirectoryExists(value.ToString())); } -#endif /// /// Extracts the directory from the given file-spec. @@ -683,7 +659,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 @@ -710,7 +685,6 @@ internal static void DeleteSubdirectoriesNoThrow(string directory) // 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. @@ -1534,7 +1508,6 @@ private static bool PathsEqualNonAscii(string strA, string strB, int i, int leng return false; } -#if !CLR2COMPATIBILITY /// /// Clears the file existence cache. /// @@ -1542,7 +1515,6 @@ internal static void ClearFileExistenceCache() { FileExistenceCache.Clear(); } -#endif internal static void ReadFromStream(this Stream stream, byte[] content, int startIndex, int length) { @@ -1578,7 +1550,7 @@ internal static void ReadExactly(this Stream stream, byte[] buffer, int offset, { throw new EndOfStreamException(); } - offset +=read; + offset += read; count -= read; } } diff --git a/src/Shared/InterningBinaryReader.cs b/src/Shared/InterningBinaryReader.cs index 307cc68bdc9..3ce9cf42c2b 100644 --- a/src/Shared/InterningBinaryReader.cs +++ b/src/Shared/InterningBinaryReader.cs @@ -2,18 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Text; -using System.IO; +using System.Buffers; using System.Diagnostics; +using System.IO; +using System.Text; using System.Threading; - -#if !CLR2COMPATIBILITY -using System.Buffers; -#endif - -using ErrorUtilities = Microsoft.Build.Shared.ErrorUtilities; - using Microsoft.NET.StringTools; +using ErrorUtilities = Microsoft.Build.Shared.ErrorUtilities; #nullable disable @@ -152,12 +147,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,7 +164,6 @@ public override String ReadString() Debug.Assert(false, e.ToString()); throw; } -#if !CLR2COMPATIBILITY finally { // resultBuffer shall always be either Rented or null @@ -182,7 +172,6 @@ public override String ReadString() ArrayPool.Shared.Return(resultBuffer); } } -#endif } /// diff --git a/src/Shared/LogMessagePacketBase.cs b/src/Shared/LogMessagePacketBase.cs index 57c7ffe3c46..7b5b5305f55 100644 --- a/src/Shared/LogMessagePacketBase.cs +++ b/src/Shared/LogMessagePacketBase.cs @@ -509,11 +509,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) { @@ -903,10 +899,8 @@ private void WriteResponseFileUsedEventToStream(ResponseFileUsedEventArgs respon translator.Translate(ref filePath); -#if !CLR2COMPATIBILITY DateTime timestamp = responseFileUsedEventArgs.RawTimestamp; translator.Translate(ref timestamp); -#endif } #endregion @@ -1062,11 +1056,9 @@ 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/Shared/Modifiers.cs b/src/Shared/Modifiers.cs index d7b9a9fc95f..e089c7a719a 100644 --- a/src/Shared/Modifiers.cs +++ b/src/Shared/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,16 +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( - [ - 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, [ @@ -83,7 +69,6 @@ internal static class ItemSpecModifiers DefiningProjectName, DefiningProjectExtension, ]); -#endif /// /// Indicates if the given name is reserved for an item-spec modifier. diff --git a/src/Shared/NamedPipeUtil.cs b/src/Shared/NamedPipeUtil.cs index 0b85b05bacd..d5ab0e11b80 100644 --- a/src/Shared/NamedPipeUtil.cs +++ b/src/Shared/NamedPipeUtil.cs @@ -30,13 +30,7 @@ internal static string GetPlatformSpecificPipeName(string pipeName) // 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/Shared/NodeEndpointOutOfProcBase.cs b/src/Shared/NodeEndpointOutOfProcBase.cs index ae422e25e31..cfb964f38dd 100644 --- a/src/Shared/NodeEndpointOutOfProcBase.cs +++ b/src/Shared/NodeEndpointOutOfProcBase.cs @@ -6,11 +6,7 @@ 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; @@ -137,7 +133,7 @@ internal abstract class NodeEndpointOutOfProcBase : INodeEndpoint nameof(HandshakeComponents.FileVersionPrivate)]; #endif -#endregion + #endregion #region INodeEndpoint Events @@ -318,11 +314,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); @@ -421,7 +413,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 @@ -812,8 +804,8 @@ private void RunReadLoop( while (!exitLoop); } -#endregion + #endregion -#endregion + #endregion } } diff --git a/src/Shared/ReadOnlyEmptyDictionary.cs b/src/Shared/ReadOnlyEmptyDictionary.cs index 46b1b2738e9..a5bca3729c3 100644 --- a/src/Shared/ReadOnlyEmptyDictionary.cs +++ b/src/Shared/ReadOnlyEmptyDictionary.cs @@ -70,22 +70,12 @@ public bool IsReadOnly /// /// Gets empty collection /// - public ICollection Keys => -#if CLR2COMPATIBILITY - new K[0]; -#else - Array.Empty(); -#endif + public ICollection Keys => Array.Empty(); /// /// Gets empty collection /// - public ICollection Values => -#if CLR2COMPATIBILITY - new V[0]; -#else - Array.Empty(); -#endif + public ICollection Values => Array.Empty(); /// /// Is it fixed size diff --git a/src/Shared/TaskEngineAssemblyResolver.cs b/src/Shared/TaskEngineAssemblyResolver.cs index 44b243ab0f0..153ce441e68 100644 --- a/src/Shared/TaskEngineAssemblyResolver.cs +++ b/src/Shared/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/Shared/TaskHostConfiguration.cs b/src/Shared/TaskHostConfiguration.cs index 168648efb56..916655b28ed 100644 --- a/src/Shared/TaskHostConfiguration.cs +++ b/src/Shared/TaskHostConfiguration.cs @@ -525,25 +525,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/Shared/TaskHostTaskComplete.cs b/src/Shared/TaskHostTaskComplete.cs index 8255ca19865..aaa903827a2 100644 --- a/src/Shared/TaskHostTaskComplete.cs +++ b/src/Shared/TaskHostTaskComplete.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; -#if !CLR2COMPATIBILITY && FEATURE_REPORTFILEACCESSES +#if FEATURE_REPORTFILEACCESSES using Microsoft.Build.Experimental.FileAccess; #endif using Microsoft.Build.Shared; diff --git a/src/Shared/TempFileUtilities.cs b/src/Shared/TempFileUtilities.cs index 3533bf22763..c24c8923e64 100644 --- a/src/Shared/TempFileUtilities.cs +++ b/src/Shared/TempFileUtilities.cs @@ -221,11 +221,7 @@ public sealed class TempWorkingDirectory : IDisposable { public string Path { get; } - public TempWorkingDirectory(string sourcePath, -#if !CLR2COMPATIBILITY - [CallerMemberName] -#endif - string name = null) + public TempWorkingDirectory(string sourcePath, [CallerMemberName] string name = null) { Path = name == null ? GetTemporaryDirectory() diff --git a/src/Shared/XMakeAttributes.cs b/src/Shared/XMakeAttributes.cs index 47e15477b90..fbe39e96b32 100644 --- a/src/Shared/XMakeAttributes.cs +++ b/src/Shared/XMakeAttributes.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -#if !CLR2COMPATIBILITY using System.Runtime.InteropServices; -#endif #nullable disable @@ -420,7 +418,6 @@ internal static bool TryMergeArchitectureValues(string architectureA, string arc /// internal static string GetCurrentMSBuildArchitecture() { -#if !CLR2COMPATIBILITY string currentArchitecture = string.Empty; switch (RuntimeInformation.ProcessArchitecture) { @@ -440,9 +437,7 @@ internal static string GetCurrentMSBuildArchitecture() 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 83253e4489fc66e0e0122afafaab656b4ac15670 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 25 Feb 2026 11:50:18 -0800 Subject: [PATCH 02/27] Remove all TASKHOST code paths Now that all .NET 3.5 code has been consolidated in MSBuildTaskHost, the TASKHOST conditional compiler constant can be removed entirely from the code base. --- src/Framework/FileUtilities.cs | 8 +--- src/Framework/IConstrainedEqualityComparer.cs | 4 -- .../ImmutableDictionaryExtensions.cs | 2 - src/Shared/BinaryReaderExtensions.cs | 12 ------ src/Shared/BinaryWriterExtensions.cs | 12 ------ src/Shared/ExceptionHandling.cs | 2 - src/Shared/LogMessagePacketBase.cs | 43 ++----------------- src/Shared/Modifiers.cs | 4 -- src/Shared/NodePipeBase.cs | 17 ++------ src/Shared/NodePipeServer.cs | 19 ++------ src/Shared/TaskParameter.cs | 11 ++--- src/Shared/TranslatorHelpers.cs | 4 -- 12 files changed, 13 insertions(+), 125 deletions(-) diff --git a/src/Framework/FileUtilities.cs b/src/Framework/FileUtilities.cs index d861b6821ee..8c44519749f 100644 --- a/src/Framework/FileUtilities.cs +++ b/src/Framework/FileUtilities.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. -#if !TASKHOST using System.Threading; -#endif -#if NETFRAMEWORK && !TASKHOST +#if NETFRAMEWORK using Path = Microsoft.IO.Path; #else using System.IO; @@ -27,7 +25,6 @@ internal static class FrameworkFileUtilities internal static readonly char[] Slashes = [UnixDirectorySeparator, WindowsDirectorySeparator]; -#if !TASKHOST /// /// AsyncLocal working directory for use during property/item expansion in multithreaded mode. /// Set by MultiThreadedTaskEnvironmentDriver when building projects. null in multi-process mode. @@ -40,7 +37,6 @@ internal static string? CurrentThreadWorkingDirectory get => s_currentThreadWorkingDirectory.Value; set => s_currentThreadWorkingDirectory.Value = value; } -#endif /// /// Indicates if the given character is a slash in current OS. @@ -104,7 +100,6 @@ internal static string EnsureNoTrailingSlash(string path) return path; } -#if !TASKHOST /// /// Checks if the path contains backslashes on Unix. /// @@ -228,6 +223,5 @@ internal static AbsolutePath FixFilePath(AbsolutePath path) original: path.OriginalValue, ignoreRootedCheck: true); } -#endif } } diff --git a/src/Framework/IConstrainedEqualityComparer.cs b/src/Framework/IConstrainedEqualityComparer.cs index 3ecc49524e9..2132ce59c88 100644 --- a/src/Framework/IConstrainedEqualityComparer.cs +++ b/src/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/Framework/ImmutableDictionaryExtensions.cs b/src/Framework/ImmutableDictionaryExtensions.cs index ba7b04d91ae..47972976cca 100644 --- a/src/Framework/ImmutableDictionaryExtensions.cs +++ b/src/Framework/ImmutableDictionaryExtensions.cs @@ -16,7 +16,6 @@ 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. /// @@ -42,6 +41,5 @@ public static ImmutableDictionary SetItems( return builder.ToImmutable(); } -#endif } } diff --git a/src/Shared/BinaryReaderExtensions.cs b/src/Shared/BinaryReaderExtensions.cs index 9078401ba2f..e2907397e73 100644 --- a/src/Shared/BinaryReaderExtensions.cs +++ b/src/Shared/BinaryReaderExtensions.cs @@ -11,25 +11,19 @@ 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 @@ -54,9 +48,7 @@ public static int Read7BitEncodedInt(this BinaryReader reader) return count; } -#if !TASKHOST [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif public static DateTime ReadTimestamp(this BinaryReader reader) { long timestampTicks = reader.ReadInt64(); @@ -65,7 +57,6 @@ public static DateTime ReadTimestamp(this BinaryReader reader) return timestamp; } -#if !TASKHOST [MethodImpl(MethodImplOptions.AggressiveInlining)] public static BuildEventContext? ReadOptionalBuildEventContext(this BinaryReader reader) { @@ -91,11 +82,8 @@ public static BuildEventContext ReadBuildEventContext(this BinaryReader reader) 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/Shared/BinaryWriterExtensions.cs b/src/Shared/BinaryWriterExtensions.cs index 9cb458f4ec7..6df75ed8d67 100644 --- a/src/Shared/BinaryWriterExtensions.cs +++ b/src/Shared/BinaryWriterExtensions.cs @@ -11,9 +11,7 @@ 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 +25,7 @@ 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 +39,14 @@ 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,7 +61,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) { @@ -95,11 +86,8 @@ public static void WriteBuildEventContext(this BinaryWriter writer, BuildEventCo 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/Shared/ExceptionHandling.cs b/src/Shared/ExceptionHandling.cs index 163ab94ef02..c3b8f3af6e7 100644 --- a/src/Shared/ExceptionHandling.cs +++ b/src/Shared/ExceptionHandling.cs @@ -107,9 +107,7 @@ internal static bool IsCriticalException(Exception e) || e is ThreadAbortException || e is ThreadInterruptedException || e is AccessViolationException -#if !TASKHOST || e is CriticalTaskException -#endif #if !BUILDINGAPPXTASKS || e is InternalErrorException #endif diff --git a/src/Shared/LogMessagePacketBase.cs b/src/Shared/LogMessagePacketBase.cs index 7b5b5305f55..17589c57473 100644 --- a/src/Shared/LogMessagePacketBase.cs +++ b/src/Shared/LogMessagePacketBase.cs @@ -5,14 +5,10 @@ using System.Collections.Generic; using System.IO; using System.Reflection; - using Microsoft.Build.BackEnd; +using Microsoft.Build.Experimental.BuildCheck; using Microsoft.Build.Framework; - -#if !TASKHOST using Microsoft.Build.Framework.Telemetry; -using Microsoft.Build.Experimental.BuildCheck; -#endif #nullable disable @@ -263,14 +259,7 @@ 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. /// private static readonly Dictionary s_writeMethodCache = new Dictionary(); @@ -444,26 +433,7 @@ internal void ReadFromStream(ITranslator translator) 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); } @@ -544,8 +514,6 @@ private BuildEventArgs GetBuildEventArgFromId() 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(), @@ -575,7 +543,7 @@ private BuildEventArgs GetBuildEventArgFromId() 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) }; } @@ -598,12 +566,10 @@ 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; @@ -620,8 +586,6 @@ private LoggingEventType GetLoggingEventId(BuildEventArgs eventArg) { return LoggingEventType.ExternalProjectFinishedEvent; } - -#if !TASKHOST else if (eventType == typeof(ProjectEvaluationFinishedEventArgs)) { return LoggingEventType.ProjectEvaluationFinishedEvent; @@ -726,7 +690,6 @@ private LoggingEventType GetLoggingEventId(BuildEventArgs eventArg) { return LoggingEventType.WorkerNodeTelemetryEvent; } -#endif else if (eventType == typeof(TargetStartedEventArgs)) { return LoggingEventType.TargetStartedEvent; diff --git a/src/Shared/Modifiers.cs b/src/Shared/Modifiers.cs index e089c7a719a..968015dc321 100644 --- a/src/Shared/Modifiers.cs +++ b/src/Shared/Modifiers.cs @@ -185,11 +185,7 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS if (currentDirectory == null) { -#if !TASKHOST currentDirectory = FrameworkFileUtilities.CurrentThreadWorkingDirectory ?? string.Empty; -#else - currentDirectory = string.Empty; -#endif } modifiedItemSpec = GetFullPath(itemSpec, currentDirectory); diff --git a/src/Shared/NodePipeBase.cs b/src/Shared/NodePipeBase.cs index 00cfed04af6..d0a8f3c7cbf 100644 --- a/src/Shared/NodePipeBase.cs +++ b/src/Shared/NodePipeBase.cs @@ -2,17 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Buffers.Binary; using System.IO; using System.IO.Pipes; using System.Threading; -using Microsoft.Build.BackEnd; -using Microsoft.Build.Framework; - -#if !TASKHOST -using System.Buffers.Binary; using System.Threading.Tasks; +using Microsoft.Build.BackEnd; using Microsoft.Build.Eventing; -#endif +using Microsoft.Build.Framework; namespace Microsoft.Build.Internal { @@ -124,12 +121,8 @@ internal INodePacket ReadPacket() throw new IOException($"Incomplete header read. {headerBytesRead} of {HeaderLength} bytes read."); } -#if TASKHOST - int packetLength = BitConverter.ToInt32(_headerData, 1); -#else int packetLength = BinaryPrimitives.ReadInt32LittleEndian(new Span(_headerData, 1, 4)); MSBuildEventSource.Log.PacketReadSize(packetLength); -#endif // Read the packet. Set the buffer length now to avoid additional resizing during the read. _readBuffer.Position = 0; @@ -144,7 +137,6 @@ internal INodePacket ReadPacket() return DeserializePacket(); } -#if !TASKHOST internal async Task WritePacketAsync(INodePacket packet, CancellationToken cancellationToken = default) { int messageLength = WritePacketToBuffer(packet); @@ -194,7 +186,6 @@ internal async Task ReadPacketAsync(CancellationToken cancellationT return DeserializePacket(); } -#endif private int WritePacketToBuffer(INodePacket packet) { @@ -233,7 +224,6 @@ private int Read(byte[] buffer, int bytesToRead) return totalBytesRead; } -#if !TASKHOST private async ValueTask ReadAsync(byte[] buffer, int bytesToRead, CancellationToken cancellationToken) { int totalBytesRead = 0; @@ -256,7 +246,6 @@ private async ValueTask ReadAsync(byte[] buffer, int bytesToRead, Cancellat return totalBytesRead; } -#endif private INodePacket DeserializePacket() { diff --git a/src/Shared/NodePipeServer.cs b/src/Shared/NodePipeServer.cs index e5ff7c2ae25..c251080b156 100644 --- a/src/Shared/NodePipeServer.cs +++ b/src/Shared/NodePipeServer.cs @@ -8,13 +8,10 @@ using System.Security.AccessControl; using System.Security.Principal; #endif -using Microsoft.Build.BackEnd; -using Microsoft.Build.Shared; - -#if !TASKHOST using System.Threading; using System.Threading.Tasks; -#endif +using Microsoft.Build.BackEnd; +using Microsoft.Build.Shared; namespace Microsoft.Build.Internal { @@ -80,11 +77,7 @@ internal NodePipeServer(string pipeName, Handshake handshake, int maxNumberOfSer protected override PipeStream NodeStream => _pipeServer; -#if TASKHOST - internal LinkStatus WaitForConnection() -#else internal async Task WaitForConnectionAsync(CancellationToken cancellationToken) -#endif { DateTime originalWaitStartTime = DateTime.UtcNow; bool gotValidConnection = false; @@ -102,12 +95,6 @@ internal async Task WaitForConnectionAsync(CancellationToken cancell try { // Wait for a connection -#if TASKHOST - IAsyncResult resultForConnection = _pipeServer.BeginWaitForConnection(null, null); - CommunicationsUtilities.Trace("Waiting for connection {0} ms...", waitTimeRemaining); - bool connected = resultForConnection.AsyncWaitHandle.WaitOne(waitTimeRemaining, false); - _pipeServer.EndWaitForConnection(resultForConnection); -#else using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); cts.CancelAfter(waitTimeRemaining); bool connected = false; @@ -121,7 +108,7 @@ internal async Task WaitForConnectionAsync(CancellationToken cancell { connected = false; } -#endif + if (!connected) { CommunicationsUtilities.Trace("Connection timed out waiting a host to contact us. Exiting comm thread."); diff --git a/src/Shared/TaskParameter.cs b/src/Shared/TaskParameter.cs index 6c54e0e8f06..7e22fd27613 100644 --- a/src/Shared/TaskParameter.cs +++ b/src/Shared/TaskParameter.cs @@ -541,10 +541,8 @@ private class TaskParameterTaskItem : #endif ITaskItem, ITaskItem2, - ITranslatable -#if !TASKHOST - , IMetadataContainer -#endif + ITranslatable, + IMetadataContainer { /// /// The item spec @@ -750,7 +748,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. @@ -767,9 +764,7 @@ public void CopyMetadataTo(ITaskItem destinationItem) destinationItemAsMetadataContainer.ImportMetadata(metadataToImport); } - else -#endif - if (_customEscapedMetadata != null) + else if (_customEscapedMetadata != null) { foreach (KeyValuePair entry in _customEscapedMetadata) { diff --git a/src/Shared/TranslatorHelpers.cs b/src/Shared/TranslatorHelpers.cs index 52bf3941657..b195d17daf1 100644 --- a/src/Shared/TranslatorHelpers.cs +++ b/src/Shared/TranslatorHelpers.cs @@ -2,10 +2,8 @@ // 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,7 +176,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, @@ -257,7 +254,6 @@ public static void TranslateDictionary( dictionary = (ImmutableDictionary)localDict; } } -#endif public static void TranslateHashSet( this ITranslator translator, From 5e8c0ef332b517c0e994b1f2ed58072f8dd8b5f0 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 25 Feb 2026 12:04:30 -0800 Subject: [PATCH 03/27] Remove all NET35 code paths (except StringTools) Now that all .NET 3.5 code has been consolidated in MSBuildTaskHost, the NET35 conditionally compiled code can be removed entirely -- except for StringTools, which compiles for .NET 3.5. --- .../BackEnd/AssemblyTaskFactory_Tests.cs | 28 --- .../BackEnd/TaskExecutionHost_Tests.cs | 6 - .../BackEnd/TaskHostConfiguration_Tests.cs | 196 +++++------------- .../Components/RequestBuilder/TaskBuilder.cs | 2 - .../TaskExecutionHost/TaskExecutionHost.cs | 10 - .../TaskFactories/AssemblyTaskFactory.cs | 4 - .../Instance/TaskFactories/TaskHostTask.cs | 8 - .../OutOfProcTaskAppDomainWrapperBase.cs | 12 +- src/MSBuild/OutOfProcTaskHostNode.cs | 2 - src/Shared/AssemblyLoadInfo.cs | 4 - src/Shared/CopyOnWriteDictionary.cs | 15 +- src/Shared/FileUtilitiesRegex.cs | 2 - src/Shared/LoadedType.cs | 9 - src/Shared/ReadOnlyEmptyDictionary.cs | 19 -- src/Shared/TaskHostConfiguration.cs | 8 - src/Shared/TaskParameterTypeVerifier.cs | 3 - 16 files changed, 54 insertions(+), 274 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs b/src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs index 82e190d1e1d..f26704bbc6e 100644 --- a/src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs +++ b/src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs @@ -250,9 +250,7 @@ public void VerifyGoodTaskInstantiation() new MockHost(), TaskHostParameters.Empty, projectFile: "proj.proj", -#if !NET35 hostServices: null, -#endif #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -290,9 +288,7 @@ public void VerifyMatchingTaskParametersDontLaunchTaskHost1() new MockHost(), taskParameters, projectFile: "proj.proj", -#if !NET35 hostServices: null, -#endif #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -330,9 +326,7 @@ public void VerifyMatchingTaskParametersDontLaunchTaskHost2() new MockHost(), taskParameters, projectFile: "proj.proj", -#if !NET35 hostServices: null, -#endif #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -372,9 +366,7 @@ public void VerifyMatchingUsingTaskParametersDontLaunchTaskHost1() new MockHost(), TaskHostParameters.Empty, projectFile: "proj.proj", -#if !NET35 hostServices: null, -#endif #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -414,9 +406,7 @@ public void VerifyMatchingUsingTaskParametersDontLaunchTaskHost2() new MockHost(), TaskHostParameters.Empty, projectFile: "proj.proj", -#if !NET35 hostServices: null, -#endif #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -458,9 +448,7 @@ public void VerifyMatchingParametersDontLaunchTaskHost() new MockHost(), taskParameters, projectFile: "proj.proj", -#if !NET35 hostServices: null, -#endif #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -500,9 +488,7 @@ public void VerifyNonmatchingUsingTaskParametersLaunchTaskHost() new MockHost(), TaskHostParameters.Empty, projectFile: "proj.proj", -#if !NET35 hostServices: null, -#endif #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -540,9 +526,7 @@ public void VerifyNonmatchingTaskParametersLaunchTaskHost() new MockHost(), taskParameters, projectFile: "proj.proj", -#if !NET35 hostServices: null, -#endif #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -584,9 +568,7 @@ public void VerifyNonmatchingParametersLaunchTaskHost() new MockHost(), taskParameters, projectFile: "proj.proj", -#if !NET35 hostServices: null, -#endif #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -624,9 +606,7 @@ public void VerifyExplicitlyLaunchTaskHost() new MockHost(), TaskHostParameters.Empty, projectFile: "proj.proj", -#if !NET35 hostServices: null, -#endif #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -666,9 +646,7 @@ public void VerifyExplicitlyLaunchTaskHostEvenIfParametersMatch1() new MockHost(), TaskHostParameters.Empty, projectFile: "proj.proj", -#if !NET35 hostServices: null, -#endif #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -708,9 +686,7 @@ public void VerifyExplicitlyLaunchTaskHostEvenIfParametersMatch2() new MockHost(), taskParameters, projectFile: "proj.proj", -#if !NET35 hostServices: null, -#endif #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -751,9 +727,7 @@ public void VerifySameFactoryCanGenerateDifferentTaskInstances() new MockHost(), TaskHostParameters.Empty, projectFile: "proj.proj", -#if !NET35 hostServices: null, -#endif #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -783,9 +757,7 @@ public void VerifySameFactoryCanGenerateDifferentTaskInstances() new MockHost(), taskParameters, projectFile: "proj.proj", -#if !NET35 hostServices: null, -#endif #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif diff --git a/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs b/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs index 8be60f5b825..6b11c8b62fd 100644 --- a/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs @@ -996,9 +996,7 @@ public void TestTaskResolutionFailureWithUsingTask() #if FEATURE_APPDOMAIN null, #endif -#if !NET35 null, -#endif false, CancellationToken.None, TaskEnvironmentHelper.CreateForTest()); @@ -1029,9 +1027,7 @@ public void TestTaskResolutionFailureWithNoUsingTask() #if FEATURE_APPDOMAIN null, #endif -#if !NET35 null, -#endif false, CancellationToken.None, TaskEnvironmentHelper.CreateForTest()); @@ -1277,9 +1273,7 @@ private void InitializeHost(bool throwOnExecute) #if FEATURE_APPDOMAIN null, #endif -#if !NET35 null, -#endif false, CancellationToken.None, TaskEnvironmentHelper.CreateForTest()); diff --git a/src/Build.UnitTests/BackEnd/TaskHostConfiguration_Tests.cs b/src/Build.UnitTests/BackEnd/TaskHostConfiguration_Tests.cs index 8b77b14045c..d67845b31c1 100644 --- a/src/Build.UnitTests/BackEnd/TaskHostConfiguration_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskHostConfiguration_Tests.cs @@ -43,17 +43,11 @@ public void ConstructorWithNullName() buildProcessEnvironment: null, culture: Thread.CurrentThread.CurrentCulture, uiCulture: Thread.CurrentThread.CurrentUICulture, -#if !NET35 - null, -#endif -#if FEATURE_APPDOMAIN - appDomainSetup: + hostServices: null, #if FEATURE_APPDOMAIN - null, + appDomainSetup: null, #endif - lineNumberOfTask: -#endif - 1, + lineNumberOfTask: 1, columnNumberOfTask: 1, projectFileOfTask: @"c:\my project\myproj.proj", targetName: "", @@ -83,17 +77,11 @@ public void ConstructorWithEmptyName() buildProcessEnvironment: null, culture: Thread.CurrentThread.CurrentCulture, uiCulture: Thread.CurrentThread.CurrentUICulture, -#if !NET35 - null, -#endif + hostServices: null, #if FEATURE_APPDOMAIN - appDomainSetup: -#if FEATURE_APPDOMAIN - null, -#endif - lineNumberOfTask: + appDomainSetup: null, #endif - 1, + lineNumberOfTask: 1, columnNumberOfTask: 1, projectFileOfTask: @"c:\my project\myproj.proj", targetName: "", @@ -123,17 +111,11 @@ public void ConstructorWithNullLocation() buildProcessEnvironment: null, culture: Thread.CurrentThread.CurrentCulture, uiCulture: Thread.CurrentThread.CurrentUICulture, -#if !NET35 - null, -#endif -#if FEATURE_APPDOMAIN - appDomainSetup: + hostServices: null, #if FEATURE_APPDOMAIN - null, + appDomainSetup: null, #endif - lineNumberOfTask: -#endif - 1, + lineNumberOfTask: 1, columnNumberOfTask: 1, projectFileOfTask: @"c:\my project\myproj.proj", targetName: "", @@ -165,17 +147,11 @@ public void ConstructorWithEmptyLocation() buildProcessEnvironment: null, culture: Thread.CurrentThread.CurrentCulture, uiCulture: Thread.CurrentThread.CurrentUICulture, -#if !NET35 - null, -#endif + hostServices: null, #if FEATURE_APPDOMAIN - appDomainSetup: -#if FEATURE_APPDOMAIN - null, -#endif - lineNumberOfTask: + appDomainSetup: null, #endif - 1, + lineNumberOfTask: 1, columnNumberOfTask: 1, projectFileOfTask: @"c:\my project\myproj.proj", targetName: "", @@ -205,17 +181,11 @@ public void TestValidConstructors() buildProcessEnvironment: null, culture: Thread.CurrentThread.CurrentCulture, uiCulture: Thread.CurrentThread.CurrentUICulture, -#if !NET35 - null, -#endif -#if FEATURE_APPDOMAIN - appDomainSetup: + hostServices: null, #if FEATURE_APPDOMAIN - null, + appDomainSetup: null, #endif - lineNumberOfTask: -#endif - 1, + lineNumberOfTask: 1, columnNumberOfTask: 1, projectFileOfTask: @"c:\my project\myproj.proj", targetName: "TargetName", @@ -236,17 +206,11 @@ public void TestValidConstructors() buildProcessEnvironment: null, culture: Thread.CurrentThread.CurrentCulture, uiCulture: Thread.CurrentThread.CurrentUICulture, -#if !NET35 - null, -#endif -#if FEATURE_APPDOMAIN - appDomainSetup: + hostServices: null, #if FEATURE_APPDOMAIN - null, + appDomainSetup: null, #endif - lineNumberOfTask: -#endif - 1, + lineNumberOfTask: 1, columnNumberOfTask: 1, projectFileOfTask: @"c:\my project\myproj.proj", targetName: "TargetName", @@ -268,17 +232,11 @@ public void TestValidConstructors() buildProcessEnvironment: null, culture: Thread.CurrentThread.CurrentCulture, uiCulture: Thread.CurrentThread.CurrentUICulture, -#if !NET35 - null, -#endif -#if FEATURE_APPDOMAIN - appDomainSetup: + hostServices: null, #if FEATURE_APPDOMAIN - null, + appDomainSetup: null, #endif - lineNumberOfTask: -#endif - 1, + lineNumberOfTask: 1, columnNumberOfTask: 1, projectFileOfTask: @"c:\my project\myproj.proj", targetName: "TargetName", @@ -305,17 +263,11 @@ public void TestValidConstructors() buildProcessEnvironment: null, culture: Thread.CurrentThread.CurrentCulture, uiCulture: Thread.CurrentThread.CurrentUICulture, -#if !NET35 - null, -#endif -#if FEATURE_APPDOMAIN - appDomainSetup: + hostServices: null, #if FEATURE_APPDOMAIN - null, + appDomainSetup: null, #endif - lineNumberOfTask: -#endif - 1, + lineNumberOfTask: 1, columnNumberOfTask: 1, projectFileOfTask: @"c:\my project\myproj.proj", targetName: "TargetName", @@ -342,17 +294,11 @@ public void TestValidConstructors() buildProcessEnvironment: null, culture: Thread.CurrentThread.CurrentCulture, uiCulture: Thread.CurrentThread.CurrentUICulture, -#if !NET35 - null, -#endif -#if FEATURE_APPDOMAIN - appDomainSetup: + hostServices: null, #if FEATURE_APPDOMAIN - null, -#endif - lineNumberOfTask: + appDomainSetup: null, #endif - 1, + lineNumberOfTask: 1, columnNumberOfTask: 1, projectFileOfTask: @"c:\my project\myproj.proj", targetName: "TargetName", @@ -386,17 +332,11 @@ public void TestTranslationWithNullDictionary() buildProcessEnvironment: null, culture: Thread.CurrentThread.CurrentCulture, uiCulture: Thread.CurrentThread.CurrentUICulture, -#if !NET35 - null, -#endif + hostServices: null, #if FEATURE_APPDOMAIN - appDomainSetup: -#if FEATURE_APPDOMAIN - null, -#endif - lineNumberOfTask: + appDomainSetup: null, #endif - 1, + lineNumberOfTask: 1, columnNumberOfTask: 1, projectFileOfTask: @"c:\my project\myproj.proj", targetName: "TargetName", @@ -442,9 +382,7 @@ public void TestTranslationWithAppDomainSetup(byte[] configBytes) buildProcessEnvironment: null, culture: Thread.CurrentThread.CurrentCulture, uiCulture: Thread.CurrentThread.CurrentUICulture, -#if !NET35 - null, -#endif + hostServices: null, appDomainSetup: setup, lineNumberOfTask: 1, columnNumberOfTask: 1, @@ -499,17 +437,11 @@ public void TestTranslationWithEmptyDictionary() buildProcessEnvironment: null, culture: Thread.CurrentThread.CurrentCulture, uiCulture: Thread.CurrentThread.CurrentUICulture, -#if !NET35 - null, -#endif -#if FEATURE_APPDOMAIN - appDomainSetup: + hostServices: null, #if FEATURE_APPDOMAIN - null, + appDomainSetup: null, #endif - lineNumberOfTask: -#endif - 1, + lineNumberOfTask: 1, columnNumberOfTask: 1, projectFileOfTask: @"c:\my project\myproj.proj", targetName: "TargetName", @@ -555,17 +487,11 @@ public void TestTranslationWithValueTypesInDictionary() buildProcessEnvironment: null, culture: Thread.CurrentThread.CurrentCulture, uiCulture: Thread.CurrentThread.CurrentUICulture, -#if !NET35 - null, -#endif -#if FEATURE_APPDOMAIN - appDomainSetup: + hostServices: null, #if FEATURE_APPDOMAIN - null, + appDomainSetup: null, #endif - lineNumberOfTask: -#endif - 1, + lineNumberOfTask: 1, columnNumberOfTask: 1, projectFileOfTask: @"c:\my project\myproj.proj", targetName: "TargetName", @@ -609,17 +535,11 @@ public void TestTranslationWithITaskItemInDictionary() buildProcessEnvironment: null, culture: Thread.CurrentThread.CurrentCulture, uiCulture: Thread.CurrentThread.CurrentUICulture, -#if !NET35 - null, -#endif + hostServices: null, #if FEATURE_APPDOMAIN - appDomainSetup: -#if FEATURE_APPDOMAIN - null, -#endif - lineNumberOfTask: + appDomainSetup: null, #endif - 1, + lineNumberOfTask: 1, columnNumberOfTask: 1, projectFileOfTask: @"c:\my project\myproj.proj", targetName: "TargetName", @@ -662,17 +582,11 @@ public void TestTranslationWithITaskItemArrayInDictionary() buildProcessEnvironment: null, culture: Thread.CurrentThread.CurrentCulture, uiCulture: Thread.CurrentThread.CurrentUICulture, -#if !NET35 - null, -#endif + hostServices: null, #if FEATURE_APPDOMAIN - appDomainSetup: -#if FEATURE_APPDOMAIN - null, -#endif - lineNumberOfTask: + appDomainSetup: null, #endif - 1, + lineNumberOfTask: 1, columnNumberOfTask: 1, projectFileOfTask: @"c:\my project\myproj.proj", targetName: "TargetName", @@ -722,17 +636,11 @@ public void TestTranslationWithWarningsAsErrors() buildProcessEnvironment: null, culture: Thread.CurrentThread.CurrentCulture, uiCulture: Thread.CurrentThread.CurrentUICulture, -#if !NET35 - null, -#endif + hostServices: null, #if FEATURE_APPDOMAIN - appDomainSetup: -#if FEATURE_APPDOMAIN - null, -#endif - lineNumberOfTask: + appDomainSetup: null, #endif - 1, + lineNumberOfTask: 1, columnNumberOfTask: 1, projectFileOfTask: @"c:\my project\myproj.proj", targetName: "TargetName", @@ -777,17 +685,11 @@ public void TestTranslationWithWarningsAsMessages() buildProcessEnvironment: null, culture: Thread.CurrentThread.CurrentCulture, uiCulture: Thread.CurrentThread.CurrentUICulture, -#if !NET35 - null, -#endif + hostServices: null, #if FEATURE_APPDOMAIN - appDomainSetup: -#if FEATURE_APPDOMAIN - null, -#endif - lineNumberOfTask: + appDomainSetup: null, #endif - 1, + lineNumberOfTask: 1, columnNumberOfTask: 1, projectFileOfTask: @"c:\my project\myproj.proj", targetName: "TargetName", diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs index 51857855ddd..a6c6592bca0 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs @@ -332,9 +332,7 @@ private async ValueTask ExecuteTask(TaskExecutionMode mode, Look #if FEATURE_APPDOMAIN taskHost.AppDomainSetup, #endif -#if !NET35 _buildRequestEntry.Request.HostServices, -#endif taskHost.IsOutOfProc, _cancellationToken, _buildRequestEntry.TaskEnvironment); diff --git a/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs b/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs index 670a3866842..46273a65ec1 100644 --- a/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs +++ b/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs @@ -231,9 +231,7 @@ internal TaskFactoryWrapper _UNITTESTONLY_TaskFactoryWrapper set => _taskFactoryWrapper = value; } -#if !NET35 private HostServices _hostServices; -#endif #if FEATURE_APPDOMAIN /// @@ -273,9 +271,7 @@ public void InitializeForTask( #if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, #endif -#if !NET35 HostServices hostServices, -#endif bool isOutOfProc, CancellationToken cancellationToken, TaskEnvironment taskEnvironment) @@ -292,9 +288,7 @@ public void InitializeForTask( #if FEATURE_APPDOMAIN AppDomainSetup = appDomainSetup; #endif -#if !NET35 _hostServices = hostServices; -#endif IsOutOfProc = isOutOfProc; TaskEnvironment = taskEnvironment; } @@ -1002,9 +996,7 @@ private ITask InstantiateTask(int scheduledNodeId, in TaskHostParameters taskIde _buildComponentHost, taskIdentityParameters, _projectFile, -#if !NET35 _hostServices, -#endif #if FEATURE_APPDOMAIN AppDomainSetup, #endif @@ -1852,9 +1844,7 @@ private ITask CreateTaskHostTaskForOutOfProcFactory( #if FEATURE_APPDOMAIN AppDomainSetup, #endif -#if !NET35 _hostServices, -#endif scheduledNodeId, TaskEnvironment); } diff --git a/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs b/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs index 7b080d2b3b5..a33d9f0226d 100644 --- a/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs +++ b/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs @@ -318,9 +318,7 @@ internal ITask CreateTaskInstance( IBuildComponentHost buildComponentHost, in TaskHostParameters taskIdentityParameters, string projectFile, -#if !NET35 HostServices hostServices, -#endif #if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, #endif @@ -385,9 +383,7 @@ internal ITask CreateTaskInstance( #if FEATURE_APPDOMAIN appDomainSetup, #endif -#if !NET35 hostServices, -#endif scheduledNodeId, taskEnvironment: taskEnvironment); return task; diff --git a/src/Build/Instance/TaskFactories/TaskHostTask.cs b/src/Build/Instance/TaskFactories/TaskHostTask.cs index 62729224986..269362b1c7a 100644 --- a/src/Build/Instance/TaskFactories/TaskHostTask.cs +++ b/src/Build/Instance/TaskFactories/TaskHostTask.cs @@ -146,9 +146,7 @@ internal class TaskHostTask : IGeneratedTask, ICancelableTask, INodePacketFactor /// private bool _useSidecarTaskHost = false; -#if !NET35 private readonly HostServices _hostServices; -#endif /// /// The project file path that requests task execution. @@ -174,9 +172,7 @@ public TaskHostTask( #if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, #endif -#if !NET35 HostServices hostServices, -#endif int scheduledNodeId, TaskEnvironment taskEnvironment) { @@ -192,9 +188,7 @@ public TaskHostTask( #if FEATURE_APPDOMAIN _appDomainSetup = appDomainSetup; #endif -#if !NET35 _hostServices = hostServices; -#endif _projectFile = projectFile; _taskHostParameters = taskHostParameters; _useSidecarTaskHost = useSidecarTaskHost; @@ -336,9 +330,7 @@ public bool Execute() (IDictionary)_taskEnvironment.GetEnvironmentVariables(), _buildComponentHost.BuildParameters.Culture, _buildComponentHost.BuildParameters.UICulture, -#if !NET35 _hostServices, -#endif #if FEATURE_APPDOMAIN _appDomainSetup, #endif diff --git a/src/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs b/src/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs index 9fe51b52a9a..7fb11f41a27 100644 --- a/src/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs +++ b/src/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs @@ -9,11 +9,9 @@ using System.Reflection; using Microsoft.Build.BackEnd; +using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.Shared; -#if !NET35 -using Microsoft.Build.Execution; -#endif #nullable disable @@ -58,9 +56,7 @@ 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 @@ -110,9 +106,7 @@ internal OutOfProcTaskHostTaskResult ExecuteTask( #if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, #endif -#if !NET35 HostServices hostServices, -#endif IDictionary taskParams) { buildEngine = oopTaskHostNode; @@ -121,9 +115,7 @@ internal OutOfProcTaskHostTaskResult ExecuteTask( #if FEATURE_APPDOMAIN _taskAppDomain = null; #endif -#if !NET35 _hostServices = hostServices; -#endif wrappedTask = null; LoadedType taskType = null; @@ -344,12 +336,10 @@ 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; } diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index dcb4be5d2f8..83b7be54060 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -1109,9 +1109,7 @@ private void RunTask(object state) #if FEATURE_APPDOMAIN taskConfiguration.AppDomainSetup, #endif -#if !NET35 taskConfiguration.HostServices, -#endif taskParams); } catch (ThreadAbortException) diff --git a/src/Shared/AssemblyLoadInfo.cs b/src/Shared/AssemblyLoadInfo.cs index 4ced73e1360..931d96a5480 100644 --- a/src/Shared/AssemblyLoadInfo.cs +++ b/src/Shared/AssemblyLoadInfo.cs @@ -228,11 +228,7 @@ internal override string AssemblyLocation /// internal override bool IsInlineTask { -#if !NET35 get { return _assemblyFile?.EndsWith(TaskFactoryUtilities.InlineTaskSuffix, StringComparison.OrdinalIgnoreCase) == true; } -#else - get { return false; } -#endif } } } diff --git a/src/Shared/CopyOnWriteDictionary.cs b/src/Shared/CopyOnWriteDictionary.cs index b0948ed17a2..f11c87f5b5c 100644 --- a/src/Shared/CopyOnWriteDictionary.cs +++ b/src/Shared/CopyOnWriteDictionary.cs @@ -28,7 +28,6 @@ namespace Microsoft.Build.Collections [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 @@ -42,8 +41,6 @@ internal class CopyOnWriteDictionary : IDictionary, IDictionary, I /// allocating new comparers objects. /// private static readonly ImmutableDictionary OrdinalIgnoreCaseComparerDictionaryPrototype = ImmutableDictionary.Create(StringComparer.OrdinalIgnoreCase); -#endif - /// /// The backing dictionary. @@ -84,15 +81,11 @@ protected CopyOnWriteDictionary(SerializationInfo info, StreamingContext context 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 + ? NameComparerDictionaryPrototype + : keyComparer == StringComparer.OrdinalIgnoreCase + ? OrdinalIgnoreCaseComparerDictionaryPrototype + : ImmutableDictionary.Create(keyComparer); } /// diff --git a/src/Shared/FileUtilitiesRegex.cs b/src/Shared/FileUtilitiesRegex.cs index 6a27e415aec..03a29979c53 100644 --- a/src/Shared/FileUtilitiesRegex.cs +++ b/src/Shared/FileUtilitiesRegex.cs @@ -155,9 +155,7 @@ 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/Shared/LoadedType.cs b/src/Shared/LoadedType.cs index 18f59bd164f..a108e072da7 100644 --- a/src/Shared/LoadedType.cs +++ b/src/Shared/LoadedType.cs @@ -56,7 +56,6 @@ internal LoadedType( 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. @@ -161,12 +160,6 @@ internal LoadedType( } } } -#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,9 +229,7 @@ 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/Shared/ReadOnlyEmptyDictionary.cs b/src/Shared/ReadOnlyEmptyDictionary.cs index a5bca3729c3..a769a5156e8 100644 --- a/src/Shared/ReadOnlyEmptyDictionary.cs +++ b/src/Shared/ReadOnlyEmptyDictionary.cs @@ -298,22 +298,3 @@ public void CopyTo(System.Array array, int index) } } } - -#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/Shared/TaskHostConfiguration.cs b/src/Shared/TaskHostConfiguration.cs index 916655b28ed..0383ca84911 100644 --- a/src/Shared/TaskHostConfiguration.cs +++ b/src/Shared/TaskHostConfiguration.cs @@ -95,9 +95,7 @@ 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 +165,7 @@ public TaskHostConfiguration( IDictionary buildProcessEnvironment, CultureInfo culture, CultureInfo uiCulture, -#if !NET35 HostServices hostServices, -#endif #if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, #endif @@ -206,9 +202,7 @@ public TaskHostConfiguration( _culture = culture; _uiCulture = uiCulture; -#if !NET35 _hostServices = hostServices; -#endif #if FEATURE_APPDOMAIN _appDomainSetup = appDomainSetup; #endif @@ -308,7 +302,6 @@ public AppDomainSetup AppDomainSetup } #endif -#if !NET35 /// /// The HostServices to be used by the task host. /// @@ -318,7 +311,6 @@ public HostServices HostServices get { return _hostServices; } } -#endif /// /// Line number where the instance of this task is defined. diff --git a/src/Shared/TaskParameterTypeVerifier.cs b/src/Shared/TaskParameterTypeVerifier.cs index 2b7d6160d84..0390b5c1de4 100644 --- a/src/Shared/TaskParameterTypeVerifier.cs +++ b/src/Shared/TaskParameterTypeVerifier.cs @@ -4,9 +4,6 @@ using System; using System.Reflection; using Microsoft.Build.Framework; -#if NET35 -using Microsoft.Build.Shared; -#endif #nullable disable From f8699df23189a4f05072ce48060d8121e809c37c Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 27 Feb 2026 14:20:02 -0800 Subject: [PATCH 04/27] Move FileUtilities.IsPathTooLong to Framework Move FileUtilities.IsPathTooLong to FrameworkFileUtilities.IsPathTooLong. --- src/Framework/FileUtilities.cs | 3 +++ src/Shared/FileSystem/WindowsFileSystem.cs | 3 ++- src/Shared/FileUtilities.cs | 11 ++--------- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Framework/FileUtilities.cs b/src/Framework/FileUtilities.cs index 8c44519749f..d40882421dd 100644 --- a/src/Framework/FileUtilities.cs +++ b/src/Framework/FileUtilities.cs @@ -100,6 +100,9 @@ internal static string EnsureNoTrailingSlash(string path) return path; } + public static bool IsPathTooLong(string path) + => path.Length >= NativeMethods.MaxPath; // >= not > because MAX_PATH assumes a trailing null + /// /// Checks if the path contains backslashes on Unix. /// diff --git a/src/Shared/FileSystem/WindowsFileSystem.cs b/src/Shared/FileSystem/WindowsFileSystem.cs index 0a6ee86805f..cf7b9f20c4f 100644 --- a/src/Shared/FileSystem/WindowsFileSystem.cs +++ b/src/Shared/FileSystem/WindowsFileSystem.cs @@ -7,6 +7,7 @@ using System.IO; using System.Runtime.InteropServices; using System.Runtime.Versioning; +using Microsoft.Build.Framework; namespace Microsoft.Build.Shared.FileSystem @@ -54,7 +55,7 @@ public override IEnumerable EnumerateFileSystemEntries(string path, stri public override bool DirectoryExists(string path) { - if (!string.IsNullOrEmpty(path) && FileUtilities.IsPathTooLong(path)) + if (!string.IsNullOrEmpty(path) && FrameworkFileUtilities.IsPathTooLong(path)) { // If the path is too long, we can't check if it exists on windows string message = ResourceUtilities.FormatString(AssemblyResources.GetString("Shared.PathTooLong"), path, NativeMethodsShared.MaxPath); diff --git a/src/Shared/FileUtilities.cs b/src/Shared/FileUtilities.cs index 8ecd4ddc34a..03d515ad43d 100644 --- a/src/Shared/FileUtilities.cs +++ b/src/Shared/FileUtilities.cs @@ -433,7 +433,7 @@ private static string GetFullPath(string path) { string uncheckedFullPath = NativeMethodsShared.GetFullPath(path); - if (IsPathTooLong(uncheckedFullPath)) + if (FrameworkFileUtilities.IsPathTooLong(uncheckedFullPath)) { string message = ResourceUtilities.FormatString(AssemblyResources.GetString("Shared.PathTooLong"), path, NativeMethodsShared.MaxPath); throw new PathTooLongException(message); @@ -1186,20 +1186,13 @@ internal static string MakeRelative(string basePath, string path) /// internal static string AttemptToShortenPath(string path) { - if (IsPathTooLong(path) || IsPathTooLongIfRooted(path)) + if (FrameworkFileUtilities.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; From 12e702099226d85774326c4c9191f26b25e0deb6 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 27 Feb 2026 14:46:01 -0800 Subject: [PATCH 05/27] Move FileSystem to Framework - Move code from src/Shared/FileSystem to Microsoft.Build.Framework/FileSystem - Move "Shared.PathTooLong" resource string from Strings.Shared.resx to Microsoft.Build.Framework/Resources/SR.resx. (and move translated strings.) - Remove Shared/FileSystemSources.proj from Microsoft.Build.Engine.OM.UnitTests, Microsoft.Build, Microsoft.Build.Tasks, Microsoft.Build.Utilities, and MSBuild - Delete Shared/FileSystemSources.proj --- .../Microsoft.Build.Engine.OM.UnitTests.csproj | 3 +-- src/Build/Microsoft.Build.csproj | 3 +-- .../FileSystem/CachingFileSystemWrapper.cs | 0 src/{Shared => Framework}/FileSystem/FileSystems.cs | 3 ++- src/{Shared => Framework}/FileSystem/IFileSystem.cs | 0 .../FileSystem/MSBuildOnWindowsFileSystem.cs | 1 - .../FileSystem/ManagedFileSystem.cs | 11 ++++------- .../FileSystem/NativeWin32Exception.cs | 0 .../FileSystem/SafeFileHandle.cs | 0 .../FileSystem/WindowsFileSystem.cs | 12 ++++++------ .../FileSystem/WindowsNative.cs | 0 src/Framework/Resources/SR.resx | 3 +++ src/Framework/Resources/xlf/SR.cs.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.de.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.es.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.fr.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.it.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.ja.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.ko.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.pl.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.pt-BR.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.ru.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.tr.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.zh-Hans.xlf | 7 ++++++- src/Framework/Resources/xlf/SR.zh-Hant.xlf | 7 ++++++- src/MSBuild/MSBuild.csproj | 3 +-- src/Shared/FileSystemSources.proj | 7 ------- src/Shared/FileUtilities.cs | 3 +-- src/Shared/Resources/Strings.shared.resx | 3 --- src/Shared/Resources/xlf/Strings.shared.cs.xlf | 5 ----- src/Shared/Resources/xlf/Strings.shared.de.xlf | 5 ----- src/Shared/Resources/xlf/Strings.shared.es.xlf | 5 ----- src/Shared/Resources/xlf/Strings.shared.fr.xlf | 5 ----- src/Shared/Resources/xlf/Strings.shared.it.xlf | 5 ----- src/Shared/Resources/xlf/Strings.shared.ja.xlf | 5 ----- src/Shared/Resources/xlf/Strings.shared.ko.xlf | 5 ----- src/Shared/Resources/xlf/Strings.shared.pl.xlf | 5 ----- src/Shared/Resources/xlf/Strings.shared.pt-BR.xlf | 5 ----- src/Shared/Resources/xlf/Strings.shared.ru.xlf | 5 ----- src/Shared/Resources/xlf/Strings.shared.tr.xlf | 5 ----- src/Shared/Resources/xlf/Strings.shared.zh-Hans.xlf | 5 ----- src/Shared/Resources/xlf/Strings.shared.zh-Hant.xlf | 5 ----- src/Tasks/Microsoft.Build.Tasks.csproj | 3 +-- src/Utilities/Microsoft.Build.Utilities.csproj | 3 +-- 44 files changed, 99 insertions(+), 115 deletions(-) rename src/{Shared => Framework}/FileSystem/CachingFileSystemWrapper.cs (100%) rename src/{Shared => Framework}/FileSystem/FileSystems.cs (89%) rename src/{Shared => Framework}/FileSystem/IFileSystem.cs (100%) rename src/{Shared => Framework}/FileSystem/MSBuildOnWindowsFileSystem.cs (99%) rename src/{Shared => Framework}/FileSystem/ManagedFileSystem.cs (94%) rename src/{Shared => Framework}/FileSystem/NativeWin32Exception.cs (100%) rename src/{Shared => Framework}/FileSystem/SafeFileHandle.cs (100%) rename src/{Shared => Framework}/FileSystem/WindowsFileSystem.cs (94%) rename src/{Shared => Framework}/FileSystem/WindowsNative.cs (100%) delete mode 100644 src/Shared/FileSystemSources.proj diff --git a/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj b/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj index fc675e1852f..191316badff 100644 --- a/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj +++ b/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj @@ -1,6 +1,5 @@ - + - diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 5343eabdc97..153e507d8af 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -1,6 +1,5 @@ - + - diff --git a/src/Shared/FileSystem/CachingFileSystemWrapper.cs b/src/Framework/FileSystem/CachingFileSystemWrapper.cs similarity index 100% rename from src/Shared/FileSystem/CachingFileSystemWrapper.cs rename to src/Framework/FileSystem/CachingFileSystemWrapper.cs diff --git a/src/Shared/FileSystem/FileSystems.cs b/src/Framework/FileSystem/FileSystems.cs similarity index 89% rename from src/Shared/FileSystem/FileSystems.cs rename to src/Framework/FileSystem/FileSystems.cs index 8ada584ef2c..4ceaaa2085d 100644 --- a/src/Shared/FileSystem/FileSystems.cs +++ b/src/Framework/FileSystem/FileSystems.cs @@ -1,6 +1,7 @@ // 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.Framework; namespace Microsoft.Build.Shared.FileSystem { @@ -13,7 +14,7 @@ internal static class FileSystems private static IFileSystem GetFileSystem() { - if (NativeMethodsShared.IsWindows) + if (NativeMethods.IsWindows) { return MSBuildOnWindowsFileSystem.Singleton(); } diff --git a/src/Shared/FileSystem/IFileSystem.cs b/src/Framework/FileSystem/IFileSystem.cs similarity index 100% rename from src/Shared/FileSystem/IFileSystem.cs rename to src/Framework/FileSystem/IFileSystem.cs diff --git a/src/Shared/FileSystem/MSBuildOnWindowsFileSystem.cs b/src/Framework/FileSystem/MSBuildOnWindowsFileSystem.cs similarity index 99% rename from src/Shared/FileSystem/MSBuildOnWindowsFileSystem.cs rename to src/Framework/FileSystem/MSBuildOnWindowsFileSystem.cs index d385c059783..d7386b41871 100644 --- a/src/Shared/FileSystem/MSBuildOnWindowsFileSystem.cs +++ b/src/Framework/FileSystem/MSBuildOnWindowsFileSystem.cs @@ -6,7 +6,6 @@ using System.IO; using System.Runtime.Versioning; - namespace Microsoft.Build.Shared.FileSystem { /// diff --git a/src/Shared/FileSystem/ManagedFileSystem.cs b/src/Framework/FileSystem/ManagedFileSystem.cs similarity index 94% rename from src/Shared/FileSystem/ManagedFileSystem.cs rename to src/Framework/FileSystem/ManagedFileSystem.cs index 6e49213da9a..4f81a669d94 100644 --- a/src/Shared/FileSystem/ManagedFileSystem.cs +++ b/src/Framework/FileSystem/ManagedFileSystem.cs @@ -5,6 +5,9 @@ using System.Collections.Generic; using System.IO; +#if FEATURE_MSIOREDIST +using Microsoft.Build.Framework; +#endif namespace Microsoft.Build.Shared.FileSystem { @@ -22,13 +25,7 @@ private static bool ShouldUseMicrosoftIO { get { -#if !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS - return NativeMethodsShared.IsWindows; -#else - // We need to mock usage of ChangeWaves class, - // because Microsoft.Build.Engine.OM.UnitTests should not have access to internals of Microsoft.Build.Framework. - return true; -#endif + return NativeMethods.IsWindows; } } #endif diff --git a/src/Shared/FileSystem/NativeWin32Exception.cs b/src/Framework/FileSystem/NativeWin32Exception.cs similarity index 100% rename from src/Shared/FileSystem/NativeWin32Exception.cs rename to src/Framework/FileSystem/NativeWin32Exception.cs diff --git a/src/Shared/FileSystem/SafeFileHandle.cs b/src/Framework/FileSystem/SafeFileHandle.cs similarity index 100% rename from src/Shared/FileSystem/SafeFileHandle.cs rename to src/Framework/FileSystem/SafeFileHandle.cs diff --git a/src/Shared/FileSystem/WindowsFileSystem.cs b/src/Framework/FileSystem/WindowsFileSystem.cs similarity index 94% rename from src/Shared/FileSystem/WindowsFileSystem.cs rename to src/Framework/FileSystem/WindowsFileSystem.cs index cf7b9f20c4f..27de39f3a8c 100644 --- a/src/Shared/FileSystem/WindowsFileSystem.cs +++ b/src/Framework/FileSystem/WindowsFileSystem.cs @@ -58,10 +58,10 @@ public override bool DirectoryExists(string path) if (!string.IsNullOrEmpty(path) && FrameworkFileUtilities.IsPathTooLong(path)) { // If the path is too long, we can't check if it exists on windows - string message = ResourceUtilities.FormatString(AssemblyResources.GetString("Shared.PathTooLong"), path, NativeMethodsShared.MaxPath); - throw new PathTooLongException(message); + throw new PathTooLongException(SR.FormatPathTooLong(path, NativeMethods.MaxPath)); } - return NativeMethodsShared.DirectoryExistsWindows(path); + + return NativeMethods.DirectoryExistsWindows(path); } public override bool FileExists(string path) @@ -75,12 +75,12 @@ public override bool FileExists(string path) public override bool FileOrDirectoryExists(string path) { - return NativeMethodsShared.FileOrDirectoryExistsWindows(path); + return NativeMethods.FileOrDirectoryExistsWindows(path); } public override DateTime GetLastWriteTimeUtc(string path) { - var fileLastWriteTime = NativeMethodsShared.GetLastWriteFileUtcTime(path); + var fileLastWriteTime = NativeMethods.GetLastWriteFileUtcTime(path); if (fileLastWriteTime != DateTime.MinValue) { @@ -88,7 +88,7 @@ public override DateTime GetLastWriteTimeUtc(string path) } else { - NativeMethodsShared.GetLastWriteDirectoryUtcTime(path, out var directoryLastWriteTime); + NativeMethods.GetLastWriteDirectoryUtcTime(path, out var directoryLastWriteTime); return directoryLastWriteTime; } } diff --git a/src/Shared/FileSystem/WindowsNative.cs b/src/Framework/FileSystem/WindowsNative.cs similarity index 100% rename from src/Shared/FileSystem/WindowsNative.cs rename to src/Framework/FileSystem/WindowsNative.cs diff --git a/src/Framework/Resources/SR.resx b/src/Framework/Resources/SR.resx index 56e83f04183..6759931fcab 100644 --- a/src/Framework/Resources/SR.resx +++ b/src/Framework/Resources/SR.resx @@ -153,4 +153,7 @@ Path must be rooted. + + 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/Framework/Resources/xlf/SR.cs.xlf b/src/Framework/Resources/xlf/SR.cs.xlf index f4f8e2b05ac..af85a424798 100644 --- a/src/Framework/Resources/xlf/SR.cs.xlf +++ b/src/Framework/Resources/xlf/SR.cs.xlf @@ -62,6 +62,11 @@ Cesta musí začínat kořenem. + + 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ů. + + - \ No newline at end of file + diff --git a/src/Framework/Resources/xlf/SR.de.xlf b/src/Framework/Resources/xlf/SR.de.xlf index eb572f92b9e..07bf73ba667 100644 --- a/src/Framework/Resources/xlf/SR.de.xlf +++ b/src/Framework/Resources/xlf/SR.de.xlf @@ -62,6 +62,11 @@ Der Pfad muss einen Stamm besitzen. + + 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. + + - \ No newline at end of file + diff --git a/src/Framework/Resources/xlf/SR.es.xlf b/src/Framework/Resources/xlf/SR.es.xlf index 19122de0467..507f7c82781 100644 --- a/src/Framework/Resources/xlf/SR.es.xlf +++ b/src/Framework/Resources/xlf/SR.es.xlf @@ -62,6 +62,11 @@ Debe ser una ruta de acceso raíz. + + 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. + + - \ No newline at end of file + diff --git a/src/Framework/Resources/xlf/SR.fr.xlf b/src/Framework/Resources/xlf/SR.fr.xlf index a2f744dca0e..6108c028217 100644 --- a/src/Framework/Resources/xlf/SR.fr.xlf +++ b/src/Framework/Resources/xlf/SR.fr.xlf @@ -62,6 +62,11 @@ Le chemin doit être associé à une racine. + + 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. + + - \ No newline at end of file + diff --git a/src/Framework/Resources/xlf/SR.it.xlf b/src/Framework/Resources/xlf/SR.it.xlf index 4a071c970d8..6a2a9d01f26 100644 --- a/src/Framework/Resources/xlf/SR.it.xlf +++ b/src/Framework/Resources/xlf/SR.it.xlf @@ -62,6 +62,11 @@ Il percorso deve contenere una radice. + + 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}. + + - \ No newline at end of file + diff --git a/src/Framework/Resources/xlf/SR.ja.xlf b/src/Framework/Resources/xlf/SR.ja.xlf index 689291c930e..96e9341135d 100644 --- a/src/Framework/Resources/xlf/SR.ja.xlf +++ b/src/Framework/Resources/xlf/SR.ja.xlf @@ -62,6 +62,11 @@ パスはルート指定パスである必要があります。 + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + パス: {0} は OS のパスの上限を越えています。完全修飾のファイル名は {1} 文字以下にする必要があります。 + + - \ No newline at end of file + diff --git a/src/Framework/Resources/xlf/SR.ko.xlf b/src/Framework/Resources/xlf/SR.ko.xlf index 3cee3f89bbc..94567fe1940 100644 --- a/src/Framework/Resources/xlf/SR.ko.xlf +++ b/src/Framework/Resources/xlf/SR.ko.xlf @@ -62,6 +62,11 @@ 루트 경로로 지정해야 합니다. + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + 경로: {0}은(는) OS 최대 경로 제한을 초과합니다. 정규화된 파일 이름은 {1}자 이하여야 합니다. + + - \ No newline at end of file + diff --git a/src/Framework/Resources/xlf/SR.pl.xlf b/src/Framework/Resources/xlf/SR.pl.xlf index 5dba01b5471..f19731bc3fc 100644 --- a/src/Framework/Resources/xlf/SR.pl.xlf +++ b/src/Framework/Resources/xlf/SR.pl.xlf @@ -62,6 +62,11 @@ Ścieżka musi zaczynać się od katalogu głównego. + + 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. + + - \ No newline at end of file + diff --git a/src/Framework/Resources/xlf/SR.pt-BR.xlf b/src/Framework/Resources/xlf/SR.pt-BR.xlf index 8dc6f5e3e4d..b5f68f74392 100644 --- a/src/Framework/Resources/xlf/SR.pt-BR.xlf +++ b/src/Framework/Resources/xlf/SR.pt-BR.xlf @@ -62,6 +62,11 @@ Caminho deve ter raiz. + + 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. + + - \ No newline at end of file + diff --git a/src/Framework/Resources/xlf/SR.ru.xlf b/src/Framework/Resources/xlf/SR.ru.xlf index b4848dfe4fd..863113e65df 100644 --- a/src/Framework/Resources/xlf/SR.ru.xlf +++ b/src/Framework/Resources/xlf/SR.ru.xlf @@ -62,6 +62,11 @@ Путь должен иметь корень. + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + Длина пути {0} превышает максимально допустимую в ОС. Символов в полном имени файла должно быть не больше {1}. + + - \ No newline at end of file + diff --git a/src/Framework/Resources/xlf/SR.tr.xlf b/src/Framework/Resources/xlf/SR.tr.xlf index 021ba510b13..9a735a7fb52 100644 --- a/src/Framework/Resources/xlf/SR.tr.xlf +++ b/src/Framework/Resources/xlf/SR.tr.xlf @@ -62,6 +62,11 @@ Yol kökü belirtilmelidir. + + 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. + + - \ No newline at end of file + diff --git a/src/Framework/Resources/xlf/SR.zh-Hans.xlf b/src/Framework/Resources/xlf/SR.zh-Hans.xlf index 47ed63f6cc3..16a40b3360a 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hans.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hans.xlf @@ -62,6 +62,11 @@ 路径必须是根路径。 + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + 路径: {0} 超过 OS 最大路径限制。完全限定的文件名必须少于 {1} 个字符。 + + - \ No newline at end of file + diff --git a/src/Framework/Resources/xlf/SR.zh-Hant.xlf b/src/Framework/Resources/xlf/SR.zh-Hant.xlf index 308821aef1a..3f2b2ee9276 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hant.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hant.xlf @@ -62,6 +62,11 @@ 路徑必須為根路徑。 + + Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + 路徑: {0} 超過 OS 路徑上限。完整檔案名稱必須少於 {1} 個字元。 + + - \ No newline at end of file + diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj index cf205b2366d..5dbd434d5a8 100644 --- a/src/MSBuild/MSBuild.csproj +++ b/src/MSBuild/MSBuild.csproj @@ -1,6 +1,5 @@ - + - diff --git a/src/Shared/FileSystemSources.proj b/src/Shared/FileSystemSources.proj deleted file mode 100644 index f86f3cfbd1c..00000000000 --- a/src/Shared/FileSystemSources.proj +++ /dev/null @@ -1,7 +0,0 @@ - - - - FileSystem\%(Filename).cs - - - \ No newline at end of file diff --git a/src/Shared/FileUtilities.cs b/src/Shared/FileUtilities.cs index 03d515ad43d..12eacad01b7 100644 --- a/src/Shared/FileUtilities.cs +++ b/src/Shared/FileUtilities.cs @@ -435,8 +435,7 @@ private static string GetFullPath(string path) if (FrameworkFileUtilities.IsPathTooLong(uncheckedFullPath)) { - string message = ResourceUtilities.FormatString(AssemblyResources.GetString("Shared.PathTooLong"), path, NativeMethodsShared.MaxPath); - throw new PathTooLongException(message); + throw new PathTooLongException(Framework.Resources.SR.FormatPathTooLong(path, NativeMethodsShared.MaxPath)); } // We really don't care about extensions here, but Path.HasExtension provides a great way to diff --git a/src/Shared/Resources/Strings.shared.resx b/src/Shared/Resources/Strings.shared.resx index 6e29c8e68af..1ee972f2dab 100644 --- a/src/Shared/Resources/Strings.shared.resx +++ b/src/Shared/Resources/Strings.shared.resx @@ -334,9 +334,6 @@ 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} diff --git a/src/Shared/Resources/xlf/Strings.shared.cs.xlf b/src/Shared/Resources/xlf/Strings.shared.cs.xlf index efc33efebe0..7c27da24ffb 100644 --- a/src/Shared/Resources/xlf/Strings.shared.cs.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.cs.xlf @@ -85,11 +85,6 @@ 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}. diff --git a/src/Shared/Resources/xlf/Strings.shared.de.xlf b/src/Shared/Resources/xlf/Strings.shared.de.xlf index a63a5c4e29d..96e44a116e6 100644 --- a/src/Shared/Resources/xlf/Strings.shared.de.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.de.xlf @@ -85,11 +85,6 @@ 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. diff --git a/src/Shared/Resources/xlf/Strings.shared.es.xlf b/src/Shared/Resources/xlf/Strings.shared.es.xlf index b7936f94976..28992cf81d7 100644 --- a/src/Shared/Resources/xlf/Strings.shared.es.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.es.xlf @@ -85,11 +85,6 @@ 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}". diff --git a/src/Shared/Resources/xlf/Strings.shared.fr.xlf b/src/Shared/Resources/xlf/Strings.shared.fr.xlf index abbc14e5158..c33ef71d145 100644 --- a/src/Shared/Resources/xlf/Strings.shared.fr.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.fr.xlf @@ -85,11 +85,6 @@ 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}". diff --git a/src/Shared/Resources/xlf/Strings.shared.it.xlf b/src/Shared/Resources/xlf/Strings.shared.it.xlf index 082c3faab51..13d9fc42900 100644 --- a/src/Shared/Resources/xlf/Strings.shared.it.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.it.xlf @@ -85,11 +85,6 @@ 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}". diff --git a/src/Shared/Resources/xlf/Strings.shared.ja.xlf b/src/Shared/Resources/xlf/Strings.shared.ja.xlf index 8e33727f2fb..bf5724129aa 100644 --- a/src/Shared/Resources/xlf/Strings.shared.ja.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.ja.xlf @@ -85,11 +85,6 @@ 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}" が含まれています。 diff --git a/src/Shared/Resources/xlf/Strings.shared.ko.xlf b/src/Shared/Resources/xlf/Strings.shared.ko.xlf index 102c6ecfb97..f36fecb0d5d 100644 --- a/src/Shared/Resources/xlf/Strings.shared.ko.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.ko.xlf @@ -85,11 +85,6 @@ 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}" 프로젝트가 포함되어 있습니다. diff --git a/src/Shared/Resources/xlf/Strings.shared.pl.xlf b/src/Shared/Resources/xlf/Strings.shared.pl.xlf index f5c531296d1..e0cf71f3188 100644 --- a/src/Shared/Resources/xlf/Strings.shared.pl.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.pl.xlf @@ -85,11 +85,6 @@ 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}”. diff --git a/src/Shared/Resources/xlf/Strings.shared.pt-BR.xlf b/src/Shared/Resources/xlf/Strings.shared.pt-BR.xlf index cf78ee34200..01ca48b9f29 100644 --- a/src/Shared/Resources/xlf/Strings.shared.pt-BR.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.pt-BR.xlf @@ -85,11 +85,6 @@ 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}". diff --git a/src/Shared/Resources/xlf/Strings.shared.ru.xlf b/src/Shared/Resources/xlf/Strings.shared.ru.xlf index a40cb28ad14..d0f2c1e081f 100644 --- a/src/Shared/Resources/xlf/Strings.shared.ru.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.ru.xlf @@ -85,11 +85,6 @@ Для 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}". diff --git a/src/Shared/Resources/xlf/Strings.shared.tr.xlf b/src/Shared/Resources/xlf/Strings.shared.tr.xlf index e5e76c65345..359306dbdc8 100644 --- a/src/Shared/Resources/xlf/Strings.shared.tr.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.tr.xlf @@ -85,11 +85,6 @@ 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. diff --git a/src/Shared/Resources/xlf/Strings.shared.zh-Hans.xlf b/src/Shared/Resources/xlf/Strings.shared.zh-Hans.xlf index f0540b9f5e0..6b1f3ae581e 100644 --- a/src/Shared/Resources/xlf/Strings.shared.zh-Hans.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.zh-Hans.xlf @@ -85,11 +85,6 @@ 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}”。 diff --git a/src/Shared/Resources/xlf/Strings.shared.zh-Hant.xlf b/src/Shared/Resources/xlf/Strings.shared.zh-Hant.xlf index a7e8187bddb..518e09fdb4c 100644 --- a/src/Shared/Resources/xlf/Strings.shared.zh-Hant.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.zh-Hant.xlf @@ -85,11 +85,6 @@ 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}" 的解決方案檔案中。 diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj index d00e4b81005..ec70e168d2f 100644 --- a/src/Tasks/Microsoft.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.Build.Tasks.csproj @@ -1,6 +1,5 @@ - + - diff --git a/src/Utilities/Microsoft.Build.Utilities.csproj b/src/Utilities/Microsoft.Build.Utilities.csproj index c287f5b9f6a..977bfb8385e 100644 --- a/src/Utilities/Microsoft.Build.Utilities.csproj +++ b/src/Utilities/Microsoft.Build.Utilities.csproj @@ -1,6 +1,5 @@ - + - From 99fe386adbcc6784defc5c302468bddc6048a4b9 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Mar 2026 09:41:30 -0800 Subject: [PATCH 06/27] Move EscapingUtilities to Framework - Move src/Shared/EscapingUtilities.cs to src/Framework/EscapingUtilities.cs - Add project reference from Microsoft.Build.Framework to StringTools.csproj. This dependency is needed by EscapingUtilities. - Remove all project reference to StringTools.csproj from projects that reference it transitively through Microsoft.Build.Framework. --- .../Microsoft.Build.Engine.OM.UnitTests.csproj | 1 - src/Build/Microsoft.Build.csproj | 4 ---- .../Microsoft.Build.Framework.UnitTests.csproj | 4 +--- src/{Shared => Framework}/EscapingUtilities.cs | 0 src/Framework/Microsoft.Build.Framework.csproj | 6 +++++- src/MSBuild/MSBuild.csproj | 1 - src/Tasks/Microsoft.Build.Tasks.csproj | 5 ----- src/Utilities/Microsoft.Build.Utilities.csproj | 5 ----- 8 files changed, 6 insertions(+), 20 deletions(-) rename src/{Shared => Framework}/EscapingUtilities.cs (100%) diff --git a/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj b/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj index 191316badff..acd9dab5ac2 100644 --- a/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj +++ b/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj @@ -47,7 +47,6 @@ - diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 153e507d8af..7ebaf033ce9 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -29,7 +29,6 @@ - @@ -656,9 +655,6 @@ Errors\ErrorUtilities.cs - - SharedUtilities\EscapingUtilities.cs - SharedUtilities\VersionUtilities.cs diff --git a/src/Framework.UnitTests/Microsoft.Build.Framework.UnitTests.csproj b/src/Framework.UnitTests/Microsoft.Build.Framework.UnitTests.csproj index d31ef573b24..0f8b51866c5 100644 --- a/src/Framework.UnitTests/Microsoft.Build.Framework.UnitTests.csproj +++ b/src/Framework.UnitTests/Microsoft.Build.Framework.UnitTests.csproj @@ -1,4 +1,4 @@ - + $(RuntimeOutputTargetFrameworks) @@ -19,7 +19,6 @@ - @@ -28,7 +27,6 @@ - diff --git a/src/Shared/EscapingUtilities.cs b/src/Framework/EscapingUtilities.cs similarity index 100% rename from src/Shared/EscapingUtilities.cs rename to src/Framework/EscapingUtilities.cs diff --git a/src/Framework/Microsoft.Build.Framework.csproj b/src/Framework/Microsoft.Build.Framework.csproj index 8397124ae9f..08f2c7c6968 100644 --- a/src/Framework/Microsoft.Build.Framework.csproj +++ b/src/Framework/Microsoft.Build.Framework.csproj @@ -72,6 +72,10 @@ + + + + @@ -81,6 +85,6 @@ true - + diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj index 5dbd434d5a8..6eea476a90e 100644 --- a/src/MSBuild/MSBuild.csproj +++ b/src/MSBuild/MSBuild.csproj @@ -80,7 +80,6 @@ RegisteredTaskObjectCacheBase.cs - diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj index ec70e168d2f..1b7d0b6cada 100644 --- a/src/Tasks/Microsoft.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.Build.Tasks.csproj @@ -127,9 +127,6 @@ StringUtils.cs - - EscapingUtilities.cs - Modifiers.cs @@ -666,7 +663,6 @@ - @@ -709,7 +705,6 @@ - diff --git a/src/Utilities/Microsoft.Build.Utilities.csproj b/src/Utilities/Microsoft.Build.Utilities.csproj index 977bfb8385e..0d06f65e567 100644 --- a/src/Utilities/Microsoft.Build.Utilities.csproj +++ b/src/Utilities/Microsoft.Build.Utilities.csproj @@ -19,7 +19,6 @@ - @@ -32,7 +31,6 @@ - @@ -71,9 +69,6 @@ Shared\ErrorUtilities.cs - - Shared\EscapingUtilities.cs - Shared\EventArgsFormatting.cs From bbfccd9d6be8d63da073df16fcea4cf830d5630e Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 2 Mar 2026 09:44:46 -0800 Subject: [PATCH 07/27] Move FileUtilitiesRegex to Framework - Move src/Shared/FileUtilitiesRegex.cs to src/Framework/FileUtilitiesRegex.cs --- .../Microsoft.Build.Engine.OM.UnitTests.csproj | 1 - src/Build/Microsoft.Build.csproj | 3 --- .../Microsoft.Build.Framework.UnitTests.csproj | 1 - src/{Shared => Framework}/FileUtilitiesRegex.cs | 0 src/MSBuild/MSBuild.csproj | 3 --- src/Tasks/Microsoft.Build.Tasks.csproj | 1 - src/Utilities/Microsoft.Build.Utilities.csproj | 3 --- 7 files changed, 12 deletions(-) rename src/{Shared => Framework}/FileUtilitiesRegex.cs (100%) diff --git a/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj b/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj index acd9dab5ac2..4720d94db6e 100644 --- a/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj +++ b/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj @@ -47,7 +47,6 @@ - App.config diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 7ebaf033ce9..534837ff01b 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -672,9 +672,6 @@ - - SharedUtilities\FileUtilitiesRegex.cs - SharedUtilities\FrameworkLocationHelper.cs diff --git a/src/Framework.UnitTests/Microsoft.Build.Framework.UnitTests.csproj b/src/Framework.UnitTests/Microsoft.Build.Framework.UnitTests.csproj index 0f8b51866c5..878a0e7d8c7 100644 --- a/src/Framework.UnitTests/Microsoft.Build.Framework.UnitTests.csproj +++ b/src/Framework.UnitTests/Microsoft.Build.Framework.UnitTests.csproj @@ -28,7 +28,6 @@ - diff --git a/src/Shared/FileUtilitiesRegex.cs b/src/Framework/FileUtilitiesRegex.cs similarity index 100% rename from src/Shared/FileUtilitiesRegex.cs rename to src/Framework/FileUtilitiesRegex.cs diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj index 6eea476a90e..1abb07244c7 100644 --- a/src/MSBuild/MSBuild.csproj +++ b/src/MSBuild/MSBuild.csproj @@ -72,9 +72,6 @@ FileUtilities.cs - - FileUtilitiesRegex.cs - RegisteredTaskObjectCacheBase.cs diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj index 1b7d0b6cada..a053bd4e0b9 100644 --- a/src/Tasks/Microsoft.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.Build.Tasks.csproj @@ -143,7 +143,6 @@ - diff --git a/src/Utilities/Microsoft.Build.Utilities.csproj b/src/Utilities/Microsoft.Build.Utilities.csproj index 0d06f65e567..f4e986bf314 100644 --- a/src/Utilities/Microsoft.Build.Utilities.csproj +++ b/src/Utilities/Microsoft.Build.Utilities.csproj @@ -81,9 +81,6 @@ Shared\FileMatcher.cs - - Shared\FileUtilitiesRegex.cs - Shared\FrameworkLocationHelper.cs From 002592ce8f3302d51c8ab319715b6e5d97bc0527 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 9 Mar 2026 10:11:41 -0700 Subject: [PATCH 08/27] Move exception dumping members from ExceptionHandling to DebugUtils This change moves all of the implementations of exception dumping members from ExceptionHandling to DebugUtils. Member stubs have been left in ExceptionHandling that delegate to the implementations in DebugUtils. This will be cleaned up in a later commit. Note that references to the BUILDINGAPPXTASKS conditional compilation symbol have been removed from ExceptionHandling. BUILDINGAPPXTASKS isn't set anywhere within the msbuild repo. --- src/Shared/Debugging/DebugUtils.cs | 163 +++++++++++++++++++++++++ src/Shared/ExceptionHandling.cs | 188 +++-------------------------- 2 files changed, 180 insertions(+), 171 deletions(-) diff --git a/src/Shared/Debugging/DebugUtils.cs b/src/Shared/Debugging/DebugUtils.cs index a642dfc2bd8..b4643cda115 100644 --- a/src/Shared/Debugging/DebugUtils.cs +++ b/src/Shared/Debugging/DebugUtils.cs @@ -2,8 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; +using System.Text; using Microsoft.Build.Framework; +using Microsoft.Build.Framework.Telemetry; using Microsoft.Build.Shared.FileSystem; #nullable disable @@ -100,6 +105,58 @@ internal static void SetDebugPath() DebugPath = debugDirectory; } + private static readonly string s_debugDumpPath = GetDebugDumpPath(); + + /// + /// Gets the location of the directory used for diagnostic log files. + /// + /// + private static string GetDebugDumpPath() + { + string debugPath = DebugPath; + + 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; + + /// + /// The filename that exceptions will be dumped to + /// + private static string s_dumpFileName; + private static readonly Lazy ProcessNodeMode = new( () => NodeModeHelper.ExtractFromCommandLine(Environment.CommandLine)); @@ -157,5 +214,111 @@ public static string FindNextAvailableDebugFilePath(string fileName) return fullPath; } + + + /// + /// 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); +#if !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS + RecordCrashTelemetryForUnhandledException(ex); +#endif + } + +#if !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS + /// + /// Records and immediately flushes crash telemetry for an unhandled exception. + /// Best effort - must never throw, as the process is already crashing. + /// + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private static void RecordCrashTelemetryForUnhandledException(Exception ex) + { + CrashTelemetryRecorder.RecordAndFlushCrashTelemetry( + ex, + exitType: CrashExitType.UnhandledException, + isUnhandled: true, + isCritical: ExceptionHandling.IsCriticalException(ex)); + } +#endif + + /// + /// 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(); + } } } diff --git a/src/Shared/ExceptionHandling.cs b/src/Shared/ExceptionHandling.cs index c3b8f3af6e7..b138eab372f 100644 --- a/src/Shared/ExceptionHandling.cs +++ b/src/Shared/ExceptionHandling.cs @@ -3,96 +3,40 @@ #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 !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS using Microsoft.Build.Shared.Debugging; -using Microsoft.Build.Framework.Telemetry; -#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() + /// + internal static bool ResetDebugDumpPathInRunningTests { - string debugPath = - -#if MICROSOFT_BUILD_ENGINE_OM_UNITTESTS - Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); -#else - DebugUtils.DebugPath; -#endif - - return !string.IsNullOrEmpty(debugPath) - ? debugPath - : FileUtilities.TempFileDirectory; + get => DebugUtils.ResetDebugDumpPathInRunningTests; + set => DebugUtils.ResetDebugDumpPathInRunningTests = value; } - 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; - } - } + /// + internal static string DebugDumpPath => DebugUtils.DebugDumpPath; - /// - /// The file used for diagnostic log files. - /// - internal static string DumpFilePath => s_dumpFileName; + /// + internal static string DumpFilePath => DebugUtils.DumpFilePath; -#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. @@ -108,10 +52,7 @@ internal static bool IsCriticalException(Exception e) || e is ThreadInterruptedException || e is AccessViolationException || e is CriticalTaskException -#if !BUILDINGAPPXTASKS - || e is InternalErrorException -#endif - ) + || 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. @@ -175,7 +116,7 @@ internal static bool IsXmlException(Exception e) { return e is XmlException #if FEATURE_SECURITY_PERMISSIONS - || e is System.Security.XmlSyntaxException + || e is XmlSyntaxException #endif || e is XmlSchemaException || e is UriFormatException; // XmlTextReader for example uses this under the covers @@ -213,8 +154,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. @@ -319,111 +258,18 @@ internal static bool NotExpectedFunctionException(Exception e) 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); -#if !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS - RecordCrashTelemetryForUnhandledException(ex); -#endif - } - -#if !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS - /// - /// Records and immediately flushes crash telemetry for an unhandled exception. - /// Best effort - must never throw, as the process is already crashing. - /// - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] - private static void RecordCrashTelemetryForUnhandledException(Exception ex) - { - CrashTelemetryRecorder.RecordAndFlushCrashTelemetry( - ex, - exitType: CrashExitType.UnhandledException, - isUnhandled: true, - isCritical: IsCriticalException(ex)); - } -#endif + => DebugUtils.UnhandledExceptionHandler(sender, e); - /// - /// 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("==================="); - } - } - } + => DebugUtils.DumpExceptionToFile(ex); - // 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 + => DebugUtils.ReadAnyExceptionFromFile(fromTimeUtc); /// Line and column pair. internal struct LineAndColumn From 34ec1790baa7e422cfb47aa88b88833d3f887710 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 9 Mar 2026 11:10:57 -0700 Subject: [PATCH 09/27] Move ExceptionHandling member implementations to Framework - Add src/Framework/ExceptionHandling.cs containing FrameworkExceptionHandling static class. - Move implementations of members from src/Shared/ExceptionHandling.cs to src/Framework/ExceptionHandling.cs and leave stubs behind. The stubs will be cleaned up in a later commit. - Remove used ExceptionHandling.GetXmlLineAndColumn(Exception) method. --- src/Framework/ExceptionHandling.cs | 206 ++++++++++++++++++++++++ src/Shared/ExceptionHandling.cs | 242 +++-------------------------- 2 files changed, 225 insertions(+), 223 deletions(-) create mode 100644 src/Framework/ExceptionHandling.cs diff --git a/src/Framework/ExceptionHandling.cs b/src/Framework/ExceptionHandling.cs new file mode 100644 index 00000000000..646833a7319 --- /dev/null +++ b/src/Framework/ExceptionHandling.cs @@ -0,0 +1,206 @@ +// 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.Reflection; +using System.Runtime.Serialization; +using System.Security; +using System.Threading; +using System.Xml; +using System.Xml.Schema; + +namespace Microsoft.Build.Framework; + +internal static class FrameworkExceptionHandling +{ + /// + /// 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 CriticalTaskException + || 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. + + // ExecutionEngineException has been deprecated by the CLR + return true; + } + + // 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; + } + } + + 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 XmlSyntaxException +#endif + || e is XmlSchemaException + || e is UriFormatException; // XmlTextReader for example uses this under the covers + } + + /// + /// 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; + } +} diff --git a/src/Shared/ExceptionHandling.cs b/src/Shared/ExceptionHandling.cs index b138eab372f..7843999d229 100644 --- a/src/Shared/ExceptionHandling.cs +++ b/src/Shared/ExceptionHandling.cs @@ -5,17 +5,8 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Security; -using System.Threading; -using System.Xml; -using System.Xml.Schema; -using System.Runtime.Serialization; -using Microsoft.Build.Shared.Debugging; - using Microsoft.Build.Framework; +using Microsoft.Build.Shared.Debugging; namespace Microsoft.Build.Shared { @@ -37,226 +28,41 @@ internal static bool ResetDebugDumpPathInRunningTests /// internal static string DumpFilePath => DebugUtils.DumpFilePath; - /// - /// 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 CriticalTaskException - || 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. - - // ExecutionEngineException has been deprecated by the CLR - return true; - } + => FrameworkExceptionHandling.IsCriticalException(e); - // 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; - } - } - - 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); - } + => FrameworkExceptionHandling.NotExpectedException(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; - } + => FrameworkExceptionHandling.IsIoRelatedException(e); - /// 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 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 - }; - } + => FrameworkExceptionHandling.IsXmlException(e); - /// - /// 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; - } + => FrameworkExceptionHandling.NotExpectedIoOrXmlException(e); - /// - /// 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; - } + => FrameworkExceptionHandling.NotExpectedReflectionException(e); - /// - /// 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; - } + => FrameworkExceptionHandling.NotExpectedSerializationException(e); - 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; - } + => FrameworkExceptionHandling.NotExpectedRegistryException(e); - 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; - } + => FrameworkExceptionHandling.NotExpectedFunctionException(e); /// [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "It is called by the CLR")] @@ -270,15 +76,5 @@ internal static void DumpExceptionToFile(Exception ex) /// internal static string ReadAnyExceptionFromFile(DateTime fromTimeUtc) => DebugUtils.ReadAnyExceptionFromFile(fromTimeUtc); - - /// 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 03d0b9ebaf264ed60ea3a3a466ac878fad14412e Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 9 Mar 2026 11:36:47 -0700 Subject: [PATCH 10/27] Move TempFileUtilities member implementations to Framework - Add src/Framework/FileUtilities_TemFiles.cs containing a new partial part of the FrameworkFileUtilities static class. - Move implementations of members from src/Shared/TempFileUtilities.cs to src/Framework/FileUtilities_TemFiles.cs and leave stubs behind. The stubs will be cleaned up in a later commit. - Remove TempWorkingDirectory nested type from src/Shared/TempFileUtilities.cs and update callers to use the type nested in FrameworkFileUtilities. - Add "FailedCreatingTempFile" string resource, taking care to copy the translated strings from "Shared.FailedCreatingTempFile" --- src/Framework/FileUtilities.cs | 2 +- src/Framework/FileUtilities_TempFiles.cs | 241 ++++++++++++++++++ src/Framework/Resources/SR.resx | 3 + src/Framework/Resources/xlf/SR.cs.xlf | 5 + src/Framework/Resources/xlf/SR.de.xlf | 5 + src/Framework/Resources/xlf/SR.es.xlf | 5 + src/Framework/Resources/xlf/SR.fr.xlf | 5 + src/Framework/Resources/xlf/SR.it.xlf | 5 + src/Framework/Resources/xlf/SR.ja.xlf | 5 + src/Framework/Resources/xlf/SR.ko.xlf | 5 + src/Framework/Resources/xlf/SR.pl.xlf | 5 + src/Framework/Resources/xlf/SR.pt-BR.xlf | 5 + src/Framework/Resources/xlf/SR.ru.xlf | 5 + src/Framework/Resources/xlf/SR.tr.xlf | 5 + src/Framework/Resources/xlf/SR.zh-Hans.xlf | 5 + src/Framework/Resources/xlf/SR.zh-Hant.xlf | 5 + src/Shared/TempFileUtilities.cs | 230 ++--------------- .../TypeLoader_Dependencies_Tests.cs | 5 +- src/Shared/UnitTests/TypeLoader_Tests.cs | 9 +- 19 files changed, 339 insertions(+), 216 deletions(-) create mode 100644 src/Framework/FileUtilities_TempFiles.cs diff --git a/src/Framework/FileUtilities.cs b/src/Framework/FileUtilities.cs index d40882421dd..8dbe6dd06f6 100644 --- a/src/Framework/FileUtilities.cs +++ b/src/Framework/FileUtilities.cs @@ -18,7 +18,7 @@ namespace Microsoft.Build.Framework /// 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 partial class FrameworkFileUtilities { private const char UnixDirectorySeparator = '/'; private const char WindowsDirectorySeparator = '\\'; diff --git a/src/Framework/FileUtilities_TempFiles.cs b/src/Framework/FileUtilities_TempFiles.cs new file mode 100644 index 00000000000..d0ee572e95d --- /dev/null +++ b/src/Framework/FileUtilities_TempFiles.cs @@ -0,0 +1,241 @@ +// 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.Runtime.CompilerServices; +using System.Threading; +using Microsoft.Build.Shared.FileSystem; + +#nullable disable + +namespace Microsoft.Build.Framework; + +internal static partial class FrameworkFileUtilities +{ + private static Lazy tempFileDirectory = CreateTempFileDirectoryLazy(); + + private const string msbuildTempFolderPrefix = "MSBuildTemp"; + + internal static string TempFileDirectory => tempFileDirectory.Value; + + private static Lazy CreateTempFileDirectoryLazy() + { + return new Lazy( + () => + { + string path = CreateFolderUnderTemp(); + RegisterCleanupOnExit(path); + return path; + }, + LazyThreadSafetyMode.ExecutionAndPublication); + } + + private static void RegisterCleanupOnExit(string pathToCleanup) + { + AppDomain.CurrentDomain.ProcessExit += (_, _) => + { + try + { + if (Directory.Exists(pathToCleanup)) + { + Directory.Delete(pathToCleanup, recursive: true); + } + } + catch + { + // Best effort - ignore failures during cleanup + } + }; + } + + internal static void ClearTempFileDirectory() + { + tempFileDirectory = CreateTempFileDirectoryLazy(); + } + + private static string CreateFolderUnderTemp() + { + string path; + +#if NET + path = Directory.CreateTempSubdirectory(msbuildTempFolderPrefix).FullName; +#else + // CreateTempSubdirectory API is not available in .NET Framework + path = Path.Combine(Path.GetTempPath(), $"{msbuildTempFolderPrefix}{Guid.NewGuid():N}"); + Directory.CreateDirectory(path); +#endif + + return EnsureTrailingSlash(path); + } + + /// + /// Generates a unique directory name in the temporary folder. + /// Caller must delete when finished. + /// + /// + /// + internal static string GetTemporaryDirectory(bool createDirectory = true, string subfolder = null) + { + string temporaryDirectory = Path.Combine(TempFileDirectory, $"Temporary{Guid.NewGuid():N}", subfolder ?? string.Empty); + + if (createDirectory) + { + Directory.CreateDirectory(temporaryDirectory); + } + + return temporaryDirectory; + } + + /// + /// Generates a unique temporary file name with a given extension in the temporary folder. + /// File is guaranteed to be unique. + /// Extension may have an initial period. + /// File will NOT be created. + /// May throw IOException. + /// + internal static string GetTemporaryFileName() + { + return GetTemporaryFileName(".tmp"); + } + + /// + /// Generates a unique temporary file name with a given extension in the temporary folder. + /// File is guaranteed to be unique. + /// Extension may have an initial period. + /// File will NOT be created. + /// May throw IOException. + /// + internal static string GetTemporaryFileName(string extension) + { + return GetTemporaryFile(null, null, extension, false); + } + + /// + /// Generates a unique temporary file name with a given extension in the temporary folder. + /// If no extension is provided, uses ".tmp". + /// File is guaranteed to be unique. + /// Caller must delete it when finished. + /// + internal static string GetTemporaryFile() + { + return GetTemporaryFile(".tmp"); + } + + /// + /// Generates a unique temporary file name with a given extension in the temporary folder. + /// File is guaranteed to be unique. + /// Caller must delete it when finished. + /// + internal static string GetTemporaryFile(string fileName, string extension, bool createFile) + { + return GetTemporaryFile(null, fileName, extension, createFile); + } + + /// + /// Generates a unique temporary file name with a given extension in the temporary folder. + /// File is guaranteed to be unique. + /// Extension may have an initial period. + /// Caller must delete it when finished. + /// May throw IOException. + /// + internal static string GetTemporaryFile(string extension) + { + return GetTemporaryFile(null, null, extension); + } + + /// + /// Creates a file with unique temporary file name with a given extension in the specified folder. + /// File is guaranteed to be unique. + /// Extension may have an initial period. + /// If folder is null, the temporary folder will be used. + /// Caller must delete it when finished. + /// May throw IOException. + /// + internal static string GetTemporaryFile(string directory, string fileName, string extension, bool createFile = true) + { + if (directory is not null) + { + ArgumentException.ThrowIfNullOrEmpty(directory); + } + + try + { + directory ??= TempFileDirectory; + + // If the extension needs a dot prepended, do so. + if (extension is null) + { + extension = string.Empty; + } + else if (extension.Length > 0 && extension[0] != '.') + { + extension = '.' + extension; + } + + // If the fileName is null, use tmp{Guid}; otherwise use fileName. + if (string.IsNullOrEmpty(fileName)) + { + fileName = $"tmp{Guid.NewGuid():N}"; + } + + Directory.CreateDirectory(directory); + + string file = Path.Combine(directory, $"{fileName}{extension}"); + + FrameworkErrorUtilities.VerifyThrow(!FileSystems.Default.FileExists(file), "Guid should be unique"); + + if (createFile) + { + File.WriteAllText(file, string.Empty); + } + + return file; + } + catch (Exception ex) when (FrameworkExceptionHandling.IsIoRelatedException(ex)) + { + throw new IOException(SR.FormatFailedCreatingTempFile(ex.Message), ex); + } + } + + internal static void CopyDirectory(string source, string dest) + { + Directory.CreateDirectory(dest); + + DirectoryInfo sourceInfo = new DirectoryInfo(source); + foreach (var fileInfo in sourceInfo.GetFiles()) + { + string destFile = Path.Combine(dest, fileInfo.Name); + fileInfo.CopyTo(destFile); + } + foreach (var subdirInfo in sourceInfo.GetDirectories()) + { + string destDir = Path.Combine(dest, subdirInfo.Name); + CopyDirectory(subdirInfo.FullName, destDir); + } + } + + public sealed class TempWorkingDirectory : IDisposable + { + public string Path { get; } + + public TempWorkingDirectory(string sourcePath, [CallerMemberName] string name = null) + { + Path = name == null + ? GetTemporaryDirectory() + : System.IO.Path.Combine(TempFileDirectory, name); + + if (FileSystems.Default.DirectoryExists(Path)) + { + Directory.Delete(Path, true); + } + + CopyDirectory(sourcePath, Path); + } + + public void Dispose() + { + Directory.Delete(Path, true); + } + } +} diff --git a/src/Framework/Resources/SR.resx b/src/Framework/Resources/SR.resx index 6759931fcab..b45b5160f10 100644 --- a/src/Framework/Resources/SR.resx +++ b/src/Framework/Resources/SR.resx @@ -156,4 +156,7 @@ Path: {0} exceeds the OS max path limit. The fully qualified file name must be less than {1} characters. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + \ No newline at end of file diff --git a/src/Framework/Resources/xlf/SR.cs.xlf b/src/Framework/Resources/xlf/SR.cs.xlf index af85a424798..3868aa76ffd 100644 --- a/src/Framework/Resources/xlf/SR.cs.xlf +++ b/src/Framework/Resources/xlf/SR.cs.xlf @@ -57,6 +57,11 @@ The value cannot be an empty string. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + 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} + + Path must be rooted. Cesta musí začínat kořenem. diff --git a/src/Framework/Resources/xlf/SR.de.xlf b/src/Framework/Resources/xlf/SR.de.xlf index 07bf73ba667..bbff622e1ce 100644 --- a/src/Framework/Resources/xlf/SR.de.xlf +++ b/src/Framework/Resources/xlf/SR.de.xlf @@ -57,6 +57,11 @@ The value cannot be an empty string. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + Fehler beim Erstellen einer temporären Datei. Der Ordner für temporäre Dateien ist voll, oder der Pfad ist falsch. {0} + + Path must be rooted. Der Pfad muss einen Stamm besitzen. diff --git a/src/Framework/Resources/xlf/SR.es.xlf b/src/Framework/Resources/xlf/SR.es.xlf index 507f7c82781..348eed9655e 100644 --- a/src/Framework/Resources/xlf/SR.es.xlf +++ b/src/Framework/Resources/xlf/SR.es.xlf @@ -57,6 +57,11 @@ The value cannot be an empty string. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + Error al crear un archivo temporal. La carpeta de archivos temporales está llena o la ruta de acceso no es correcta. {0} + + Path must be rooted. Debe ser una ruta de acceso raíz. diff --git a/src/Framework/Resources/xlf/SR.fr.xlf b/src/Framework/Resources/xlf/SR.fr.xlf index 6108c028217..b11d8f4c209 100644 --- a/src/Framework/Resources/xlf/SR.fr.xlf +++ b/src/Framework/Resources/xlf/SR.fr.xlf @@ -57,6 +57,11 @@ The value cannot be an empty string. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + Échec de la création d'un fichier temporaire. Le dossier de fichiers temporaires est plein ou son chemin est incorrect. {0} + + Path must be rooted. Le chemin doit être associé à une racine. diff --git a/src/Framework/Resources/xlf/SR.it.xlf b/src/Framework/Resources/xlf/SR.it.xlf index 6a2a9d01f26..184d77a83f4 100644 --- a/src/Framework/Resources/xlf/SR.it.xlf +++ b/src/Framework/Resources/xlf/SR.it.xlf @@ -57,6 +57,11 @@ The value cannot be an empty string. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + non è stato possibile creare un file temporaneo. La cartella dei file temporanei è piena oppure il percorso è errato. {0} + + Path must be rooted. Il percorso deve contenere una radice. diff --git a/src/Framework/Resources/xlf/SR.ja.xlf b/src/Framework/Resources/xlf/SR.ja.xlf index 96e9341135d..4edf08f08e4 100644 --- a/src/Framework/Resources/xlf/SR.ja.xlf +++ b/src/Framework/Resources/xlf/SR.ja.xlf @@ -57,6 +57,11 @@ The value cannot be an empty string. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + 一時ファイルを作成できませんでした。一時ファイル フォルダーがいっぱいであるか、またはそのパスが正しくありません。{0} + + Path must be rooted. パスはルート指定パスである必要があります。 diff --git a/src/Framework/Resources/xlf/SR.ko.xlf b/src/Framework/Resources/xlf/SR.ko.xlf index 94567fe1940..1460c14ba8b 100644 --- a/src/Framework/Resources/xlf/SR.ko.xlf +++ b/src/Framework/Resources/xlf/SR.ko.xlf @@ -57,6 +57,11 @@ The value cannot be an empty string. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + 임시 파일을 만들지 못했습니다. 임시 파일 폴더가 꽉 찼거나 경로가 올바르지 않습니다. {0} + + Path must be rooted. 루트 경로로 지정해야 합니다. diff --git a/src/Framework/Resources/xlf/SR.pl.xlf b/src/Framework/Resources/xlf/SR.pl.xlf index f19731bc3fc..aa2d433ae5b 100644 --- a/src/Framework/Resources/xlf/SR.pl.xlf +++ b/src/Framework/Resources/xlf/SR.pl.xlf @@ -57,6 +57,11 @@ The value cannot be an empty string. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + Nie można utworzyć pliku tymczasowego. Folder plików tymczasowych jest zapełniony lub jego ścieżka jest niepoprawna. {0} + + Path must be rooted. Ścieżka musi zaczynać się od katalogu głównego. diff --git a/src/Framework/Resources/xlf/SR.pt-BR.xlf b/src/Framework/Resources/xlf/SR.pt-BR.xlf index b5f68f74392..aca940a4c33 100644 --- a/src/Framework/Resources/xlf/SR.pt-BR.xlf +++ b/src/Framework/Resources/xlf/SR.pt-BR.xlf @@ -57,6 +57,11 @@ The value cannot be an empty string. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + Falha ao criar arquivo temporário. A pasta de arquivos temporários está cheia ou o caminho está incorreto. {0} + + Path must be rooted. Caminho deve ter raiz. diff --git a/src/Framework/Resources/xlf/SR.ru.xlf b/src/Framework/Resources/xlf/SR.ru.xlf index 863113e65df..b462b2f737a 100644 --- a/src/Framework/Resources/xlf/SR.ru.xlf +++ b/src/Framework/Resources/xlf/SR.ru.xlf @@ -57,6 +57,11 @@ The value cannot be an empty string. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + не удалось создать временный файл. Папка временных файлов переполнена, или указан неверный путь. {0} + + Path must be rooted. Путь должен иметь корень. diff --git a/src/Framework/Resources/xlf/SR.tr.xlf b/src/Framework/Resources/xlf/SR.tr.xlf index 9a735a7fb52..fbf2b0797f9 100644 --- a/src/Framework/Resources/xlf/SR.tr.xlf +++ b/src/Framework/Resources/xlf/SR.tr.xlf @@ -57,6 +57,11 @@ The value cannot be an empty string. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + Geçici bir dosya oluşturulamadı. Geçici dosyalar klasörü dolu veya yolu hatalı. {0} + + Path must be rooted. Yol kökü belirtilmelidir. diff --git a/src/Framework/Resources/xlf/SR.zh-Hans.xlf b/src/Framework/Resources/xlf/SR.zh-Hans.xlf index 16a40b3360a..b8dd5f66da4 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hans.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hans.xlf @@ -57,6 +57,11 @@ The value cannot be an empty string. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + 未能创建临时文件。临时文件文件夹已满或其路径不正确。{0} + + Path must be rooted. 路径必须是根路径。 diff --git a/src/Framework/Resources/xlf/SR.zh-Hant.xlf b/src/Framework/Resources/xlf/SR.zh-Hant.xlf index 3f2b2ee9276..a7e66f308db 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hant.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hant.xlf @@ -57,6 +57,11 @@ The value cannot be an empty string. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + 無法建立暫存檔案。暫存檔案資料夾已滿或其路徑錯誤。{0} + + Path must be rooted. 路徑必須為根路徑。 diff --git a/src/Shared/TempFileUtilities.cs b/src/Shared/TempFileUtilities.cs index c24c8923e64..1b3535ebbd9 100644 --- a/src/Shared/TempFileUtilities.cs +++ b/src/Shared/TempFileUtilities.cs @@ -1,12 +1,7 @@ // 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.Runtime.CompilerServices; -using System.Threading; using Microsoft.Build.Framework; -using Microsoft.Build.Shared.FileSystem; #nullable disable @@ -18,227 +13,44 @@ namespace Microsoft.Build.Shared /// internal static partial class FileUtilities { - private static Lazy tempFileDirectory = CreateTempFileDirectoryLazy(); - - private const string msbuildTempFolderPrefix = "MSBuildTemp"; - - internal static string TempFileDirectory => tempFileDirectory.Value; - - private static Lazy CreateTempFileDirectoryLazy() - { - return new Lazy( - () => - { - string path = CreateFolderUnderTemp(); - RegisterCleanupOnExit(path); - return path; - }, - LazyThreadSafetyMode.ExecutionAndPublication); - } - - private static void RegisterCleanupOnExit(string pathToCleanup) - { - AppDomain.CurrentDomain.ProcessExit += (_, _) => - { - try - { - if (Directory.Exists(pathToCleanup)) - { - Directory.Delete(pathToCleanup, recursive: true); - } - } - catch - { - // Best effort - ignore failures during cleanup - } - }; - } + /// + internal static string TempFileDirectory + => FrameworkFileUtilities.TempFileDirectory; + /// internal static void ClearTempFileDirectory() - { - tempFileDirectory = CreateTempFileDirectoryLazy(); - } - - private static string CreateFolderUnderTemp() - { - string path; - -#if NET - path = Directory.CreateTempSubdirectory(msbuildTempFolderPrefix).FullName; -#else - // CreateTempSubdirectory API is not available in .NET Framework - path = Path.Combine(Path.GetTempPath(), $"{msbuildTempFolderPrefix}{Guid.NewGuid():N}"); - Directory.CreateDirectory(path); -#endif + => FrameworkFileUtilities.ClearTempFileDirectory(); - return FrameworkFileUtilities.EnsureTrailingSlash(path); - } - - /// - /// Generates a unique directory name in the temporary folder. - /// Caller must delete when finished. - /// - /// - /// + /// internal static string GetTemporaryDirectory(bool createDirectory = true, string subfolder = null) - { - string temporaryDirectory = Path.Combine(TempFileDirectory, $"Temporary{Guid.NewGuid():N}", subfolder ?? string.Empty); - - if (createDirectory) - { - Directory.CreateDirectory(temporaryDirectory); - } - - return temporaryDirectory; - } + => FrameworkFileUtilities.GetTemporaryDirectory(createDirectory, subfolder); - /// - /// Generates a unique temporary file name with a given extension in the temporary folder. - /// File is guaranteed to be unique. - /// Extension may have an initial period. - /// File will NOT be created. - /// May throw IOException. - /// + /// internal static string GetTemporaryFileName() - { - return GetTemporaryFileName(".tmp"); - } + => FrameworkFileUtilities.GetTemporaryFileName(); - /// - /// Generates a unique temporary file name with a given extension in the temporary folder. - /// File is guaranteed to be unique. - /// Extension may have an initial period. - /// File will NOT be created. - /// May throw IOException. - /// + /// internal static string GetTemporaryFileName(string extension) - { - return GetTemporaryFile(null, null, extension, false); - } + => FrameworkFileUtilities.GetTemporaryFileName(extension); - /// - /// Generates a unique temporary file name with a given extension in the temporary folder. - /// If no extension is provided, uses ".tmp". - /// File is guaranteed to be unique. - /// Caller must delete it when finished. - /// + /// internal static string GetTemporaryFile() - { - return GetTemporaryFile(".tmp"); - } + => FrameworkFileUtilities.GetTemporaryFile(); - /// - /// Generates a unique temporary file name with a given extension in the temporary folder. - /// File is guaranteed to be unique. - /// Caller must delete it when finished. - /// + /// internal static string GetTemporaryFile(string fileName, string extension, bool createFile) - { - return GetTemporaryFile(null, fileName, extension, createFile); - } + => FrameworkFileUtilities.GetTemporaryFile(fileName, extension, createFile); - /// - /// Generates a unique temporary file name with a given extension in the temporary folder. - /// File is guaranteed to be unique. - /// Extension may have an initial period. - /// Caller must delete it when finished. - /// May throw IOException. - /// + /// internal static string GetTemporaryFile(string extension) - { - return GetTemporaryFile(null, null, extension); - } + => FrameworkFileUtilities.GetTemporaryFile(extension); - /// - /// Creates a file with unique temporary file name with a given extension in the specified folder. - /// File is guaranteed to be unique. - /// Extension may have an initial period. - /// If folder is null, the temporary folder will be used. - /// Caller must delete it when finished. - /// May throw IOException. - /// + /// internal static string GetTemporaryFile(string directory, string fileName, string extension, bool createFile = true) - { - ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(directory, nameof(directory)); - - try - { - directory ??= TempFileDirectory; - - // If the extension needs a dot prepended, do so. - if (extension is null) - { - extension = string.Empty; - } - else if (extension.Length > 0 && extension[0] != '.') - { - extension = '.' + extension; - } - - // If the fileName is null, use tmp{Guid}; otherwise use fileName. - if (string.IsNullOrEmpty(fileName)) - { - fileName = $"tmp{Guid.NewGuid():N}"; - } - - Directory.CreateDirectory(directory); - - string file = Path.Combine(directory, $"{fileName}{extension}"); - - ErrorUtilities.VerifyThrow(!FileSystems.Default.FileExists(file), "Guid should be unique"); - - if (createFile) - { - File.WriteAllText(file, string.Empty); - } - - return file; - } - catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) - { - throw new IOException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("Shared.FailedCreatingTempFile", ex.Message), ex); - } - } + => FrameworkFileUtilities.GetTemporaryFile(directory, fileName, extension, createFile); + /// internal static void CopyDirectory(string source, string dest) - { - Directory.CreateDirectory(dest); - - DirectoryInfo sourceInfo = new DirectoryInfo(source); - foreach (var fileInfo in sourceInfo.GetFiles()) - { - string destFile = Path.Combine(dest, fileInfo.Name); - fileInfo.CopyTo(destFile); - } - foreach (var subdirInfo in sourceInfo.GetDirectories()) - { - string destDir = Path.Combine(dest, subdirInfo.Name); - CopyDirectory(subdirInfo.FullName, destDir); - } - } - - public sealed class TempWorkingDirectory : IDisposable - { - public string Path { get; } - - public TempWorkingDirectory(string sourcePath, [CallerMemberName] string name = null) - { - Path = name == null - ? GetTemporaryDirectory() - : System.IO.Path.Combine(TempFileDirectory, name); - - if (FileSystems.Default.DirectoryExists(Path)) - { - Directory.Delete(Path, true); - } - - CopyDirectory(sourcePath, Path); - } - - public void Dispose() - { - Directory.Delete(Path, true); - } - } + => FrameworkFileUtilities.CopyDirectory(source, dest); } } diff --git a/src/Shared/UnitTests/TypeLoader_Dependencies_Tests.cs b/src/Shared/UnitTests/TypeLoader_Dependencies_Tests.cs index 191642f2806..52ce9eddc1e 100644 --- a/src/Shared/UnitTests/TypeLoader_Dependencies_Tests.cs +++ b/src/Shared/UnitTests/TypeLoader_Dependencies_Tests.cs @@ -3,6 +3,7 @@ using System.IO; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.UnitTests.Shared; using Shouldly; @@ -22,7 +23,7 @@ public class TypeLoader_Dependencies_Tests [Fact] public void LoadAssemblyAndDependency_InsideProjectFolder() { - using (var dir = new FileUtilities.TempWorkingDirectory(ProjectFileFolder)) + using (var dir = new FrameworkFileUtilities.TempWorkingDirectory(ProjectFileFolder)) { string projectFilePath = Path.Combine(dir.Path, ProjectFileName); @@ -40,7 +41,7 @@ public void LoadAssemblyAndDependency_InsideProjectFolder() [Fact] public void LoadAssemblyAndDependency_OutsideProjectFolder() { - using (var dir = new FileUtilities.TempWorkingDirectory(ProjectFileFolder)) + using (var dir = new FrameworkFileUtilities.TempWorkingDirectory(ProjectFileFolder)) { string projectFilePath = Path.Combine(dir.Path, ProjectFileName); diff --git a/src/Shared/UnitTests/TypeLoader_Tests.cs b/src/Shared/UnitTests/TypeLoader_Tests.cs index 764f5c4104a..03b9c1137d8 100644 --- a/src/Shared/UnitTests/TypeLoader_Tests.cs +++ b/src/Shared/UnitTests/TypeLoader_Tests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Reflection; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.UnitTests.Shared; using Shouldly; @@ -62,7 +63,7 @@ public void Regress_Mutation_ParameterOrderDoesntMatter() [Fact] public void LoadNonExistingAssembly() { - using var dir = new FileUtilities.TempWorkingDirectory(ProjectFileFolder); + using var dir = new FrameworkFileUtilities.TempWorkingDirectory(ProjectFileFolder); string projectFilePath = Path.Combine(dir.Path, ProjectFileName); @@ -79,7 +80,7 @@ public void LoadNonExistingAssembly() [Fact] public void LoadInsideAsssembly() { - using (var dir = new FileUtilities.TempWorkingDirectory(ProjectFileFolder)) + using (var dir = new FrameworkFileUtilities.TempWorkingDirectory(ProjectFileFolder)) { string projectFilePath = Path.Combine(dir.Path, ProjectFileName); @@ -118,7 +119,7 @@ public void LoadTaskDependingOnMSBuild() [Fact] public void LoadOutsideAssembly() { - using (var dir = new FileUtilities.TempWorkingDirectory(ProjectFileFolder)) + using (var dir = new FrameworkFileUtilities.TempWorkingDirectory(ProjectFileFolder)) { string projectFilePath = Path.Combine(dir.Path, ProjectFileName); string originalDLLPath = Path.Combine(dir.Path, DLLFileName); @@ -143,7 +144,7 @@ public void LoadOutsideAssembly() [Fact(Skip = "https://github.com/dotnet/msbuild/issues/325")] public void LoadInsideAssemblyWhenGivenOutsideAssemblyWithSameName() { - using (var dir = new FileUtilities.TempWorkingDirectory(ProjectFileFolder)) + using (var dir = new FrameworkFileUtilities.TempWorkingDirectory(ProjectFileFolder)) { string projectFilePath = Path.Combine(dir.Path, ProjectFileName); string originalDLLPath = Path.Combine(dir.Path, DLLFileName); From abf128e28b272eac18e90806d385b4833ce24280 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 9 Mar 2026 11:43:37 -0700 Subject: [PATCH 11/27] Move StreamExtensions from FileUtilities.cs to Framework - Move StreamExtensions from src/Shared/FileUtilities.cs to src/Framework/Polyfills/StreamExtensions.cs --- src/Framework/Polyfills/StreamExtensions.cs | 29 +++++++++++++++++ src/Shared/FileUtilities.cs | 35 --------------------- 2 files changed, 29 insertions(+), 35 deletions(-) create mode 100644 src/Framework/Polyfills/StreamExtensions.cs diff --git a/src/Framework/Polyfills/StreamExtensions.cs b/src/Framework/Polyfills/StreamExtensions.cs new file mode 100644 index 00000000000..098c882fc78 --- /dev/null +++ b/src/Framework/Polyfills/StreamExtensions.cs @@ -0,0 +1,29 @@ +// 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 Microsoft.Build; + +namespace System.IO; + +internal static class StreamExtensions +{ + internal static void ReadExactly(this Stream stream, byte[] buffer, int offset, int count) + { + ArgumentNullException.ThrowIfNull(buffer); + ArgumentOutOfRangeException.ThrowIfNegative(offset); + ArgumentOutOfRangeException.ThrowIfGreaterThan((uint)count, (uint)(buffer.Length - offset)); + + 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/Shared/FileUtilities.cs b/src/Shared/FileUtilities.cs index 12eacad01b7..171229b3cc7 100644 --- a/src/Shared/FileUtilities.cs +++ b/src/Shared/FileUtilities.cs @@ -1514,38 +1514,3 @@ internal static void ReadFromStream(this Stream stream, byte[] content, int star } } } - -#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 07d931d5cb60991eb4be05868ef2d2304f819c5f Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 10 Mar 2026 09:07:35 -0700 Subject: [PATCH 12/27] Prepare FrameworkFileUtiliites for FileUtilities merge - Add 'NewPath' type alias for Microsoft.IO.Path when compiling for net472. This will facilitate the process of copying members from `src/Shared/FileUtilities.cs` that all use System.IO.Path. --- src/Framework/FileUtilities.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Framework/FileUtilities.cs b/src/Framework/FileUtilities.cs index 8dbe6dd06f6..563589e1478 100644 --- a/src/Framework/FileUtilities.cs +++ b/src/Framework/FileUtilities.cs @@ -4,9 +4,9 @@ using System.Threading; #if NETFRAMEWORK -using Path = Microsoft.IO.Path; +using NewPath = Microsoft.IO.Path; #else -using System.IO; +using NewPath = System.IO.Path; #endif namespace Microsoft.Build.Framework @@ -45,7 +45,7 @@ internal static string? CurrentThreadWorkingDirectory /// true, if slash internal static bool IsSlash(char c) { - return (c == Path.DirectorySeparatorChar) || (c == Path.AltDirectorySeparatorChar); + return (c == NewPath.DirectorySeparatorChar) || (c == NewPath.AltDirectorySeparatorChar); } /// @@ -66,7 +66,7 @@ internal static bool EndsWithSlash(string fileSpec) /// internal static string FixFilePath(string path) { - return string.IsNullOrEmpty(path) || Path.DirectorySeparatorChar == WindowsDirectorySeparator ? path : path.Replace(WindowsDirectorySeparator, UnixDirectorySeparator); + return string.IsNullOrEmpty(path) || NewPath.DirectorySeparatorChar == WindowsDirectorySeparator ? path : path.Replace(WindowsDirectorySeparator, UnixDirectorySeparator); } /// @@ -80,7 +80,7 @@ internal static string EnsureTrailingSlash(string fileSpec) fileSpec = FixFilePath(fileSpec); if (fileSpec.Length > 0 && !IsSlash(fileSpec[fileSpec.Length - 1])) { - fileSpec += Path.DirectorySeparatorChar; + fileSpec += NewPath.DirectorySeparatorChar; } return fileSpec; @@ -207,7 +207,7 @@ internal static AbsolutePath NormalizePath(AbsolutePath path) return path; } - return new AbsolutePath(FixFilePath(Path.GetFullPath(path.Value)), + return new AbsolutePath(FixFilePath(NewPath.GetFullPath(path.Value)), original: path.OriginalValue, ignoreRootedCheck: true); } From 2741dffc2fc3b3a62d7ee88e779d2749d1e70c04 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 9 Mar 2026 16:12:57 -0700 Subject: [PATCH 13/27] Move FileUtilities member implementations to Framework - Move implementations of all members (except ExecutingAssemblyPath) from src/Shared/FileUtilities.cs to src/Framework/FileUtilities.cs and leave stubs behind. The stubs will be cleaned up in a later commit. - Add "DebugPathTooLong" and "InvalidGetPathOfFileAboveParameter" string resources, taking care to copy the translated strings. - Add 'Path' and 'NewPath' aliases to avoid conflicts between System.IO.Path and Microsoft.IO.Path when compiling for net472 and netstandard2.0. This ensures that members copied from src/Shared/FileUtilities.cs continues to call into System.IO.Path, which is necessary to throw expected path exceptions when running on .NET Framework. --- src/Framework/ErrorUtilities.cs | 17 + src/Framework/FileUtilities.cs | 1494 ++++++++++++++++++- src/Framework/Resources/SR.resx | 6 + src/Framework/Resources/xlf/SR.cs.xlf | 10 + src/Framework/Resources/xlf/SR.de.xlf | 10 + src/Framework/Resources/xlf/SR.es.xlf | 10 + src/Framework/Resources/xlf/SR.fr.xlf | 10 + src/Framework/Resources/xlf/SR.it.xlf | 10 + src/Framework/Resources/xlf/SR.ja.xlf | 10 + src/Framework/Resources/xlf/SR.ko.xlf | 10 + src/Framework/Resources/xlf/SR.pl.xlf | 10 + src/Framework/Resources/xlf/SR.pt-BR.xlf | 10 + src/Framework/Resources/xlf/SR.ru.xlf | 10 + src/Framework/Resources/xlf/SR.tr.xlf | 10 + src/Framework/Resources/xlf/SR.zh-Hans.xlf | 10 + src/Framework/Resources/xlf/SR.zh-Hant.xlf | 10 + src/Shared/FileUtilities.cs | 1506 +++----------------- 17 files changed, 1804 insertions(+), 1349 deletions(-) diff --git a/src/Framework/ErrorUtilities.cs b/src/Framework/ErrorUtilities.cs index e9b9275d7c8..c0534973ccf 100644 --- a/src/Framework/ErrorUtilities.cs +++ b/src/Framework/ErrorUtilities.cs @@ -45,6 +45,23 @@ internal static void VerifyThrowInternalNull([NotNull] object? parameter, [Calle } } + /// + /// 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", innerException: null, args: parameterName); + } + } + /// /// Throws InternalErrorException. /// This is only for situations that would mean that there is a bug in MSBuild itself. diff --git a/src/Framework/FileUtilities.cs b/src/Framework/FileUtilities.cs index 563589e1478..c81e6499103 100644 --- a/src/Framework/FileUtilities.cs +++ b/src/Framework/FileUtilities.cs @@ -1,12 +1,29 @@ // 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.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; using System.Threading; +using Microsoft.Build.Shared; +using Microsoft.Build.Shared.FileSystem; #if NETFRAMEWORK using NewPath = Microsoft.IO.Path; +using Path = System.IO.Path; #else using NewPath = System.IO.Path; +using Path = System.IO.Path; #endif namespace Microsoft.Build.Framework @@ -38,6 +55,117 @@ internal static string? CurrentThreadWorkingDirectory set => s_currentThreadWorkingDirectory.Value = value; } + /// + /// The directory where MSBuild stores cache information used during the build. + /// + internal static string? cacheDirectory = null; + + /// + /// 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; + } + + /// + /// Clears the MSBuild runtime cache + /// + internal static void ClearCacheDirectory() + { + string cacheDirectory = GetCacheDirectory(); + + if (DefaultFileSystem.DirectoryExists(cacheDirectory)) + { + DeleteDirectoryNoThrow(cacheDirectory, true); + } + } + /// /// Indicates if the given character is a slash in current OS. /// @@ -45,7 +173,7 @@ internal static string? CurrentThreadWorkingDirectory /// true, if slash internal static bool IsSlash(char c) { - return (c == NewPath.DirectorySeparatorChar) || (c == NewPath.AltDirectorySeparatorChar); + return (c == Path.DirectorySeparatorChar) || (c == Path.AltDirectorySeparatorChar); } /// @@ -66,7 +194,7 @@ internal static bool EndsWithSlash(string fileSpec) /// internal static string FixFilePath(string path) { - return string.IsNullOrEmpty(path) || NewPath.DirectorySeparatorChar == WindowsDirectorySeparator ? path : path.Replace(WindowsDirectorySeparator, UnixDirectorySeparator); + return string.IsNullOrEmpty(path) || Path.DirectorySeparatorChar == WindowsDirectorySeparator ? path : path.Replace(WindowsDirectorySeparator, UnixDirectorySeparator); } /// @@ -80,7 +208,7 @@ internal static string EnsureTrailingSlash(string fileSpec) fileSpec = FixFilePath(fileSpec); if (fileSpec.Length > 0 && !IsSlash(fileSpec[fileSpec.Length - 1])) { - fileSpec += NewPath.DirectorySeparatorChar; + fileSpec += Path.DirectorySeparatorChar; } return fileSpec; @@ -226,5 +354,1365 @@ internal static AbsolutePath FixFilePath(AbsolutePath path) original: path.OriginalValue, ignoreRootedCheck: true); } + + /// + /// 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 (NativeMethods.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) + { + throw new ArgumentException(SR.FormatDebugPathTooLong(directory)); + } + catch (Exception) + { + return false; + } + } + + /// + /// 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 && IsSlash(path[start])) + { + start++; + } + while (start < stop && IsSlash(path[stop - 1])) + { + stop--; + } + + return 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 && IsSlash(path[start])) + { + start++; + } + + return FixFilePath(start < stop && 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 = 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. + /// + [return: NotNullIfNotNull(nameof(path))] + 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 FixFilePath(fullPath.Substring(0, i)); + } + return null; + } + + internal static string TruncatePathToTrailingSegments(string path, int trailingSegmentsToKeep) + { + FrameworkErrorUtilities.VerifyThrowInternalLength(path, nameof(path)); + FrameworkErrorUtilities.VerifyThrow(trailingSegmentsToKeep >= 0, "trailing segments must be positive"); + + var segments = path.Split(Slashes, StringSplitOptions.RemoveEmptyEntries); + + var headingSegmentsToRemove = Math.Max(0, segments.Length - trailingSegmentsToKeep); + + return string.Join(DirectorySeparatorString, segments.Skip(headingSegmentsToRemove)); + } + + 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; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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) + { + ArgumentException.ThrowIfNullOrEmpty(path); + string fullPath = GetFullPath(path); + return FixFilePath(fullPath); + } + + internal static string NormalizePath(string directory, string file) + { + return NormalizePath(Path.Combine(directory, file)); + } + + internal static string NormalizePath(params string[] paths) + { + return NormalizePath(Path.Combine(paths)); + } + + private static string GetFullPath(string path) + { +#if FEATURE_LEGACY_GETFULLPATH + if (NativeMethods.IsWindows) + { + string uncheckedFullPath = NativeMethods.GetFullPath(path); + + if (IsPathTooLong(uncheckedFullPath)) + { + throw new PathTooLongException(SR.FormatPathTooLong(path, NativeMethods.MaxPath)); + } + + // 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 (!NativeMethods.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 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 (NativeMethods.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 (NativeMethods.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; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsAnySlash(char c) => c == '/' || c == '\\'; + + /// + /// 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 (NativeMethods.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())); + } + + /// + /// Extracts the directory from the given file-spec. + /// + /// The filespec. + /// directory path + internal static string GetDirectory(string 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 + 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; + } + + /// + /// 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 (FrameworkExceptionHandling.IsIoRelatedException(ex)) + { + // If we can't enumerate the directories, ignore. Other cases should be handled by DeleteDirectoryNoThrow. + } + } + + /// + /// 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 (allowedExtensions != null && 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"; + + /// + /// 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 = 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 (NativeMethods.IsWindows && !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 (FrameworkExceptionHandling.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 = NormalizeForPathComparison(path); + 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(Slashes); + return path.AsSpan(lastDirectorySeparator >= 0 ? lastDirectorySeparator + 1 : 0).ContainsAny(InvalidFileNameChars); + } +#else + if (path.IndexOfAny(InvalidPathChars) < 0) + { + int lastDirectorySeparator = path.LastIndexOfAny(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(FixFilePath(path)); + } + catch (Exception ex) when (FrameworkExceptionHandling.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 = FixFilePath(path); + + for (int i = 0; i < retryCount; i++) + { + try + { + if (DefaultFileSystem.DirectoryExists(path)) + { + Directory.Delete(path, recursive); + break; + } + } + catch (Exception ex) when (FrameworkExceptionHandling.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(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 (FrameworkExceptionHandling.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) + { + ArgumentNullException.ThrowIfNull(basePath); + ArgumentException.ThrowIfNullOrEmpty(path); + + string fullBase = GetFullPath(basePath); + string fullPath = GetFullPath(path); + + string[] splitBase = fullBase.Split(MSBuildConstants.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); + string[] splitPath = fullPath.Split(MSBuildConstants.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); + + FrameworkErrorUtilities.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 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 FixFilePath(path); + } + 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(FixFilePath(path)); + } + catch (Exception ex) when (FrameworkExceptionHandling.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) + { + ArgumentNullException.ThrowIfNull(root); + ArgumentNullException.ThrowIfNull(paths); + + return paths.Aggregate(root, Path.Combine); + } + + internal static string TrimTrailingSlashes(string s) + { + return s.TrimEnd(Slashes); + } + + /// + /// Replace all backward slashes to forward slashes + /// + internal static string ToSlash(string s) + { + return s.Replace('\\', '/'); + } + + internal static string ToBackslash(string s) + { + return s.Replace('/', '\\'); + } + + /// + /// Ensure all slashes are the current platform's slash + /// + /// + /// + internal static string ToPlatformSlash(string s) + { + var separator = Path.DirectorySeparatorChar; + + return s.Replace(separator == '/' ? '\\' : '/', separator); + } + + internal static string WithTrailingSlash(string s) + { + return EnsureTrailingSlash(s); + } + + internal static string NormalizeForPathComparison(string s) + => TrimTrailingSlashes(ToPlatformSlash(s)); + + // 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))) + { + throw new ArgumentException(SR.FormatInvalidGetPathOfFileAboveParameter(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 = ToSlash(strA); + var slash2 = ToSlash(strB); + + if (string.Compare(slash1, i, slash2, i, length, StringComparison.OrdinalIgnoreCase) == 0) + { + return true; + } + + return false; + } + + /// + /// Clears the file existence cache. + /// + internal static void ClearFileExistenceCache() + { + FileExistenceCache.Clear(); + } + + internal static void ReadFromStream(Stream stream, byte[] content, int startIndex, int length) + { + stream.ReadExactly(content, startIndex, length); + } } } diff --git a/src/Framework/Resources/SR.resx b/src/Framework/Resources/SR.resx index b45b5160f10..3c1aa170589 100644 --- a/src/Framework/Resources/SR.resx +++ b/src/Framework/Resources/SR.resx @@ -159,4 +159,10 @@ Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} + + The parameter '{0}' can only be a file name and cannot include a directory. + + + The path "{0}" used for debug logs is too long. Set it to a shorter value using the MSBUILDDEBUGPATH environment variable or change your system configuration to allow long paths. + \ No newline at end of file diff --git a/src/Framework/Resources/xlf/SR.cs.xlf b/src/Framework/Resources/xlf/SR.cs.xlf index 3868aa76ffd..642b5e45589 100644 --- a/src/Framework/Resources/xlf/SR.cs.xlf +++ b/src/Framework/Resources/xlf/SR.cs.xlf @@ -57,11 +57,21 @@ The value cannot be an empty string. + + The path "{0}" used for debug logs is too long. Set it to a shorter value using the MSBUILDDEBUGPATH environment variable or change your system configuration to allow long paths. + Cesta „{0}“ použitá pro protokoly ladění je příliš dlouhá. Nastavte ji na kratší hodnotu pomocí proměnné prostředí MSBUILDDEBUGPATH nebo změňte konfigurace vašeho systému, aby povolovala dlouhé cesty. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} 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} + + The parameter '{0}' can only be a file name and cannot include a directory. + Parametr {0} může být jenom název souboru a nemůže obsahovat adresář. + + Path must be rooted. Cesta musí začínat kořenem. diff --git a/src/Framework/Resources/xlf/SR.de.xlf b/src/Framework/Resources/xlf/SR.de.xlf index bbff622e1ce..e5b97f7dae4 100644 --- a/src/Framework/Resources/xlf/SR.de.xlf +++ b/src/Framework/Resources/xlf/SR.de.xlf @@ -57,11 +57,21 @@ The value cannot be an empty string. + + The path "{0}" used for debug logs is too long. Set it to a shorter value using the MSBUILDDEBUGPATH environment variable or change your system configuration to allow long paths. + Der für Debugprotokolle verwendete Pfad "{0}" ist zu lang. Legen Sie den Wert mithilfe der Umgebungsvariablen MSBUILDDEBUGPATH auf einen kürzeren Wert fest, oder ändern Sie die Systemkonfiguration so, dass lange Pfade zulässig sind. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} Fehler beim Erstellen einer temporären Datei. Der Ordner für temporäre Dateien ist voll, oder der Pfad ist falsch. {0} + + The parameter '{0}' can only be a file name and cannot include a directory. + Der Parameter '{0}' kann nur ein Dateiname sein und darf kein Verzeichnis enthalten. + + Path must be rooted. Der Pfad muss einen Stamm besitzen. diff --git a/src/Framework/Resources/xlf/SR.es.xlf b/src/Framework/Resources/xlf/SR.es.xlf index 348eed9655e..fcf6502dd92 100644 --- a/src/Framework/Resources/xlf/SR.es.xlf +++ b/src/Framework/Resources/xlf/SR.es.xlf @@ -57,11 +57,21 @@ The value cannot be an empty string. + + The path "{0}" used for debug logs is too long. Set it to a shorter value using the MSBUILDDEBUGPATH environment variable or change your system configuration to allow long paths. + La ruta de acceso "{0}" usada para los registros de depuración es demasiado larga. Establézcalo en un valor más corto con la variable de entorno MSBUILDDEBUGPATH o cambie la configuración del sistema para permitir rutas de acceso largas. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} Error al crear un archivo temporal. La carpeta de archivos temporales está llena o la ruta de acceso no es correcta. {0} + + The parameter '{0}' can only be a file name and cannot include a directory. + El parámetro "{0}" solo puede ser el nombre de un archivo y no puede incluir un directorio. + + Path must be rooted. Debe ser una ruta de acceso raíz. diff --git a/src/Framework/Resources/xlf/SR.fr.xlf b/src/Framework/Resources/xlf/SR.fr.xlf index b11d8f4c209..ba17be70e8b 100644 --- a/src/Framework/Resources/xlf/SR.fr.xlf +++ b/src/Framework/Resources/xlf/SR.fr.xlf @@ -57,11 +57,21 @@ The value cannot be an empty string. + + The path "{0}" used for debug logs is too long. Set it to a shorter value using the MSBUILDDEBUGPATH environment variable or change your system configuration to allow long paths. + Le chemin d’accès "{0}" utilisé pour les journaux de débogage est trop long. Définissez-la sur une valeur plus courte à l’aide de la variable d’environnement MSBUILDDEBUGPATH ou modifiez votre configuration système pour autoriser les chemins longs. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} Échec de la création d'un fichier temporaire. Le dossier de fichiers temporaires est plein ou son chemin est incorrect. {0} + + The parameter '{0}' can only be a file name and cannot include a directory. + Le paramètre "{0}" peut uniquement être un nom de fichier et ne peut pas inclure de répertoire. + + Path must be rooted. Le chemin doit être associé à une racine. diff --git a/src/Framework/Resources/xlf/SR.it.xlf b/src/Framework/Resources/xlf/SR.it.xlf index 184d77a83f4..ceac3e20e76 100644 --- a/src/Framework/Resources/xlf/SR.it.xlf +++ b/src/Framework/Resources/xlf/SR.it.xlf @@ -57,11 +57,21 @@ The value cannot be an empty string. + + The path "{0}" used for debug logs is too long. Set it to a shorter value using the MSBUILDDEBUGPATH environment variable or change your system configuration to allow long paths. + il percorso "{0}" usato per i log di debug è troppo lungo. Impostarlo su un valore più breve usando la variabile dell'ambiente MSBUILDDEBUGPATH o modificare la configurazione del sistema per consentire percorsi lunghi. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} non è stato possibile creare un file temporaneo. La cartella dei file temporanei è piena oppure il percorso è errato. {0} + + The parameter '{0}' can only be a file name and cannot include a directory. + Il parametro '{0}' può solo essere un nome file e non può includere una directory. + + Path must be rooted. Il percorso deve contenere una radice. diff --git a/src/Framework/Resources/xlf/SR.ja.xlf b/src/Framework/Resources/xlf/SR.ja.xlf index 4edf08f08e4..b2f79051a7e 100644 --- a/src/Framework/Resources/xlf/SR.ja.xlf +++ b/src/Framework/Resources/xlf/SR.ja.xlf @@ -57,11 +57,21 @@ The value cannot be an empty string. + + The path "{0}" used for debug logs is too long. Set it to a shorter value using the MSBUILDDEBUGPATH environment variable or change your system configuration to allow long paths. + デバッグ ログに使用されるパス "{0}" が長すぎます。MSBUILDDEBUGPATH 環境変数を使用して短い値に設定するか、長いパスを許可するようにシステム構成を変更してください。 + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} 一時ファイルを作成できませんでした。一時ファイル フォルダーがいっぱいであるか、またはそのパスが正しくありません。{0} + + The parameter '{0}' can only be a file name and cannot include a directory. + パラメーター '{0}' に使用できるのはファイル名のみで、ディレクトリを含めることはできません。 + + Path must be rooted. パスはルート指定パスである必要があります。 diff --git a/src/Framework/Resources/xlf/SR.ko.xlf b/src/Framework/Resources/xlf/SR.ko.xlf index 1460c14ba8b..187f2e6b5e9 100644 --- a/src/Framework/Resources/xlf/SR.ko.xlf +++ b/src/Framework/Resources/xlf/SR.ko.xlf @@ -57,11 +57,21 @@ The value cannot be an empty string. + + The path "{0}" used for debug logs is too long. Set it to a shorter value using the MSBUILDDEBUGPATH environment variable or change your system configuration to allow long paths. + 디버그 로그에 사용된 경로 "{0}"이(가) 너무 깁니다. MSBUILDDEBUGPATH 환경 변수를 사용하여 값을 더 짧게 설정하거나 긴 경로를 허용하도록 시스템 구성을 변경합니다. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} 임시 파일을 만들지 못했습니다. 임시 파일 폴더가 꽉 찼거나 경로가 올바르지 않습니다. {0} + + The parameter '{0}' can only be a file name and cannot include a directory. + '{0}' 매개 변수는 파일 이름일 수만 있으며 디렉터리를 포함할 수 없습니다. + + Path must be rooted. 루트 경로로 지정해야 합니다. diff --git a/src/Framework/Resources/xlf/SR.pl.xlf b/src/Framework/Resources/xlf/SR.pl.xlf index aa2d433ae5b..af3f15a6d5b 100644 --- a/src/Framework/Resources/xlf/SR.pl.xlf +++ b/src/Framework/Resources/xlf/SR.pl.xlf @@ -57,11 +57,21 @@ The value cannot be an empty string. + + The path "{0}" used for debug logs is too long. Set it to a shorter value using the MSBUILDDEBUGPATH environment variable or change your system configuration to allow long paths. + Ścieżka „{0}” używana w dziennikach debugowania jest za długa. Ustaw ją na krótszą wartość przy użyciu zmiennej środowiskowej MSBUILDEBUGPATH lub zmień konfigurację systemu, aby zezwolić na długie ścieżki. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} Nie można utworzyć pliku tymczasowego. Folder plików tymczasowych jest zapełniony lub jego ścieżka jest niepoprawna. {0} + + The parameter '{0}' can only be a file name and cannot include a directory. + Parametr „{0}” może zawierać tylko nazwę pliku i nie może zawierać katalogu. + + Path must be rooted. Ścieżka musi zaczynać się od katalogu głównego. diff --git a/src/Framework/Resources/xlf/SR.pt-BR.xlf b/src/Framework/Resources/xlf/SR.pt-BR.xlf index aca940a4c33..0e379fdab13 100644 --- a/src/Framework/Resources/xlf/SR.pt-BR.xlf +++ b/src/Framework/Resources/xlf/SR.pt-BR.xlf @@ -57,11 +57,21 @@ The value cannot be an empty string. + + The path "{0}" used for debug logs is too long. Set it to a shorter value using the MSBUILDDEBUGPATH environment variable or change your system configuration to allow long paths. + O caminho "{0}" usado para logs de depuração é muito longo. Defina-o para um valor mais curto usando a variável de ambiente MSBUILDDEBUGPATH ou altere a configuração do sistema para permitir caminhos longos. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} Falha ao criar arquivo temporário. A pasta de arquivos temporários está cheia ou o caminho está incorreto. {0} + + The parameter '{0}' can only be a file name and cannot include a directory. + O parâmetro '{0}' pode ser somente um nome de arquivo e não pode incluir um diretório. + + Path must be rooted. Caminho deve ter raiz. diff --git a/src/Framework/Resources/xlf/SR.ru.xlf b/src/Framework/Resources/xlf/SR.ru.xlf index b462b2f737a..20ece505407 100644 --- a/src/Framework/Resources/xlf/SR.ru.xlf +++ b/src/Framework/Resources/xlf/SR.ru.xlf @@ -57,11 +57,21 @@ The value cannot be an empty string. + + The path "{0}" used for debug logs is too long. Set it to a shorter value using the MSBUILDDEBUGPATH environment variable or change your system configuration to allow long paths. + слишком длинный путь "{0}" для журналов отладки. Установите более короткое значение, используя переменную среду MSBUILDDEBUGPATH, или измените конфигурацию системы, чтобы разрешить длинные пути. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} не удалось создать временный файл. Папка временных файлов переполнена, или указан неверный путь. {0} + + The parameter '{0}' can only be a file name and cannot include a directory. + Параметр "{0}" может быть только именем файла и не может включать в себя каталог. + + Path must be rooted. Путь должен иметь корень. diff --git a/src/Framework/Resources/xlf/SR.tr.xlf b/src/Framework/Resources/xlf/SR.tr.xlf index fbf2b0797f9..c5326267a21 100644 --- a/src/Framework/Resources/xlf/SR.tr.xlf +++ b/src/Framework/Resources/xlf/SR.tr.xlf @@ -57,11 +57,21 @@ The value cannot be an empty string. + + The path "{0}" used for debug logs is too long. Set it to a shorter value using the MSBUILDDEBUGPATH environment variable or change your system configuration to allow long paths. + Hata ayıklama günlükleri için kullanılan "{0}" yolu çok uzun. MSBUILDDEBUGPATH ortam değişkenini kullanarak yolu daha kısa bir değere ayarlayın veya sistem yapılandırmanızı uzun yollara izin verecek şekilde değiştirin. + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} Geçici bir dosya oluşturulamadı. Geçici dosyalar klasörü dolu veya yolu hatalı. {0} + + The parameter '{0}' can only be a file name and cannot include a directory. + '{0}' yalnızca bir dosya adı olabilir ve dizin içeremez. + + Path must be rooted. Yol kökü belirtilmelidir. diff --git a/src/Framework/Resources/xlf/SR.zh-Hans.xlf b/src/Framework/Resources/xlf/SR.zh-Hans.xlf index b8dd5f66da4..243328fdfbd 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hans.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hans.xlf @@ -57,11 +57,21 @@ The value cannot be an empty string. + + The path "{0}" used for debug logs is too long. Set it to a shorter value using the MSBUILDDEBUGPATH environment variable or change your system configuration to allow long paths. + 用于调试日志的路径"{0}"太长。使用 MSBUILDDEBUGPATH 环境变量将其设置为较短值,或更改系统配置以允许长路径。 + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} 未能创建临时文件。临时文件文件夹已满或其路径不正确。{0} + + The parameter '{0}' can only be a file name and cannot include a directory. + 参数“{0}”只能是文件名,不能包含目录。 + + Path must be rooted. 路径必须是根路径。 diff --git a/src/Framework/Resources/xlf/SR.zh-Hant.xlf b/src/Framework/Resources/xlf/SR.zh-Hant.xlf index a7e66f308db..cee30682063 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hant.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hant.xlf @@ -57,11 +57,21 @@ The value cannot be an empty string. + + The path "{0}" used for debug logs is too long. Set it to a shorter value using the MSBUILDDEBUGPATH environment variable or change your system configuration to allow long paths. + 用於偵錯記錄檔 "{0}" 的路徑太長。使用 MSBUILDDEBUGPATH 環境變數將它設定為較短的值,或變更您的系統設定以允許長路徑。 + + Failed to create a temporary file. Temporary files folder is full or its path is incorrect. {0} 無法建立暫存檔案。暫存檔案資料夾已滿或其路徑錯誤。{0} + + The parameter '{0}' can only be a file name and cannot include a directory. + 參數 '{0}' 只可以是檔案名稱,不得包含目錄。 + + Path must be rooted. 路徑必須為根路徑。 diff --git a/src/Shared/FileUtilities.cs b/src/Shared/FileUtilities.cs index 171229b3cc7..586f29e35a9 100644 --- a/src/Shared/FileUtilities.cs +++ b/src/Shared/FileUtilities.cs @@ -5,17 +5,11 @@ #if NET using System.Buffers; #endif -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.IO; -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; @@ -30,1487 +24,307 @@ 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; - - /// - /// 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; - } + => FrameworkFileUtilities.ClearCacheDirectoryPath(); - internal static readonly StringComparison PathComparison = GetIsFileSystemCaseSensitive() ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + /// + internal static StringComparison PathComparison + => FrameworkFileUtilities.PathComparison; - internal static readonly StringComparer PathComparer = GetIsFileSystemCaseSensitive() ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + /// + internal static StringComparer PathComparer + => FrameworkFileUtilities.PathComparer; - /// - /// 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; - } - } + => FrameworkFileUtilities.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( + internal static SearchValues InvalidPathChars #else - internal static readonly char[] InvalidPathChars = ( + internal static 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 - ]); + => FrameworkFileUtilities.InvalidPathChars; - /// - /// 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[] InvalidFileNameCharsArray + => FrameworkFileUtilities.InvalidFileNameCharsArray; + /// #if NET - internal static readonly SearchValues InvalidFileNameChars = SearchValues.Create(InvalidFileNameCharsArray); + internal static SearchValues InvalidFileNameChars #else - internal static char[] InvalidFileNameChars => InvalidFileNameCharsArray; + internal static char[] InvalidFileNameChars #endif + => FrameworkFileUtilities.InvalidFileNameChars; - internal static readonly string DirectorySeparatorString = Path.DirectorySeparatorChar.ToString(); - - private static readonly ConcurrentDictionary FileExistenceCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + /// + internal static string DirectorySeparatorString + => FrameworkFileUtilities.DirectorySeparatorString; - 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; - } + => FrameworkFileUtilities.GetCacheDirectory(); - /// - /// Get the hex hash string for the string - /// + /// internal static string GetHexHash(string stringToHash) - { - return stringToHash.GetHashCode().ToString("X", CultureInfo.InvariantCulture); - } + => FrameworkFileUtilities.GetHexHash(stringToHash); - /// - /// 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(); - } + => FrameworkFileUtilities.GetPathsHash(assemblyPaths); - /// - /// 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; - } - } + => FrameworkFileUtilities.CanWriteToDirectory(directory); - /// - /// Clears the MSBuild runtime cache - /// + /// internal static void ClearCacheDirectory() - { - string cacheDirectory = GetCacheDirectory(); - - if (DefaultFileSystem.DirectoryExists(cacheDirectory)) - { - DeleteDirectoryNoThrow(cacheDirectory, true); - } - } + => FrameworkFileUtilities.ClearCacheDirectory(); - /// - /// 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)); - } + => FrameworkFileUtilities.EnsureNoLeadingOrTrailingSlash(path, 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 - } + => FrameworkFileUtilities.EnsureTrailingNoLeadingSlash(path, start); - /// - /// 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); - } + => FrameworkFileUtilities.EnsureSingleQuotes(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); - } + => FrameworkFileUtilities.EnsureDoubleQuotes(path); - /// - /// 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; - } + => FrameworkFileUtilities.EnsureQuotes(path, isSingleQuote); - /// - /// Trims the string and removes any double quotes around it. - /// + /// internal static string TrimAndStripAnyQuotes(string path) - { - if (path is null) - { - return path; - } + => FrameworkFileUtilities.TrimAndStripAnyQuotes(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 GetDirectoryNameOfFullPath(string fullPath) + => FrameworkFileUtilities.GetDirectoryNameOfFullPath(fullPath); + /// internal static string TruncatePathToTrailingSegments(string path, int trailingSegmentsToKeep) - { - 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)); - } + => FrameworkFileUtilities.TruncatePathToTrailingSegments(path, trailingSegmentsToKeep); + /// 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; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool RelativePathBoundsAreValid(string path, int leftIndex, int rightIndex) - { - var leftBound = leftIndex - 1 >= 0 - ? path[leftIndex - 1] - : (char?)null; + => FrameworkFileUtilities.ContainsRelativePathSegments(path); - var rightBound = rightIndex + 1 < path.Length - ? path[rightIndex + 1] - : (char?)null; - - return IsValidRelativePathBound(leftBound) && IsValidRelativePathBound(rightBound); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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); - } + => FrameworkFileUtilities.NormalizePath(path); + /// internal static string NormalizePath(string directory, string file) - { - return NormalizePath(Path.Combine(directory, file)); - } + => FrameworkFileUtilities.NormalizePath(directory, file); + /// internal static string NormalizePath(params string[] paths) - { - return NormalizePath(Path.Combine(paths)); - } - - private static string GetFullPath(string path) - { -#if FEATURE_LEGACY_GETFULLPATH - if (NativeMethodsShared.IsWindows) - { - string uncheckedFullPath = NativeMethodsShared.GetFullPath(path); - - if (FrameworkFileUtilities.IsPathTooLong(uncheckedFullPath)) - { - throw new PathTooLongException(Framework.Resources.SR.FormatPathTooLong(path, NativeMethodsShared.MaxPath)); - } - - // 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 + => FrameworkFileUtilities.NormalizePath(paths); - /// - /// 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('\\', '/'); - } + => FrameworkFileUtilities.NormalizePathSeparatorsToForwardSlash(path); - /// - /// 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; - } + => FrameworkFileUtilities.MaybeAdjustFilePath(value, baseDirectory); - /// - /// 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); - } + => FrameworkFileUtilities.MaybeAdjustFilePath(value, baseDirectory); + /// [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; - } + internal static bool IsAnySlash(char c) + => FrameworkFileUtilities.IsAnySlash(c); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool IsAnySlash(char c) => c == '/' || c == '\\'; - - /// - /// 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); + => FrameworkFileUtilities.LooksLikeUnixFilePath(value, 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; + => FrameworkFileUtilities.LooksLikeUnixFilePath(value, baseDirectory); - // 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())); - } - - /// - /// 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; - } + => FrameworkFileUtilities.GetDirectory(fileSpec); - /// - /// 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. - } - } + => FrameworkFileUtilities.DeleteSubdirectoriesNoThrow(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"; + => FrameworkFileUtilities.HasExtension(fileName, allowedExtensions); - /// - /// Get the currently executing assembly path - /// - internal static string ExecutingAssemblyPath => Path.GetFullPath(AssemblyUtilities.GetAssemblyLocation(typeof(FileUtilities).GetTypeInfo().Assembly)); + /// + internal static string FileTimeFormat + => FrameworkFileUtilities.FileTimeFormat; /// - /// Determines the full path for the given file-spec. - /// ASSUMES INPUT IS STILL ESCAPED + /// Get the currently executing assembly path. /// - /// 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 ExecutingAssemblyPath + => Path.GetFullPath(AssemblyUtilities.GetAssemblyLocation(typeof(FileUtilities).GetTypeInfo().Assembly)); + + /// 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; - } + => FrameworkFileUtilities.GetFullPath(fileSpec, currentDirectory, escape); - /// - /// 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; - } + => FrameworkFileUtilities.GetFullPathNoThrow(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; - } + => FrameworkFileUtilities.ComparePathsNoThrow(first, second, currentDirectory, alwaysIgnoreCase); - 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; - } + => FrameworkFileUtilities.NormalizePathForComparisonNoThrow(path, currentDirectory); + /// 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; - } + => FrameworkFileUtilities.PathIsInvalid(path); - /// - /// 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)) - { - } - } + => FrameworkFileUtilities.DeleteNoThrow(path); - /// - /// 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); - } - } - } + => FrameworkFileUtilities.DeleteDirectoryNoThrow(path, recursive, retryCount, 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); - } - } + => FrameworkFileUtilities.DeleteWithoutTrailingBackslash(path, recursive); - /// - /// 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; - } - } + => FrameworkFileUtilities.GetFileInfoNoThrow(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; - } - } + => FrameworkFileUtilities.DirectoryExistsNoThrow(fullPath, fileSystem); - /// - /// 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; - } - } + => FrameworkFileUtilities.FileExistsNoThrow(fullPath, fileSystem); - /// - /// 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; - } - } + => FrameworkFileUtilities.FileOrDirectoryExistsNoThrow(fullPath, fileSystem); - /// - /// 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"); - } + => FrameworkFileUtilities.IsSolutionFilename(filename); + /// internal static bool IsSolutionFilterFilename(string filename) - { - return HasExtension(filename, ".slnf"); - } + => FrameworkFileUtilities.IsSolutionFilterFilename(filename); + /// internal static bool IsSolutionXFilename(string filename) - { - return HasExtension(filename, ".slnx"); - } + => FrameworkFileUtilities.IsSolutionXFilename(filename); - /// - /// Returns true if the specified filename is a VC++ project file, otherwise returns false - /// + /// internal static bool IsVCProjFilename(string filename) - { - return HasExtension(filename, ".vcproj"); - } + => FrameworkFileUtilities.IsVCProjFilename(filename); + /// internal static bool IsDspFilename(string filename) - { - return HasExtension(filename, ".dsp"); - } + => FrameworkFileUtilities.IsDspFilename(filename); - /// - /// Returns true if the specified filename is a metaproject file (.metaproj), otherwise false. - /// + /// internal static bool IsMetaprojectFilename(string filename) - { - return HasExtension(filename, ".metaproj"); - } + => FrameworkFileUtilities.IsMetaprojectFilename(filename); + /// internal static bool IsBinaryLogFilename(string filename) - { - return HasExtension(filename, ".binlog"); - } - - private static bool HasExtension(string filename, string extension) - { - if (String.IsNullOrEmpty(filename)) - { - return false; - } + => FrameworkFileUtilities.IsBinaryLogFilename(filename); - 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); - } + => FrameworkFileUtilities.MakeRelative(basePath, path); - /// - /// 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 (FrameworkFileUtilities.IsPathTooLong(path) || IsPathTooLongIfRooted(path)) - { - // Attempt to make it shorter -- perhaps there are some \..\ elements - path = GetFullPathNoThrow(path); - } - return FrameworkFileUtilities.FixFilePath(path); - } - 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; - } + => FrameworkFileUtilities.AttemptToShortenPath(path); - /// - /// 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); + => FrameworkFileUtilities.GetFolderAbove(path, count); - 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); - } + => FrameworkFileUtilities.CombinePaths(root, paths); + /// internal static string TrimTrailingSlashes(this string s) - { - return s.TrimEnd(FrameworkFileUtilities.Slashes); - } + => FrameworkFileUtilities.TrimTrailingSlashes(s); - /// - /// Replace all backward slashes to forward slashes - /// + /// internal static string ToSlash(this string s) - { - return s.Replace('\\', '/'); - } + => FrameworkFileUtilities.ToSlash(s); + /// internal static string ToBackslash(this string s) - { - return s.Replace('/', '\\'); - } + => FrameworkFileUtilities.ToBackslash(s); - /// - /// 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); - } + => FrameworkFileUtilities.ToPlatformSlash(s); + /// internal static string WithTrailingSlash(this string s) - { - return FrameworkFileUtilities.EnsureTrailingSlash(s); - } + => FrameworkFileUtilities.WithTrailingSlash(s); - internal static string NormalizeForPathComparison(this string s) => s.ToPlatformSlash().TrimTrailingSlashes(); + /// + internal static string NormalizeForPathComparison(this string s) + => FrameworkFileUtilities.NormalizeForPathComparison(s); - // 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; - } + => FrameworkFileUtilities.PathsEqual(path1, path2); + /// 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); - } - } + => FrameworkFileUtilities.OpenWrite(path, append, 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); - } - } + => FrameworkFileUtilities.OpenRead(path, 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; - } + => FrameworkFileUtilities.GetDirectoryNameOfFileAbove(startingDirectory, fileName, fileSystem); - /// - /// 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); - } + => FrameworkFileUtilities.GetPathOfFileAbove(file, startingDirectory, fileSystem); + /// 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; - } + => FrameworkFileUtilities.EnsureDirectoryExists(directoryPath); - /// - /// Clears the file existence cache. - /// + /// internal static void ClearFileExistenceCache() - { - FileExistenceCache.Clear(); - } + => FrameworkFileUtilities.ClearFileExistenceCache(); + /// internal static void ReadFromStream(this Stream stream, byte[] content, int startIndex, int length) - { - stream.ReadExactly(content, startIndex, length); - } + => FrameworkFileUtilities.ReadFromStream(stream, content, startIndex, length); } } From 3eefb900d5bb1f3a5212038654430e36be2e046c Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 10 Mar 2026 10:34:03 -0700 Subject: [PATCH 14/27] Move Modifiers member implementations to Framework - Add src/Framework/ItemSpecModifiers.cs containing a new ItemSpecModifiers static class. - Move implementations of members from src/Shared/Modifiers.cs to src/Framework/ItemSpecModifiers.cs and leave stubs behind. The stubs will be cleaned up in a later commit. - Add "InvalidFilespecForTransform" string resource, taking care to copy the translated strings from "Shared.InvalidFilespecForTransform" --- src/Framework/ItemSpecModifiers.cs | 446 ++++++++++++++++++++ src/Framework/Resources/SR.resx | 4 + src/Framework/Resources/xlf/SR.cs.xlf | 5 + src/Framework/Resources/xlf/SR.de.xlf | 5 + src/Framework/Resources/xlf/SR.es.xlf | 5 + src/Framework/Resources/xlf/SR.fr.xlf | 5 + src/Framework/Resources/xlf/SR.it.xlf | 5 + src/Framework/Resources/xlf/SR.ja.xlf | 5 + src/Framework/Resources/xlf/SR.ko.xlf | 5 + src/Framework/Resources/xlf/SR.pl.xlf | 5 + src/Framework/Resources/xlf/SR.pt-BR.xlf | 5 + src/Framework/Resources/xlf/SR.ru.xlf | 5 + src/Framework/Resources/xlf/SR.tr.xlf | 5 + src/Framework/Resources/xlf/SR.zh-Hans.xlf | 5 + src/Framework/Resources/xlf/SR.zh-Hant.xlf | 5 + src/Shared/Modifiers.cs | 455 ++------------------- 16 files changed, 547 insertions(+), 423 deletions(-) create mode 100644 src/Framework/ItemSpecModifiers.cs diff --git a/src/Framework/ItemSpecModifiers.cs b/src/Framework/ItemSpecModifiers.cs new file mode 100644 index 00000000000..ce6efe2caac --- /dev/null +++ b/src/Framework/ItemSpecModifiers.cs @@ -0,0 +1,446 @@ +// 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.Frozen; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using Microsoft.Build.Shared; +using Microsoft.Build.Shared.FileSystem; + +#nullable disable + +namespace Microsoft.Build.Framework; + +/// +/// 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 FrozenSet s_tableOfItemSpecModifiers = FrozenSet.Create(StringComparer.OrdinalIgnoreCase, All); + private static readonly FrozenSet s_tableOfDefiningProjectModifiers = FrozenSet.Create(StringComparer.OrdinalIgnoreCase, + [ + DefiningProjectFullPath, + DefiningProjectDirectory, + DefiningProjectName, + DefiningProjectExtension, + ]); + + /// + /// 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) + { + FrameworkErrorUtilities.VerifyThrow(itemSpec != null, "Need item-spec to modify."); + FrameworkErrorUtilities.VerifyThrow(modifier != null, "Need modifier to apply to item-spec."); + + string modifiedItemSpec = null; + + try + { + if (string.Equals(modifier, FullPath, StringComparison.OrdinalIgnoreCase)) + { + if (fullPath != null) + { + return fullPath; + } + + if (currentDirectory == null) + { + currentDirectory = FrameworkFileUtilities.CurrentThreadWorkingDirectory ?? string.Empty; + } + + modifiedItemSpec = FrameworkFileUtilities.GetFullPath(itemSpec, currentDirectory); + fullPath = modifiedItemSpec; + + ThrowForUrl(modifiedItemSpec, itemSpec, currentDirectory); + } + else if (string.Equals(modifier, RootDir, StringComparison.OrdinalIgnoreCase)) + { + GetItemSpecModifier(currentDirectory, itemSpec, definingProjectEscaped, FullPath, ref fullPath); + + modifiedItemSpec = Path.GetPathRoot(fullPath); + + if (!FrameworkFileUtilities.EndsWithSlash(modifiedItemSpec)) + { + FrameworkErrorUtilities.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, 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, 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, RelativeDir, StringComparison.OrdinalIgnoreCase)) + { + modifiedItemSpec = FrameworkFileUtilities.GetDirectory(itemSpec); + } + else if (string.Equals(modifier, Directory, StringComparison.OrdinalIgnoreCase)) + { + GetItemSpecModifier(currentDirectory, itemSpec, definingProjectEscaped, FullPath, ref fullPath); + + modifiedItemSpec = FrameworkFileUtilities.GetDirectory(fullPath); + + if (NativeMethods.IsWindows) + { + int length = -1; + if (FileUtilitiesRegex.StartsWithDrivePattern(modifiedItemSpec)) + { + length = 2; + } + else + { + length = FileUtilitiesRegex.StartsWithUncPatternMatchLength(modifiedItemSpec); + } + + if (length != -1) + { + FrameworkErrorUtilities.VerifyThrow((modifiedItemSpec.Length > length) && FrameworkFileUtilities.IsSlash(modifiedItemSpec[length]), + "Root directory must have a trailing slash."); + + modifiedItemSpec = modifiedItemSpec.Substring(length + 1); + } + } + else + { + FrameworkErrorUtilities.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, RecursiveDir, StringComparison.OrdinalIgnoreCase)) + { + // only the BuildItem class can compute this modifier -- so leave empty + modifiedItemSpec = String.Empty; + } + else if (string.Equals(modifier, Identity, StringComparison.OrdinalIgnoreCase)) + { + modifiedItemSpec = itemSpec; + } + else if (string.Equals(modifier, 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 = FrameworkFileUtilities.GetFileInfoNoThrow(unescapedItemSpec); + + if (info != null) + { + modifiedItemSpec = info.LastWriteTime.ToString(FrameworkFileUtilities.FileTimeFormat, null); + } + else + { + // File does not exist, or path is a directory + modifiedItemSpec = String.Empty; + } + } + else if (string.Equals(modifier, 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(FrameworkFileUtilities.FileTimeFormat, null); + } + else + { + // File does not exist, or path is a directory + modifiedItemSpec = String.Empty; + } + } + else if (string.Equals(modifier, 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(FrameworkFileUtilities.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, DefiningProjectDirectory, StringComparison.OrdinalIgnoreCase)) + { + // ItemSpecModifiers.Directory does not contain the root directory + modifiedItemSpec = Path.Combine( + GetItemSpecModifier(currentDirectory, definingProjectEscaped, null, RootDir), + GetItemSpecModifier(currentDirectory, definingProjectEscaped, null, Directory)); + } + else + { + string additionalModifier = null; + + if (string.Equals(modifier, DefiningProjectFullPath, StringComparison.OrdinalIgnoreCase)) + { + additionalModifier = FullPath; + } + else if (string.Equals(modifier, DefiningProjectName, StringComparison.OrdinalIgnoreCase)) + { + additionalModifier = Filename; + } + else if (string.Equals(modifier, DefiningProjectExtension, StringComparison.OrdinalIgnoreCase)) + { + additionalModifier = Extension; + } + else + { + throw new InternalErrorException($"\"{modifier}\" is not a valid item-spec modifier."); + } + + modifiedItemSpec = GetItemSpecModifier(currentDirectory, definingProjectEscaped, null, additionalModifier); + } + } + } + else + { + throw new InternalErrorException($"\"{modifier}\" is not a valid item-spec modifier."); + } + } + catch (Exception e) when (FrameworkExceptionHandling.IsIoRelatedException(e)) + { + throw new InvalidOperationException(SR.FormatInvalidFilespecForTransform(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/Framework/Resources/SR.resx b/src/Framework/Resources/SR.resx index 3c1aa170589..a21aa59f83b 100644 --- a/src/Framework/Resources/SR.resx +++ b/src/Framework/Resources/SR.resx @@ -165,4 +165,8 @@ The path "{0}" used for debug logs is too long. Set it to a shorter value using the MSBUILDDEBUGPATH environment variable or change your system configuration to allow long paths. + + 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. + \ No newline at end of file diff --git a/src/Framework/Resources/xlf/SR.cs.xlf b/src/Framework/Resources/xlf/SR.cs.xlf index 642b5e45589..f95b773869e 100644 --- a/src/Framework/Resources/xlf/SR.cs.xlf +++ b/src/Framework/Resources/xlf/SR.cs.xlf @@ -67,6 +67,11 @@ 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} + + 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. + The parameter '{0}' can only be a file name and cannot include a directory. Parametr {0} může být jenom název souboru a nemůže obsahovat adresář. diff --git a/src/Framework/Resources/xlf/SR.de.xlf b/src/Framework/Resources/xlf/SR.de.xlf index e5b97f7dae4..374b5365d0d 100644 --- a/src/Framework/Resources/xlf/SR.de.xlf +++ b/src/Framework/Resources/xlf/SR.de.xlf @@ -67,6 +67,11 @@ Fehler beim Erstellen einer temporären Datei. Der Ordner für temporäre Dateien ist voll, oder der Pfad ist falsch. {0} + + 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. + The parameter '{0}' can only be a file name and cannot include a directory. Der Parameter '{0}' kann nur ein Dateiname sein und darf kein Verzeichnis enthalten. diff --git a/src/Framework/Resources/xlf/SR.es.xlf b/src/Framework/Resources/xlf/SR.es.xlf index fcf6502dd92..233a3d7b9f7 100644 --- a/src/Framework/Resources/xlf/SR.es.xlf +++ b/src/Framework/Resources/xlf/SR.es.xlf @@ -67,6 +67,11 @@ Error al crear un archivo temporal. La carpeta de archivos temporales está llena o la ruta de acceso no es correcta. {0} + + 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. + The parameter '{0}' can only be a file name and cannot include a directory. El parámetro "{0}" solo puede ser el nombre de un archivo y no puede incluir un directorio. diff --git a/src/Framework/Resources/xlf/SR.fr.xlf b/src/Framework/Resources/xlf/SR.fr.xlf index ba17be70e8b..0186d55e61b 100644 --- a/src/Framework/Resources/xlf/SR.fr.xlf +++ b/src/Framework/Resources/xlf/SR.fr.xlf @@ -67,6 +67,11 @@ Échec de la création d'un fichier temporaire. Le dossier de fichiers temporaires est plein ou son chemin est incorrect. {0} + + 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. + The parameter '{0}' can only be a file name and cannot include a directory. Le paramètre "{0}" peut uniquement être un nom de fichier et ne peut pas inclure de répertoire. diff --git a/src/Framework/Resources/xlf/SR.it.xlf b/src/Framework/Resources/xlf/SR.it.xlf index ceac3e20e76..53ab9950b6b 100644 --- a/src/Framework/Resources/xlf/SR.it.xlf +++ b/src/Framework/Resources/xlf/SR.it.xlf @@ -67,6 +67,11 @@ non è stato possibile creare un file temporaneo. La cartella dei file temporanei è piena oppure il percorso è errato. {0} + + 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. + The parameter '{0}' can only be a file name and cannot include a directory. Il parametro '{0}' può solo essere un nome file e non può includere una directory. diff --git a/src/Framework/Resources/xlf/SR.ja.xlf b/src/Framework/Resources/xlf/SR.ja.xlf index b2f79051a7e..f2e3a0ed1e8 100644 --- a/src/Framework/Resources/xlf/SR.ja.xlf +++ b/src/Framework/Resources/xlf/SR.ja.xlf @@ -67,6 +67,11 @@ 一時ファイルを作成できませんでした。一時ファイル フォルダーがいっぱいであるか、またはそのパスが正しくありません。{0} + + 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. + The parameter '{0}' can only be a file name and cannot include a directory. パラメーター '{0}' に使用できるのはファイル名のみで、ディレクトリを含めることはできません。 diff --git a/src/Framework/Resources/xlf/SR.ko.xlf b/src/Framework/Resources/xlf/SR.ko.xlf index 187f2e6b5e9..9e415e4b9a6 100644 --- a/src/Framework/Resources/xlf/SR.ko.xlf +++ b/src/Framework/Resources/xlf/SR.ko.xlf @@ -67,6 +67,11 @@ 임시 파일을 만들지 못했습니다. 임시 파일 폴더가 꽉 찼거나 경로가 올바르지 않습니다. {0} + + 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. + The parameter '{0}' can only be a file name and cannot include a directory. '{0}' 매개 변수는 파일 이름일 수만 있으며 디렉터리를 포함할 수 없습니다. diff --git a/src/Framework/Resources/xlf/SR.pl.xlf b/src/Framework/Resources/xlf/SR.pl.xlf index af3f15a6d5b..2be8a5e7500 100644 --- a/src/Framework/Resources/xlf/SR.pl.xlf +++ b/src/Framework/Resources/xlf/SR.pl.xlf @@ -67,6 +67,11 @@ Nie można utworzyć pliku tymczasowego. Folder plików tymczasowych jest zapełniony lub jego ścieżka jest niepoprawna. {0} + + 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. + The parameter '{0}' can only be a file name and cannot include a directory. Parametr „{0}” może zawierać tylko nazwę pliku i nie może zawierać katalogu. diff --git a/src/Framework/Resources/xlf/SR.pt-BR.xlf b/src/Framework/Resources/xlf/SR.pt-BR.xlf index 0e379fdab13..a7bb6bbb606 100644 --- a/src/Framework/Resources/xlf/SR.pt-BR.xlf +++ b/src/Framework/Resources/xlf/SR.pt-BR.xlf @@ -67,6 +67,11 @@ Falha ao criar arquivo temporário. A pasta de arquivos temporários está cheia ou o caminho está incorreto. {0} + + 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. + The parameter '{0}' can only be a file name and cannot include a directory. O parâmetro '{0}' pode ser somente um nome de arquivo e não pode incluir um diretório. diff --git a/src/Framework/Resources/xlf/SR.ru.xlf b/src/Framework/Resources/xlf/SR.ru.xlf index 20ece505407..85c60ccb1c1 100644 --- a/src/Framework/Resources/xlf/SR.ru.xlf +++ b/src/Framework/Resources/xlf/SR.ru.xlf @@ -67,6 +67,11 @@ не удалось создать временный файл. Папка временных файлов переполнена, или указан неверный путь. {0} + + 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. + The parameter '{0}' can only be a file name and cannot include a directory. Параметр "{0}" может быть только именем файла и не может включать в себя каталог. diff --git a/src/Framework/Resources/xlf/SR.tr.xlf b/src/Framework/Resources/xlf/SR.tr.xlf index c5326267a21..4f9d8c4d5e5 100644 --- a/src/Framework/Resources/xlf/SR.tr.xlf +++ b/src/Framework/Resources/xlf/SR.tr.xlf @@ -67,6 +67,11 @@ Geçici bir dosya oluşturulamadı. Geçici dosyalar klasörü dolu veya yolu hatalı. {0} + + 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. + The parameter '{0}' can only be a file name and cannot include a directory. '{0}' yalnızca bir dosya adı olabilir ve dizin içeremez. diff --git a/src/Framework/Resources/xlf/SR.zh-Hans.xlf b/src/Framework/Resources/xlf/SR.zh-Hans.xlf index 243328fdfbd..95d321cba9c 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hans.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hans.xlf @@ -67,6 +67,11 @@ 未能创建临时文件。临时文件文件夹已满或其路径不正确。{0} + + 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. + The parameter '{0}' can only be a file name and cannot include a directory. 参数“{0}”只能是文件名,不能包含目录。 diff --git a/src/Framework/Resources/xlf/SR.zh-Hant.xlf b/src/Framework/Resources/xlf/SR.zh-Hant.xlf index cee30682063..1c5eae44bec 100644 --- a/src/Framework/Resources/xlf/SR.zh-Hant.xlf +++ b/src/Framework/Resources/xlf/SR.zh-Hant.xlf @@ -67,6 +67,11 @@ 無法建立暫存檔案。暫存檔案資料夾已滿或其路徑錯誤。{0} + + 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. + The parameter '{0}' can only be a file name and cannot include a directory. 參數 '{0}' 只可以是檔案名稱,不得包含目錄。 diff --git a/src/Shared/Modifiers.cs b/src/Shared/Modifiers.cs index 968015dc321..e44356ed365 100644 --- a/src/Shared/Modifiers.cs +++ b/src/Shared/Modifiers.cs @@ -1,12 +1,7 @@ // 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.Frozen; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using Microsoft.Build.Framework; -using Microsoft.Build.Shared.FileSystem; +using FrameworkItemSpecModifiers = Microsoft.Build.Framework.ItemSpecModifiers; #nullable disable @@ -25,431 +20,45 @@ internal static partial class FileUtilities /// 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 FrozenSet s_tableOfItemSpecModifiers = FrozenSet.Create(StringComparer.OrdinalIgnoreCase, All); - private static readonly FrozenSet s_tableOfDefiningProjectModifiers = FrozenSet.Create(StringComparer.OrdinalIgnoreCase, - [ - DefiningProjectFullPath, - DefiningProjectDirectory, - DefiningProjectName, - DefiningProjectExtension, - ]); - - /// - /// Indicates if the given name is reserved for an item-spec modifier. - /// + internal static string FullPath => FrameworkItemSpecModifiers.FullPath; + internal static string RootDir => FrameworkItemSpecModifiers.RootDir; + internal static string Filename => FrameworkItemSpecModifiers.Filename; + internal static string Extension => FrameworkItemSpecModifiers.Extension; + internal static string RelativeDir => FrameworkItemSpecModifiers.RelativeDir; + internal static string Directory => FrameworkItemSpecModifiers.Directory; + internal static string RecursiveDir => FrameworkItemSpecModifiers.RecursiveDir; + internal static string Identity => FrameworkItemSpecModifiers.Identity; + internal static string ModifiedTime => FrameworkItemSpecModifiers.ModifiedTime; + internal static string CreatedTime => FrameworkItemSpecModifiers.CreatedTime; + internal static string AccessedTime => FrameworkItemSpecModifiers.AccessedTime; + internal static string DefiningProjectFullPath => FrameworkItemSpecModifiers.DefiningProjectFullPath; + internal static string DefiningProjectDirectory => FrameworkItemSpecModifiers.DefiningProjectDirectory; + internal static string DefiningProjectName => FrameworkItemSpecModifiers.DefiningProjectName; + internal static string DefiningProjectExtension => FrameworkItemSpecModifiers.DefiningProjectExtension; + + /// + internal static string[] All + => FrameworkItemSpecModifiers.All; + + /// 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; - } + => FrameworkItemSpecModifiers.IsItemSpecModifier(name); - /// - /// 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); + /// + internal static bool IsDefiningProjectModifier(string name) + => FrameworkItemSpecModifiers.IsDefiningProjectModifier(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); + => FrameworkItemSpecModifiers.IsDerivableItemSpecModifier(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); - } + => FrameworkItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, definingProjectEscaped, modifier); - /// - /// 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 = FrameworkFileUtilities.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)); - } - } + => FrameworkItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, definingProjectEscaped, modifier, ref fullPath); } } } From ae08ede8903342e4655cc18fcbd8e1fa705121fa Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 10 Mar 2026 10:55:02 -0700 Subject: [PATCH 15/27] Move FileUtilities.ExecutingAssemblyPath to BuildEnvironmentHelper - Move FileUtilities.ExecutingAssemblyPath property to BuildEnvironemtnHelper. This property depends on the code being compiled into the binary that calls it. So, moving this call to another file allows src/Shared/FileUtilities.cs to be deleted. --- .../BackEnd/SdkResolverLoader_Tests.cs | 2 +- src/Build.UnitTests/BuildEnvironmentHelper_Tests.cs | 2 +- src/Build/Definition/ProjectCollection.cs | 2 +- src/MSBuild/CommandLine/CommandLineParser.cs | 4 ++-- src/MSBuild/XMake.cs | 2 +- src/Shared/BuildEnvironmentHelper.cs | 12 +++++++++++- src/Shared/Debugging/PrintLineDebuggerWriters.cs | 2 +- src/Shared/FileUtilities.cs | 7 ------- src/Shared/UnitTests/FileUtilities_Tests.cs | 2 +- 9 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/SdkResolverLoader_Tests.cs b/src/Build.UnitTests/BackEnd/SdkResolverLoader_Tests.cs index acd78200020..18c88d259dc 100644 --- a/src/Build.UnitTests/BackEnd/SdkResolverLoader_Tests.cs +++ b/src/Build.UnitTests/BackEnd/SdkResolverLoader_Tests.cs @@ -426,7 +426,7 @@ public void LoadResolverAssembly_MSBuildSdkResolver_WithAndWithoutFallback(bool if (string.IsNullOrEmpty(msBuildExePath)) { // Use the executing assembly path as fallback - msBuildExePath = FileUtilities.ExecutingAssemblyPath; + msBuildExePath = BuildEnvironmentHelper.ExecutingAssemblyPath; // If that's also null/empty, use test assembly location if (string.IsNullOrEmpty(msBuildExePath)) { diff --git a/src/Build.UnitTests/BuildEnvironmentHelper_Tests.cs b/src/Build.UnitTests/BuildEnvironmentHelper_Tests.cs index 95572078dff..d52d5f72651 100644 --- a/src/Build.UnitTests/BuildEnvironmentHelper_Tests.cs +++ b/src/Build.UnitTests/BuildEnvironmentHelper_Tests.cs @@ -18,7 +18,7 @@ public class BuildEnvironmentHelper_Tests [Fact] public void GetExecutablePath() { - var msbuildPath = Path.GetDirectoryName(FileUtilities.ExecutingAssemblyPath); + var msbuildPath = Path.GetDirectoryName(BuildEnvironmentHelper.ExecutingAssemblyPath); string expectedMSBuildPath = Path.Combine(msbuildPath, Constants.MSBuildExecutableName).ToLowerInvariant(); string configFilePath = BuildEnvironmentHelper.Instance.CurrentMSBuildConfigurationFile.ToLowerInvariant(); diff --git a/src/Build/Definition/ProjectCollection.cs b/src/Build/Definition/ProjectCollection.cs index e68f2e3316e..e64a57a7627 100644 --- a/src/Build/Definition/ProjectCollection.cs +++ b/src/Build/Definition/ProjectCollection.cs @@ -492,7 +492,7 @@ public static Version Version // Use .CodeBase instead of .Location, because .Location doesn't // work when Microsoft.Build.dll has been shadow-copied, for example // in scenarios where NUnit is loading Microsoft.Build. - var versionInfo = FileVersionInfo.GetVersionInfo(FileUtilities.ExecutingAssemblyPath); + var versionInfo = FileVersionInfo.GetVersionInfo(BuildEnvironmentHelper.ExecutingAssemblyPath); s_engineVersion = new Version(versionInfo.FileMajorPart, versionInfo.FileMinorPart, versionInfo.FileBuildPart, versionInfo.FilePrivatePart); } diff --git a/src/MSBuild/CommandLine/CommandLineParser.cs b/src/MSBuild/CommandLine/CommandLineParser.cs index fc94bcb0b34..f01bdd6e8cf 100644 --- a/src/MSBuild/CommandLine/CommandLineParser.cs +++ b/src/MSBuild/CommandLine/CommandLineParser.cs @@ -117,7 +117,7 @@ internal void GatherAllSwitches( switchesFromAutoResponseFile = new CommandLineSwitches(); if (!switchesNotFromAutoResponseFile[ParameterlessSwitch.NoAutoResponse]) { - string exePath = Path.GetDirectoryName(FileUtilities.ExecutingAssemblyPath); // Copied from XMake + string exePath = Path.GetDirectoryName(BuildEnvironmentHelper.ExecutingAssemblyPath); // Copied from XMake GatherAutoResponseFileSwitches(exePath, switchesFromAutoResponseFile, fullCommandLine); } @@ -532,7 +532,7 @@ internal bool CheckAndGatherProjectAutoResponseFile(CommandLineSwitches switches found = !string.IsNullOrWhiteSpace(directoryResponseFile) && GatherAutoResponseFileSwitchesFromFullPath(directoryResponseFile, switchesFromAutoResponseFile, commandLine); // Don't look for more response files if it's only in the same place we already looked (next to the exe) - string exePath = Path.GetDirectoryName(FileUtilities.ExecutingAssemblyPath); // Copied from XMake + string exePath = Path.GetDirectoryName(BuildEnvironmentHelper.ExecutingAssemblyPath); // Copied from XMake if (!string.Equals(projectDirectory, exePath, StringComparison.OrdinalIgnoreCase)) { // this combines any found, with higher precedence, with the switches from the original auto response file switches diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index 089e93d533a..6f81ddceb72 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -160,7 +160,7 @@ static MSBuildApp() // This forces the type to initialize in this static constructor and thus // // any configuration file exceptions can be caught here. // //////////////////////////////////////////////////////////////////////////////// - s_exePath = Path.GetDirectoryName(FileUtilities.ExecutingAssemblyPath); + s_exePath = Path.GetDirectoryName(BuildEnvironmentHelper.ExecutingAssemblyPath); commandLineParser = new CommandLineParser(); s_initialized = true; diff --git a/src/Shared/BuildEnvironmentHelper.cs b/src/Shared/BuildEnvironmentHelper.cs index c1e6259bac9..d6535d808a4 100644 --- a/src/Shared/BuildEnvironmentHelper.cs +++ b/src/Shared/BuildEnvironmentHelper.cs @@ -33,6 +33,16 @@ internal sealed class BuildEnvironmentHelper /// private static readonly string[] s_msBuildProcess = { "MSBUILD", "MSBUILDTASKHOST" }; + /// + /// Get the currently executing assembly path. + /// + /// + /// This property depends on BuildEnvironmentHelper being compiled into separate assemblies. + /// If BuildEnvironmentHelper is moved to a shared assembly, this property will need to be re-evaluated. + /// + internal static string ExecutingAssemblyPath + => Path.GetFullPath(AssemblyUtilities.GetAssemblyLocation(typeof(BuildEnvironmentHelper).Assembly)); + /// /// Gets the cached Build Environment instance. /// @@ -443,7 +453,7 @@ private static string GetProcessFromRunningProcess() private static string GetExecutingAssemblyPath() { - return FileUtilities.ExecutingAssemblyPath; + return ExecutingAssemblyPath; } private static string GetAppContextBaseDirectory() diff --git a/src/Shared/Debugging/PrintLineDebuggerWriters.cs b/src/Shared/Debugging/PrintLineDebuggerWriters.cs index bcdadc22a24..5f97665568f 100644 --- a/src/Shared/Debugging/PrintLineDebuggerWriters.cs +++ b/src/Shared/Debugging/PrintLineDebuggerWriters.cs @@ -71,7 +71,7 @@ public CompositeWriter(IEnumerable writers) private static readonly Lazy _artifactsLogs = new Lazy( () => { - var executingAssembly = FileUtilities.ExecutingAssemblyPath; + var executingAssembly = BuildEnvironmentHelper.ExecutingAssemblyPath; var binPart = $"bin"; diff --git a/src/Shared/FileUtilities.cs b/src/Shared/FileUtilities.cs index 586f29e35a9..4c3be19ce69 100644 --- a/src/Shared/FileUtilities.cs +++ b/src/Shared/FileUtilities.cs @@ -7,7 +7,6 @@ #endif using System.Collections.Generic; using System.IO; -using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using Microsoft.Build.Framework; @@ -173,12 +172,6 @@ internal static bool HasExtension(string fileName, string[] allowedExtensions) internal static string FileTimeFormat => FrameworkFileUtilities.FileTimeFormat; - /// - /// Get the currently executing assembly path. - /// - internal static string ExecutingAssemblyPath - => Path.GetFullPath(AssemblyUtilities.GetAssemblyLocation(typeof(FileUtilities).GetTypeInfo().Assembly)); - /// internal static string GetFullPath(string fileSpec, string currentDirectory, bool escape = true) => FrameworkFileUtilities.GetFullPath(fileSpec, currentDirectory, escape); diff --git a/src/Shared/UnitTests/FileUtilities_Tests.cs b/src/Shared/UnitTests/FileUtilities_Tests.cs index b7e677e2e4a..377aaf35b65 100644 --- a/src/Shared/UnitTests/FileUtilities_Tests.cs +++ b/src/Shared/UnitTests/FileUtilities_Tests.cs @@ -937,7 +937,7 @@ public void RelativePathMaybeAdjustFilePathWithBaseDirectory() } } - private static string SystemSpecificAbsolutePath => FileUtilities.ExecutingAssemblyPath; + private static string SystemSpecificAbsolutePath => BuildEnvironmentHelper.ExecutingAssemblyPath; [Fact] From 5e51cbbb3b83cbdb209d54e258da14bba669103b Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 10 Mar 2026 11:20:31 -0700 Subject: [PATCH 16/27] Update ItemSpecModifiers usages and remove Modifiers.cs - Update all usages of Microsoft.Build.Shared.FileUtilities.ItemSpecModifiers to Microsoft.Build.Framework.ItemSpecModifiers. - Remove src/Shared/Modifiers.cs. --- .../Instance/TaskItem_Tests.cs | 2 +- .../TestComparers/TaskItemComparer.cs | 8 +- .../RequestBuilder/IntrinsicTasks/MSBuild.cs | 2 +- .../Construction/ProjectMetadataElement.cs | 3 +- src/Build/Definition/BuiltInMetadata.cs | 11 +- src/Build/Definition/ProjectItem.cs | 10 +- src/Build/Definition/ProjectItemDefinition.cs | 3 +- src/Build/Evaluation/Conditionals/Scanner.cs | 3 +- src/Build/Evaluation/Expander.cs | 16 +- src/Build/Evaluation/ProjectParser.cs | 4 +- src/Build/Instance/ProjectItemInstance.cs | 16 +- src/Build/Instance/ProjectMetadataInstance.cs | 3 +- src/Build/Microsoft.Build.csproj | 1 - src/MSBuild/JsonOutputFormatter.cs | 2 +- src/MSBuild/MSBuild.csproj | 1 - src/Shared/Modifiers.cs | 64 ------- src/Shared/TaskParameter.cs | 16 +- src/Shared/UnitTests/FileUtilities_Tests.cs | 158 +++++++++--------- .../AssemblyDependency/ReferenceTable.cs | 2 +- src/Tasks/AssignLinkMetadata.cs | 7 +- src/Tasks/CreateItem.cs | 4 +- src/Tasks/MSBuild.cs | 2 +- src/Tasks/Microsoft.Build.Tasks.csproj | 3 - src/Utilities.UnitTests/TaskItem_Tests.cs | 60 +++---- .../Microsoft.Build.Utilities.csproj | 3 - src/Utilities/TaskItem.cs | 20 +-- 26 files changed, 178 insertions(+), 246 deletions(-) delete mode 100644 src/Shared/Modifiers.cs diff --git a/src/Build.UnitTests/Instance/TaskItem_Tests.cs b/src/Build.UnitTests/Instance/TaskItem_Tests.cs index 51914da66d5..7e6fd0f64f4 100644 --- a/src/Build.UnitTests/Instance/TaskItem_Tests.cs +++ b/src/Build.UnitTests/Instance/TaskItem_Tests.cs @@ -60,7 +60,7 @@ public void Serialization() Assert.Equal(item.ItemSpec, deserializedItem.ItemSpec); Assert.Equal(item.MetadataCount, deserializedItem.MetadataCount); Assert.Equal(item.GetMetadata("a"), deserializedItem.GetMetadata("a")); - Assert.Equal(item.GetMetadata(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath), deserializedItem.GetMetadata(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath)); + Assert.Equal(item.GetMetadata(ItemSpecModifiers.DefiningProjectFullPath), deserializedItem.GetMetadata(ItemSpecModifiers.DefiningProjectFullPath)); } /// diff --git a/src/Build.UnitTests/TestComparers/TaskItemComparer.cs b/src/Build.UnitTests/TestComparers/TaskItemComparer.cs index b5e5d766b5b..191807d291d 100644 --- a/src/Build.UnitTests/TestComparers/TaskItemComparer.cs +++ b/src/Build.UnitTests/TestComparers/TaskItemComparer.cs @@ -50,8 +50,8 @@ public int Compare(ITaskItem x, ITaskItem y) foreach (string metadataName in x.MetadataNames) { - if (!FileUtilities.ItemSpecModifiers.IsItemSpecModifier(metadataName) || - FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(metadataName)) + if (!ItemSpecModifiers.IsItemSpecModifier(metadataName) || + ItemSpecModifiers.IsDerivableItemSpecModifier(metadataName)) { if (x.GetMetadata(metadataName) != y.GetMetadata(metadataName)) { @@ -62,8 +62,8 @@ public int Compare(ITaskItem x, ITaskItem y) foreach (string metadataName in y.MetadataNames) { - if (!FileUtilities.ItemSpecModifiers.IsItemSpecModifier(metadataName) || - FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(metadataName)) + if (!ItemSpecModifiers.IsItemSpecModifier(metadataName) || + ItemSpecModifiers.IsDerivableItemSpecModifier(metadataName)) { if (x.GetMetadata(metadataName) != y.GetMetadata(metadataName)) { diff --git a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs index 59193af2a5e..db7e94103fe 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs @@ -711,7 +711,7 @@ internal static async Task ExecuteTargets( // Set a metadata on the output items called "MSBuildProjectFile" which tells you which project file produced this item. if (String.IsNullOrEmpty(outputItemFromTarget.GetMetadata(ItemMetadataNames.msbuildSourceProjectFile))) { - outputItemFromTarget.SetMetadata(ItemMetadataNames.msbuildSourceProjectFile, projects[i].GetMetadata(FileUtilities.ItemSpecModifiers.FullPath)); + outputItemFromTarget.SetMetadata(ItemMetadataNames.msbuildSourceProjectFile, projects[i].GetMetadata(ItemSpecModifiers.FullPath)); } } diff --git a/src/Build/Construction/ProjectMetadataElement.cs b/src/Build/Construction/ProjectMetadataElement.cs index 156755286d1..60592634cfe 100644 --- a/src/Build/Construction/ProjectMetadataElement.cs +++ b/src/Build/Construction/ProjectMetadataElement.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using Microsoft.Build.Framework; using Microsoft.Build.ObjectModelRemoting; using Microsoft.Build.Shared; @@ -103,7 +104,7 @@ public string Value internal static ProjectMetadataElement CreateDisconnected(string name, ProjectRootElement containingProject, ElementLocation location = null) { XmlUtilities.VerifyThrowArgumentValidElementName(name); - ErrorUtilities.VerifyThrowArgument(!FileUtilities.ItemSpecModifiers.IsItemSpecModifier(name), "ItemSpecModifierCannotBeCustomMetadata", name); + ErrorUtilities.VerifyThrowArgument(!ItemSpecModifiers.IsItemSpecModifier(name), "ItemSpecModifierCannotBeCustomMetadata", name); ErrorUtilities.VerifyThrowInvalidOperation(!XMakeElements.ReservedItemNames.Contains(name), "CannotModifyReservedItemMetadata", name); XmlElementWithLocation element = containingProject.CreateElement(name, location); diff --git a/src/Build/Definition/BuiltInMetadata.cs b/src/Build/Definition/BuiltInMetadata.cs index 1e28ec1c379..b93845b4b3b 100644 --- a/src/Build/Definition/BuiltInMetadata.cs +++ b/src/Build/Definition/BuiltInMetadata.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; namespace Microsoft.Build.Evaluation @@ -22,7 +23,7 @@ internal static int MetadataCount { [DebuggerStepThrough] get - { return FileUtilities.ItemSpecModifiers.All.Length; } + { return ItemSpecModifiers.All.Length; } } /// @@ -32,7 +33,7 @@ internal static ICollection MetadataNames { [DebuggerStepThrough] get - { return FileUtilities.ItemSpecModifiers.All; } + { return ItemSpecModifiers.All; } } /// @@ -71,16 +72,16 @@ internal static string GetMetadataValue(string currentDirectory, string evaluate internal static string GetMetadataValueEscaped(string currentDirectory, string evaluatedIncludeBeforeWildcardExpansionEscaped, string evaluatedIncludeEscaped, string definingProjectEscaped, string name, ref string fullPath) { // This is an assert, not a VerifyThrow, because the caller should already have done this check, and it's slow/hot. - Debug.Assert(FileUtilities.ItemSpecModifiers.IsItemSpecModifier(name)); + Debug.Assert(ItemSpecModifiers.IsItemSpecModifier(name)); string value; - if (String.Equals(name, FileUtilities.ItemSpecModifiers.RecursiveDir, StringComparison.OrdinalIgnoreCase)) + if (String.Equals(name, ItemSpecModifiers.RecursiveDir, StringComparison.OrdinalIgnoreCase)) { value = GetRecursiveDirValue(evaluatedIncludeBeforeWildcardExpansionEscaped, evaluatedIncludeEscaped); } else { - value = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(currentDirectory, evaluatedIncludeEscaped, definingProjectEscaped, name, ref fullPath); + value = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, evaluatedIncludeEscaped, definingProjectEscaped, name, ref fullPath); } return value; diff --git a/src/Build/Definition/ProjectItem.cs b/src/Build/Definition/ProjectItem.cs index 96aae94ac4f..19c954401c2 100644 --- a/src/Build/Definition/ProjectItem.cs +++ b/src/Build/Definition/ProjectItem.cs @@ -303,7 +303,7 @@ public int MetadataCount { [DebuggerStepThrough] get - { return Metadata.Count + FileUtilities.ItemSpecModifiers.All.Length; } + { return Metadata.Count + ItemSpecModifiers.All.Length; } } /// @@ -458,7 +458,7 @@ public bool HasMetadata(string name) return true; } - if (FileUtilities.ItemSpecModifiers.IsItemSpecModifier(name)) + if (ItemSpecModifiers.IsItemSpecModifier(name)) { return true; } @@ -583,7 +583,7 @@ private ProjectMetadata SetMetadataOperation(string name, string unevaluatedValu Project.VerifyThrowInvalidOperationNotImported(_xml.ContainingProject); XmlUtilities.VerifyThrowArgumentValidElementName(name); - ErrorUtilities.VerifyThrowArgument(!FileUtilities.ItemSpecModifiers.IsItemSpecModifier(name), "ItemSpecModifierCannotBeCustomMetadata", name); + ErrorUtilities.VerifyThrowArgument(!ItemSpecModifiers.IsItemSpecModifier(name), "ItemSpecModifierCannotBeCustomMetadata", name); ErrorUtilities.VerifyThrowInvalidOperation(!XMakeElements.ReservedItemNames.Contains(name), "CannotModifyReservedItemMetadata", name); ErrorUtilities.VerifyThrowInvalidOperation(_xml.Parent?.Parent != null, "OM_ObjectIsNoLongerActive"); @@ -641,7 +641,7 @@ public bool RemoveMetadata(string name) } ErrorUtilities.VerifyThrowArgumentLength(name); - ErrorUtilities.VerifyThrowArgument(!FileUtilities.ItemSpecModifiers.IsItemSpecModifier(name), "ItemSpecModifierCannotBeCustomMetadata", name); + ErrorUtilities.VerifyThrowArgument(!ItemSpecModifiers.IsItemSpecModifier(name), "ItemSpecModifierCannotBeCustomMetadata", name); Project.VerifyThrowInvalidOperationNotImported(_xml.ContainingProject); ErrorUtilities.VerifyThrowInvalidOperation(_xml.Parent?.Parent != null, "OM_ObjectIsNoLongerActive"); @@ -857,7 +857,7 @@ private string GetBuiltInMetadataEscaped(string name) { string value = null; - if (FileUtilities.ItemSpecModifiers.IsItemSpecModifier(name)) + if (ItemSpecModifiers.IsItemSpecModifier(name)) { value = BuiltInMetadata.GetMetadataValueEscaped(_project.DirectoryPath, _evaluatedIncludeBeforeWildcardExpansionEscaped, _evaluatedIncludeEscaped, this.Xml.ContainingProject.FullPath, name, ref _fullPath); } diff --git a/src/Build/Definition/ProjectItemDefinition.cs b/src/Build/Definition/ProjectItemDefinition.cs index 8fa69153f7e..9524297487c 100644 --- a/src/Build/Definition/ProjectItemDefinition.cs +++ b/src/Build/Definition/ProjectItemDefinition.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.Build.Collections; using Microsoft.Build.Construction; +using Microsoft.Build.Framework; using Microsoft.Build.ObjectModelRemoting; using Microsoft.Build.Shared; @@ -150,7 +151,7 @@ public ProjectMetadata SetMetadataValue(string name, string unevaluatedValue) } XmlUtilities.VerifyThrowArgumentValidElementName(name); - ErrorUtilities.VerifyThrowArgument(!FileUtilities.ItemSpecModifiers.IsItemSpecModifier(name), "ItemSpecModifierCannotBeCustomMetadata", name); + ErrorUtilities.VerifyThrowArgument(!ItemSpecModifiers.IsItemSpecModifier(name), "ItemSpecModifierCannotBeCustomMetadata", name); ErrorUtilities.VerifyThrowInvalidOperation(!XMakeElements.ReservedItemNames.Contains(name), "CannotModifyReservedItemMetadata", name); ProjectMetadata metadatum; diff --git a/src/Build/Evaluation/Conditionals/Scanner.cs b/src/Build/Evaluation/Conditionals/Scanner.cs index 718ae73d23d..f231c5a4676 100644 --- a/src/Build/Evaluation/Conditionals/Scanner.cs +++ b/src/Build/Evaluation/Conditionals/Scanner.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Globalization; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable @@ -474,7 +475,7 @@ private bool CheckForUnexpectedMetadata(string expression) expression = expression.Substring(period + 1); } - bool isItemSpecModifier = FileUtilities.ItemSpecModifiers.IsItemSpecModifier(expression); + bool isItemSpecModifier = ItemSpecModifiers.IsItemSpecModifier(expression); if (((_options & ParserOptions.AllowBuiltInMetadata) == 0) && isItemSpecModifier) diff --git a/src/Build/Evaluation/Expander.cs b/src/Build/Evaluation/Expander.cs index 98a2a8aeed5..1417a02d2ae 100644 --- a/src/Build/Evaluation/Expander.cs +++ b/src/Build/Evaluation/Expander.cs @@ -28,7 +28,7 @@ using Microsoft.NET.StringTools; using Microsoft.Win32; using AvailableStaticMethods = Microsoft.Build.Internal.AvailableStaticMethods; -using ItemSpecModifiers = Microsoft.Build.Shared.FileUtilities.ItemSpecModifiers; +using ItemSpecModifiers = Microsoft.Build.Framework.ItemSpecModifiers; using ParseArgs = Microsoft.Build.Evaluation.Expander.ArgumentParser; using ReservedPropertyNames = Microsoft.Build.Internal.ReservedPropertyNames; using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem; @@ -1159,7 +1159,7 @@ internal static string ExpandSingleMetadata(Match itemMetadataMatch, MetadataMat string metadataValue = null; - bool isBuiltInMetadata = FileUtilities.ItemSpecModifiers.IsItemSpecModifier(metadataName); + bool isBuiltInMetadata = ItemSpecModifiers.IsItemSpecModifier(metadataName); if ( (isBuiltInMetadata && ((evaluator._options & ExpanderOptions.ExpandBuiltInMetadata) != 0)) || @@ -1954,7 +1954,7 @@ internal static List> Transform( ItemTransformFunctions functionType; - if (FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(functionName)) + if (ItemSpecModifiers.IsDerivableItemSpecModifier(functionName)) { functionType = ItemTransformFunctions.ItemSpecModifierFunction; } @@ -2553,9 +2553,9 @@ internal static void ItemSpecModifierFunction(IElementLocation elementLocation, // only exist within a target where we can trust the current directory // 2. in single process mode we get the project directory set for the thread string directoryToUse = item.Value.ProjectDirectory ?? FrameworkFileUtilities.CurrentThreadWorkingDirectory ?? Directory.GetCurrentDirectory(); - string definingProjectEscaped = item.Value.GetMetadataValueEscaped(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath); + string definingProjectEscaped = item.Value.GetMetadataValueEscaped(ItemSpecModifiers.DefiningProjectFullPath); - result = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(directoryToUse, item.Key, definingProjectEscaped, functionName); + result = ItemSpecModifiers.GetItemSpecModifier(directoryToUse, item.Key, definingProjectEscaped, functionName); } // InvalidOperationException is how GetItemSpecModifier communicates invalid conditions upwards, so // we do not want to rethrow in that case. @@ -3313,7 +3313,7 @@ private static string GetMetadataValueFromMatch( string value = null; try { - if (FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(match.Name)) + if (ItemSpecModifiers.IsDerivableItemSpecModifier(match.Name)) { // If we're not a ProjectItem or ProjectItemInstance, then ProjectDirectory will be null. // In that case, @@ -3321,9 +3321,9 @@ private static string GetMetadataValueFromMatch( // only exist within a target where we can trust the current directory // 2. in single process mode we get the project directory set for the thread string directoryToUse = sourceOfMetadata.ProjectDirectory ?? FrameworkFileUtilities.CurrentThreadWorkingDirectory ?? Directory.GetCurrentDirectory(); - string definingProjectEscaped = sourceOfMetadata.GetMetadataValueEscaped(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath); + string definingProjectEscaped = sourceOfMetadata.GetMetadataValueEscaped(ItemSpecModifiers.DefiningProjectFullPath); - value = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(directoryToUse, itemSpec, definingProjectEscaped, match.Name); + value = ItemSpecModifiers.GetItemSpecModifier(directoryToUse, itemSpec, definingProjectEscaped, match.Name); } else { diff --git a/src/Build/Evaluation/ProjectParser.cs b/src/Build/Evaluation/ProjectParser.cs index a71a572677d..a186fba1726 100644 --- a/src/Build/Evaluation/ProjectParser.cs +++ b/src/Build/Evaluation/ProjectParser.cs @@ -376,7 +376,7 @@ internal static void CheckMetadataAsAttributeName(string name, out bool isReserv return; } - if (FileUtilities.ItemSpecModifiers.IsItemSpecModifier(name) || XMakeElements.ReservedItemNames.Contains(name)) + if (ItemSpecModifiers.IsItemSpecModifier(name) || XMakeElements.ReservedItemNames.Contains(name)) { isReservedAttributeName = false; isValidMetadataNameInAttribute = false; @@ -397,7 +397,7 @@ private ProjectMetadataElement ParseProjectMetadataElement(XmlElementWithLocatio XmlUtilities.VerifyThrowProjectValidElementName(element); ProjectErrorUtilities.VerifyThrowInvalidProject(!(parent is ProjectItemElement) || ((ProjectItemElement)parent).Remove.Length == 0, element.Location, "ChildElementsBelowRemoveNotAllowed", element.Name); - ProjectErrorUtilities.VerifyThrowInvalidProject(!FileUtilities.ItemSpecModifiers.IsItemSpecModifier(element.Name), element.Location, "ItemSpecModifierCannotBeCustomMetadata", element.Name); + ProjectErrorUtilities.VerifyThrowInvalidProject(!ItemSpecModifiers.IsItemSpecModifier(element.Name), element.Location, "ItemSpecModifierCannotBeCustomMetadata", element.Name); ProjectErrorUtilities.VerifyThrowInvalidProject(!XMakeElements.ReservedItemNames.Contains(element.Name), element.Location, "CannotModifyReservedItemMetadata", element.Name); ProjectMetadataElement metadatum = new ProjectMetadataElement(element, parent, _project); diff --git a/src/Build/Instance/ProjectItemInstance.cs b/src/Build/Instance/ProjectItemInstance.cs index 352e148e892..9ad5c055209 100644 --- a/src/Build/Instance/ProjectItemInstance.cs +++ b/src/Build/Instance/ProjectItemInstance.cs @@ -974,14 +974,14 @@ public ICollection MetadataNames { ImmutableDictionary metadataCollection = MetadataCollection; - List names = new List(capacity: metadataCollection.Count + FileUtilities.ItemSpecModifiers.All.Length); + List names = new List(capacity: metadataCollection.Count + ItemSpecModifiers.All.Length); foreach (KeyValuePair metadatum in metadataCollection) { names.Add(metadatum.Key); } - names.AddRange(FileUtilities.ItemSpecModifiers.All); + names.AddRange(ItemSpecModifiers.All); return names; } @@ -1817,7 +1817,7 @@ public bool Equals(TaskItem other) public bool HasMetadata(string name) { if ((_directMetadata?.ContainsKey(name) == true) || - FileUtilities.ItemSpecModifiers.IsItemSpecModifier(name) || + ItemSpecModifiers.IsItemSpecModifier(name) || GetItemDefinitionMetadataEscaped(name) != null) { return true; @@ -2012,7 +2012,7 @@ internal void SetMetadataOnTaskOutput(string name, string evaluatedValueEscaped) { ProjectInstance.VerifyThrowNotImmutable(_isImmutable); - if (!FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(name)) + if (!ItemSpecModifiers.IsDerivableItemSpecModifier(name)) { ProjectMetadataInstance.VerifyThrowReservedNameAllowItemSpecModifiers(name); _directMetadata = DirectMetadata.SetItem(name, evaluatedValueEscaped ?? string.Empty); @@ -2025,7 +2025,7 @@ internal void SetMetadataOnTaskOutput(IEnumerable> _directMetadata ??= ImmutableDictionaryExtensions.EmptyMetadata; var metadata = items - .Where(item => !FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(item.Key)); + .Where(item => !ItemSpecModifiers.IsDerivableItemSpecModifier(item.Key)); _directMetadata = DirectMetadata.SetItems(metadata, ProjectMetadataInstance.VerifyThrowReservedNameAllowItemSpecModifiers); } @@ -2084,7 +2084,7 @@ private string GetBuiltInMetadataEscaped(string name) { string value = String.Empty; - if (FileUtilities.ItemSpecModifiers.IsItemSpecModifier(name)) + if (ItemSpecModifiers.IsItemSpecModifier(name)) { value = BuiltInMetadata.GetMetadataValueEscaped(_projectDirectory, _includeBeforeWildcardExpansionEscaped, _includeEscaped, _definingFileEscaped, name, ref _fullPath); } @@ -2185,9 +2185,9 @@ public bool MoveNext() } } - if (_itemSpecModifiersIndex < FileUtilities.ItemSpecModifiers.All.Length) + if (_itemSpecModifiersIndex < ItemSpecModifiers.All.Length) { - Current = FileUtilities.ItemSpecModifiers.All[_itemSpecModifiersIndex]; + Current = ItemSpecModifiers.All[_itemSpecModifiersIndex]; ++_itemSpecModifiersIndex; return true; diff --git a/src/Build/Instance/ProjectMetadataInstance.cs b/src/Build/Instance/ProjectMetadataInstance.cs index da068713afb..548f35ff5fd 100644 --- a/src/Build/Instance/ProjectMetadataInstance.cs +++ b/src/Build/Instance/ProjectMetadataInstance.cs @@ -6,6 +6,7 @@ using Microsoft.Build.BackEnd; using Microsoft.Build.Collections; using Microsoft.Build.Evaluation; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable @@ -240,7 +241,7 @@ internal static void VerifyThrowReservedName(string name) // PERF: This sequence of checks is faster than a full HashSet lookup since finding a match is an error case. // Otherwise, many keys would still match to a bucket and begin a string comparison. VerifyThrowReservedNameAllowItemSpecModifiers(name); - foreach (string itemSpecModifier in FileUtilities.ItemSpecModifiers.All) + foreach (string itemSpecModifier in ItemSpecModifiers.All) { if (itemSpecModifier.Length == name.Length && itemSpecModifier[0] == char.ToUpperInvariant(name[0])) { diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 534837ff01b..78b911686f9 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -671,7 +671,6 @@ SharedUtilities\FileUtilities.cs - SharedUtilities\FrameworkLocationHelper.cs diff --git a/src/MSBuild/JsonOutputFormatter.cs b/src/MSBuild/JsonOutputFormatter.cs index a3c2f0afc9d..2aabd7b7bc4 100644 --- a/src/MSBuild/JsonOutputFormatter.cs +++ b/src/MSBuild/JsonOutputFormatter.cs @@ -101,7 +101,7 @@ internal void AddItemsInJsonFormat(string[] itemNames, Project project) jsonItem[metadatum.Name] = metadatum.EvaluatedValue; } - foreach (string metadatumName in FileUtilities.ItemSpecModifiers.All) + foreach (string metadatumName in ItemSpecModifiers.All) { if (metadatumName.Equals("Identity")) { diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj index 1abb07244c7..a7ed51c829a 100644 --- a/src/MSBuild/MSBuild.csproj +++ b/src/MSBuild/MSBuild.csproj @@ -78,7 +78,6 @@ - diff --git a/src/Shared/Modifiers.cs b/src/Shared/Modifiers.cs deleted file mode 100644 index e44356ed365..00000000000 --- a/src/Shared/Modifiers.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 FrameworkItemSpecModifiers = Microsoft.Build.Framework.ItemSpecModifiers; - -#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 static string FullPath => FrameworkItemSpecModifiers.FullPath; - internal static string RootDir => FrameworkItemSpecModifiers.RootDir; - internal static string Filename => FrameworkItemSpecModifiers.Filename; - internal static string Extension => FrameworkItemSpecModifiers.Extension; - internal static string RelativeDir => FrameworkItemSpecModifiers.RelativeDir; - internal static string Directory => FrameworkItemSpecModifiers.Directory; - internal static string RecursiveDir => FrameworkItemSpecModifiers.RecursiveDir; - internal static string Identity => FrameworkItemSpecModifiers.Identity; - internal static string ModifiedTime => FrameworkItemSpecModifiers.ModifiedTime; - internal static string CreatedTime => FrameworkItemSpecModifiers.CreatedTime; - internal static string AccessedTime => FrameworkItemSpecModifiers.AccessedTime; - internal static string DefiningProjectFullPath => FrameworkItemSpecModifiers.DefiningProjectFullPath; - internal static string DefiningProjectDirectory => FrameworkItemSpecModifiers.DefiningProjectDirectory; - internal static string DefiningProjectName => FrameworkItemSpecModifiers.DefiningProjectName; - internal static string DefiningProjectExtension => FrameworkItemSpecModifiers.DefiningProjectExtension; - - /// - internal static string[] All - => FrameworkItemSpecModifiers.All; - - /// - internal static bool IsItemSpecModifier(string name) - => FrameworkItemSpecModifiers.IsItemSpecModifier(name); - - /// - internal static bool IsDefiningProjectModifier(string name) - => FrameworkItemSpecModifiers.IsDefiningProjectModifier(name); - - /// - internal static bool IsDerivableItemSpecModifier(string name) - => FrameworkItemSpecModifiers.IsDerivableItemSpecModifier(name); - - /// - internal static string GetItemSpecModifier(string currentDirectory, string itemSpec, string definingProjectEscaped, string modifier) - => FrameworkItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, definingProjectEscaped, modifier); - - /// - internal static string GetItemSpecModifier(string currentDirectory, string itemSpec, string definingProjectEscaped, string modifier, ref string fullPath) - => FrameworkItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, definingProjectEscaped, modifier, ref fullPath); - } - } -} diff --git a/src/Shared/TaskParameter.cs b/src/Shared/TaskParameter.cs index 7e22fd27613..4c4849f5605 100644 --- a/src/Shared/TaskParameter.cs +++ b/src/Shared/TaskParameter.cs @@ -572,7 +572,7 @@ internal TaskParameterTaskItem(ITaskItem copyFrom) if (copyFrom is ITaskItem2 copyFromAsITaskItem2) { _escapedItemSpec = copyFromAsITaskItem2.EvaluatedIncludeEscaped; - _escapedDefiningProject = copyFromAsITaskItem2.GetMetadataValueEscaped(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath); + _escapedDefiningProject = copyFromAsITaskItem2.GetMetadataValueEscaped(ItemSpecModifiers.DefiningProjectFullPath); IDictionary nonGenericEscapedMetadata = copyFromAsITaskItem2.CloneCustomMetadataEscaped(); _customEscapedMetadata = nonGenericEscapedMetadata as Dictionary; @@ -593,7 +593,7 @@ internal TaskParameterTaskItem(ITaskItem copyFrom) // 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)); + _escapedDefiningProject = EscapingUtilities.EscapeWithCaching(copyFrom.GetMetadata(ItemSpecModifiers.DefiningProjectFullPath)); IDictionary customMetadata = copyFrom.CloneCustomMetadata(); _customEscapedMetadata = new Dictionary(MSBuildNameIgnoreCaseComparer.Default); @@ -644,7 +644,7 @@ public ICollection MetadataNames get { List metadataNames = (_customEscapedMetadata == null) ? new List() : new List(_customEscapedMetadata.Keys); - metadataNames.AddRange(FileUtilities.ItemSpecModifiers.All); + metadataNames.AddRange(ItemSpecModifiers.All); return metadataNames; } @@ -660,7 +660,7 @@ public int MetadataCount get { int count = (_customEscapedMetadata == null) ? 0 : _customEscapedMetadata.Count; - return count + FileUtilities.ItemSpecModifiers.All.Length; + return count + ItemSpecModifiers.All.Length; } } @@ -706,7 +706,7 @@ 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(!ItemSpecModifiers.IsDerivableItemSpecModifier(metadataName), "Shared.CannotChangeItemSpecModifiers", metadataName); _customEscapedMetadata ??= new Dictionary(MSBuildNameIgnoreCaseComparer.Default); @@ -720,7 +720,7 @@ 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(!ItemSpecModifiers.IsItemSpecModifier(metadataName), "Shared.CannotChangeItemSpecModifiers", metadataName); if (_customEscapedMetadata == null) { @@ -829,11 +829,11 @@ string ITaskItem2.GetMetadataValueEscaped(string metadataName) string metadataValue = null; - if (FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(metadataName)) + if (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); + metadataValue = ItemSpecModifiers.GetItemSpecModifier(null, _escapedItemSpec, _escapedDefiningProject, metadataName, ref _fullPath); } else if (_customEscapedMetadata != null) { diff --git a/src/Shared/UnitTests/FileUtilities_Tests.cs b/src/Shared/UnitTests/FileUtilities_Tests.cs index 377aaf35b65..622978c47b2 100644 --- a/src/Shared/UnitTests/FileUtilities_Tests.cs +++ b/src/Shared/UnitTests/FileUtilities_Tests.cs @@ -18,7 +18,7 @@ namespace Microsoft.Build.UnitTests public class FileUtilities_Tests { /// - /// Exercises FileUtilities.ItemSpecModifiers.GetItemSpecModifier + /// Exercises ItemSpecModifiers.GetItemSpecModifier /// [Fact] [Trait("Category", "netcore-osx-failing")] @@ -32,55 +32,55 @@ public void GetItemSpecModifier() private static void TestGetItemSpecModifier(string currentDirectory) { string cache = null; - string modifier = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(currentDirectory, "foo", String.Empty, FileUtilities.ItemSpecModifiers.RecursiveDir, ref cache); + string modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, "foo", String.Empty, ItemSpecModifiers.RecursiveDir, ref cache); Assert.Equal(String.Empty, modifier); cache = null; - modifier = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(currentDirectory, "foo", String.Empty, FileUtilities.ItemSpecModifiers.ModifiedTime, ref cache); + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, "foo", String.Empty, ItemSpecModifiers.ModifiedTime, ref cache); Assert.Equal(String.Empty, modifier); cache = null; - modifier = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(currentDirectory, @"foo\goo", String.Empty, FileUtilities.ItemSpecModifiers.RelativeDir, ref cache); + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, @"foo\goo", String.Empty, ItemSpecModifiers.RelativeDir, ref cache); Assert.Equal(@"foo" + Path.DirectorySeparatorChar, modifier); // confirm we get the same thing back the second time - modifier = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(currentDirectory, @"foo\goo", String.Empty, FileUtilities.ItemSpecModifiers.RelativeDir, ref cache); + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, @"foo\goo", String.Empty, ItemSpecModifiers.RelativeDir, ref cache); Assert.Equal(@"foo" + Path.DirectorySeparatorChar, modifier); cache = null; string itemSpec = NativeMethodsShared.IsWindows ? @"c:\foo.txt" : "/foo.txt"; string itemSpecDir = NativeMethodsShared.IsWindows ? @"c:\" : "/"; - modifier = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, FileUtilities.ItemSpecModifiers.FullPath, ref cache); + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.FullPath, ref cache); Assert.Equal(itemSpec, modifier); Assert.Equal(itemSpec, cache); - modifier = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, FileUtilities.ItemSpecModifiers.RootDir, ref cache); + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.RootDir, ref cache); Assert.Equal(itemSpecDir, modifier); - modifier = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, FileUtilities.ItemSpecModifiers.Filename, ref cache); + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.Filename, ref cache); Assert.Equal(@"foo", modifier); - modifier = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, FileUtilities.ItemSpecModifiers.Extension, ref cache); + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.Extension, ref cache); Assert.Equal(@".txt", modifier); - modifier = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, FileUtilities.ItemSpecModifiers.Directory, ref cache); + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.Directory, ref cache); Assert.Equal(String.Empty, modifier); - modifier = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, FileUtilities.ItemSpecModifiers.Identity, ref cache); + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.Identity, ref cache); Assert.Equal(itemSpec, modifier); string projectPath = NativeMethodsShared.IsWindows ? @"c:\abc\goo.proj" : @"/abc/goo.proj"; string projectPathDir = NativeMethodsShared.IsWindows ? @"c:\abc\" : @"/abc/"; - modifier = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, projectPath, FileUtilities.ItemSpecModifiers.DefiningProjectDirectory, ref cache); + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, projectPath, ItemSpecModifiers.DefiningProjectDirectory, ref cache); Assert.Equal(projectPathDir, modifier); - modifier = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, projectPath, FileUtilities.ItemSpecModifiers.DefiningProjectExtension, ref cache); + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, projectPath, ItemSpecModifiers.DefiningProjectExtension, ref cache); Assert.Equal(@".proj", modifier); - modifier = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, projectPath, FileUtilities.ItemSpecModifiers.DefiningProjectFullPath, ref cache); + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, projectPath, ItemSpecModifiers.DefiningProjectFullPath, ref cache); Assert.Equal(projectPath, modifier); - modifier = FileUtilities.ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, projectPath, FileUtilities.ItemSpecModifiers.DefiningProjectName, ref cache); + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, projectPath, ItemSpecModifiers.DefiningProjectName, ref cache); Assert.Equal(@"goo", modifier); } @@ -148,7 +148,7 @@ public void MakeRelativeTests() } /// - /// Exercises FileUtilities.ItemSpecModifiers.GetItemSpecModifier on a bad path. + /// Exercises ItemSpecModifiers.GetItemSpecModifier on a bad path. /// [WindowsFullFrameworkOnlyFact(additionalMessage: ".NET Core 2.1+ no longer validates paths: https://github.com/dotnet/corefx/issues/27779#issuecomment-371253486.")] public void GetItemSpecModifierOnBadPath() @@ -159,7 +159,7 @@ public void GetItemSpecModifierOnBadPath() }); } /// - /// Exercises FileUtilities.ItemSpecModifiers.GetItemSpecModifier on a bad path. + /// Exercises ItemSpecModifiers.GetItemSpecModifier on a bad path. /// [WindowsFullFrameworkOnlyFact(additionalMessage: ".NET Core 2.1+ no longer validates paths: https://github.com/dotnet/corefx/issues/27779#issuecomment-371253486.")] public void GetItemSpecModifierOnBadPath2() @@ -175,7 +175,7 @@ private static void TestGetItemSpecModifierOnBadPath(string currentDirectory) try { string cache = null; - FileUtilities.ItemSpecModifiers.GetItemSpecModifier(currentDirectory, @"http://www.microsoft.com", String.Empty, FileUtilities.ItemSpecModifiers.RootDir, ref cache); + ItemSpecModifiers.GetItemSpecModifier(currentDirectory, @"http://www.microsoft.com", String.Empty, ItemSpecModifiers.RootDir, ref cache); } catch (Exception e) { @@ -333,80 +333,80 @@ public void EnsureTrailingSlash() } /// - /// Exercises FileUtilities.ItemSpecModifiers.IsItemSpecModifier + /// Exercises ItemSpecModifiers.IsItemSpecModifier /// [Fact] public void IsItemSpecModifier() { // Positive matches using exact case. - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("FullPath")); // "test 1" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("RootDir")); // "test 2" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("Filename")); // "test 3" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("Extension")); // "test 4" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("RelativeDir")); // "test 5" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("Directory")); // "test 6" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("RecursiveDir")); // "test 7" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("Identity")); // "test 8" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("ModifiedTime")); // "test 9" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("CreatedTime")); // "test 10" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("AccessedTime")); // "test 11" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("FullPath")); // "test 1" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("RootDir")); // "test 2" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("Filename")); // "test 3" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("Extension")); // "test 4" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("RelativeDir")); // "test 5" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("Directory")); // "test 6" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("RecursiveDir")); // "test 7" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("Identity")); // "test 8" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("ModifiedTime")); // "test 9" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("CreatedTime")); // "test 10" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("AccessedTime")); // "test 11" // Positive matches using different case. - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("fullPath")); // "test 21" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("rootDir")); // "test 22" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("filename")); // "test 23" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("extension")); // "test 24" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("relativeDir")); // "test 25" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("directory")); // "test 26" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("recursiveDir")); // "test 27" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("identity")); // "test 28" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("modifiedTime")); // "test 29" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("createdTime")); // "test 30" - Assert.True(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("accessedTime")); // "test 31" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("fullPath")); // "test 21" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("rootDir")); // "test 22" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("filename")); // "test 23" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("extension")); // "test 24" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("relativeDir")); // "test 25" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("directory")); // "test 26" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("recursiveDir")); // "test 27" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("identity")); // "test 28" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("modifiedTime")); // "test 29" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("createdTime")); // "test 30" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("accessedTime")); // "test 31" // Negative tests to get maximum code coverage inside the many different branches - // of FileUtilities.ItemSpecModifiers.IsItemSpecModifier. - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("rootxxx")); // "test 41" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("Rootxxx")); // "test 42" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("xxxxxxx")); // "test 43" - - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("filexxxx")); // "test 44" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("Filexxxx")); // "test 45" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("idenxxxx")); // "test 46" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("Idenxxxx")); // "test 47" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("xxxxxxxx")); // "test 48" - - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("extenxxxx")); // "test 49" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("Extenxxxx")); // "test 50" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("direcxxxx")); // "test 51" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("Direcxxxx")); // "test 52" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("xxxxxxxxx")); // "test 53" - - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("xxxxxxxxxx")); // "test 54" - - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("relativexxx")); // "test 55" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("Relativexxx")); // "test 56" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("createdxxxx")); // "test 57" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("Createdxxxx")); // "test 58" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("xxxxxxxxxxx")); // "test 59" - - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("recursivexxx")); // "test 60" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("Recursivexxx")); // "test 61" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("accessedxxxx")); // "test 62" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("Accessedxxxx")); // "test 63" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("modifiedxxxx")); // "test 64" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("Modifiedxxxx")); // "test 65" - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier("xxxxxxxxxxxx")); // "test 66" - - Assert.False(FileUtilities.ItemSpecModifiers.IsItemSpecModifier(null)); // "test 67" + // of ItemSpecModifiers.IsItemSpecModifier. + Assert.False(ItemSpecModifiers.IsItemSpecModifier("rootxxx")); // "test 41" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Rootxxx")); // "test 42" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxx")); // "test 43" + + Assert.False(ItemSpecModifiers.IsItemSpecModifier("filexxxx")); // "test 44" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Filexxxx")); // "test 45" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("idenxxxx")); // "test 46" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Idenxxxx")); // "test 47" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxxx")); // "test 48" + + Assert.False(ItemSpecModifiers.IsItemSpecModifier("extenxxxx")); // "test 49" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Extenxxxx")); // "test 50" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("direcxxxx")); // "test 51" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Direcxxxx")); // "test 52" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxxxx")); // "test 53" + + Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxxxxx")); // "test 54" + + Assert.False(ItemSpecModifiers.IsItemSpecModifier("relativexxx")); // "test 55" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Relativexxx")); // "test 56" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("createdxxxx")); // "test 57" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Createdxxxx")); // "test 58" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxxxxxx")); // "test 59" + + Assert.False(ItemSpecModifiers.IsItemSpecModifier("recursivexxx")); // "test 60" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Recursivexxx")); // "test 61" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("accessedxxxx")); // "test 62" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Accessedxxxx")); // "test 63" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("modifiedxxxx")); // "test 64" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Modifiedxxxx")); // "test 65" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxxxxxxx")); // "test 66" + + Assert.False(ItemSpecModifiers.IsItemSpecModifier(null)); // "test 67" } [Fact] public void CheckDerivableItemSpecModifiers() { - Assert.True(FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier("Filename")); - Assert.False(FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier("RecursiveDir")); - Assert.False(FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier("recursivedir")); + Assert.True(ItemSpecModifiers.IsDerivableItemSpecModifier("Filename")); + Assert.False(ItemSpecModifiers.IsDerivableItemSpecModifier("RecursiveDir")); + Assert.False(ItemSpecModifiers.IsDerivableItemSpecModifier("recursivedir")); } [WindowsOnlyFact] @@ -441,7 +441,7 @@ public void GetItemSpecModifierRootDirThatFitsIntoMaxPath() string fullPath = @"c:\aardvark\aardvark\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\a.cs"; string cache = fullPath; - Assert.Equal(@"c:\", FileUtilities.ItemSpecModifiers.GetItemSpecModifier(currentDirectory, fullPath, String.Empty, FileUtilities.ItemSpecModifiers.RootDir, ref cache)); + Assert.Equal(@"c:\", ItemSpecModifiers.GetItemSpecModifier(currentDirectory, fullPath, String.Empty, ItemSpecModifiers.RootDir, ref cache)); } [Fact] diff --git a/src/Tasks/AssemblyDependency/ReferenceTable.cs b/src/Tasks/AssemblyDependency/ReferenceTable.cs index 1c68ec4d840..ca34cbccf33 100644 --- a/src/Tasks/AssemblyDependency/ReferenceTable.cs +++ b/src/Tasks/AssemblyDependency/ReferenceTable.cs @@ -870,7 +870,7 @@ private static AssemblyNameExtension GetAssemblyNameFromItemMetadata(ITaskItem i if (string.IsNullOrEmpty(name)) { // Fall back to inferring assembly name from file name. - name = item.GetMetadata(FileUtilities.ItemSpecModifiers.Filename); + name = item.GetMetadata(ItemSpecModifiers.Filename); } return new AssemblyNameExtension($"{name}, Version={version}, Culture=neutral, PublicKeyToken={publicKeyToken}"); diff --git a/src/Tasks/AssignLinkMetadata.cs b/src/Tasks/AssignLinkMetadata.cs index d880dc094c4..e49208769ef 100644 --- a/src/Tasks/AssignLinkMetadata.cs +++ b/src/Tasks/AssignLinkMetadata.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.Utilities; #nullable disable @@ -45,9 +44,9 @@ public override bool Execute() { try { - string definingProject = item.GetMetadata(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath); - string definingProjectDirectory = item.GetMetadata(FileUtilities.ItemSpecModifiers.DefiningProjectDirectory); - string fullPath = item.GetMetadata(FileUtilities.ItemSpecModifiers.FullPath); + string definingProject = item.GetMetadata(ItemSpecModifiers.DefiningProjectFullPath); + string definingProjectDirectory = item.GetMetadata(ItemSpecModifiers.DefiningProjectDirectory); + string fullPath = item.GetMetadata(ItemSpecModifiers.FullPath); if ( String.IsNullOrEmpty(item.GetMetadata("Link")) diff --git a/src/Tasks/CreateItem.cs b/src/Tasks/CreateItem.cs index 56cb7b1c021..cfb61b0919f 100644 --- a/src/Tasks/CreateItem.cs +++ b/src/Tasks/CreateItem.cs @@ -117,7 +117,7 @@ private List CreateOutputItems(Dictionary metadataTab // 2. If there is no existing metadata then apply the new if ((!PreserveExistingMetadata) || String.IsNullOrEmpty(newItem.GetMetadata(nameAndValue.Key))) { - if (FileUtilities.ItemSpecModifiers.IsItemSpecModifier(nameAndValue.Key)) + if (ItemSpecModifiers.IsItemSpecModifier(nameAndValue.Key)) { // Explicitly setting built-in metadata, is not allowed. Log.LogErrorWithCodeFromResources("CreateItem.AdditionalMetadataError", nameAndValue.Key); @@ -194,7 +194,7 @@ private List CreateOutputItems(Dictionary metadataTab { if (!string.IsNullOrEmpty(match.wildcardDirectoryPart)) { - newItem.SetMetadata(FileUtilities.ItemSpecModifiers.RecursiveDir, match.wildcardDirectoryPart); + newItem.SetMetadata(ItemSpecModifiers.RecursiveDir, match.wildcardDirectoryPart); } } diff --git a/src/Tasks/MSBuild.cs b/src/Tasks/MSBuild.cs index d3c24bd0f1d..df5ca14e163 100644 --- a/src/Tasks/MSBuild.cs +++ b/src/Tasks/MSBuild.cs @@ -654,7 +654,7 @@ internal static bool ExecuteTargets( // Set a metadata on the output items called "MSBuildProjectFile" which tells you which project file produced this item. if (String.IsNullOrEmpty(outputItemFromTarget.GetMetadata(ItemMetadataNames.msbuildSourceProjectFile))) { - outputItemFromTarget.SetMetadata(ItemMetadataNames.msbuildSourceProjectFile, projects[i].GetMetadata(FileUtilities.ItemSpecModifiers.FullPath)); + outputItemFromTarget.SetMetadata(ItemMetadataNames.msbuildSourceProjectFile, projects[i].GetMetadata(ItemSpecModifiers.FullPath)); } } diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj index a053bd4e0b9..a4975e7435f 100644 --- a/src/Tasks/Microsoft.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.Build.Tasks.csproj @@ -128,9 +128,6 @@ StringUtils.cs - - Modifiers.cs - diff --git a/src/Utilities.UnitTests/TaskItem_Tests.cs b/src/Utilities.UnitTests/TaskItem_Tests.cs index a7db155e2ce..6bdb86eccf7 100644 --- a/src/Utilities.UnitTests/TaskItem_Tests.cs +++ b/src/Utilities.UnitTests/TaskItem_Tests.cs @@ -41,9 +41,9 @@ public void ConstructWithITaskItem() to.GetMetadata("Cat").ShouldBe(""); // manipulate the item-spec a bit - to.GetMetadata(FileUtilities.ItemSpecModifiers.Filename).ShouldBe("Monkey"); - to.GetMetadata(FileUtilities.ItemSpecModifiers.Extension).ShouldBe(".txt"); - to.GetMetadata(FileUtilities.ItemSpecModifiers.RelativeDir).ShouldBe(string.Empty); + to.GetMetadata(ItemSpecModifiers.Filename).ShouldBe("Monkey"); + to.GetMetadata(ItemSpecModifiers.Extension).ShouldBe(".txt"); + to.GetMetadata(ItemSpecModifiers.RelativeDir).ShouldBe(string.Empty); } // Make sure metadata can be cloned from an existing ITaskItem @@ -88,14 +88,14 @@ public void MetadataNamesAndCount() TaskItem taskItem = new TaskItem("x"); // Without custom metadata, should return the built in metadata - taskItem.MetadataNames.Cast().ShouldBeSetEquivalentTo(FileUtilities.ItemSpecModifiers.All); - taskItem.MetadataCount.ShouldBe(FileUtilities.ItemSpecModifiers.All.Length); + taskItem.MetadataNames.Cast().ShouldBeSetEquivalentTo(ItemSpecModifiers.All); + taskItem.MetadataCount.ShouldBe(ItemSpecModifiers.All.Length); // Now add one taskItem.SetMetadata("m", "m1"); - taskItem.MetadataNames.Cast().ShouldBeSetEquivalentTo(FileUtilities.ItemSpecModifiers.All.Concat(new[] { "m" })); - taskItem.MetadataCount.ShouldBe(FileUtilities.ItemSpecModifiers.All.Length + 1); + taskItem.MetadataNames.Cast().ShouldBeSetEquivalentTo(ItemSpecModifiers.All.Concat(new[] { "m" })); + taskItem.MetadataCount.ShouldBe(ItemSpecModifiers.All.Length + 1); } [Fact] @@ -113,15 +113,15 @@ public void NullITaskItemCast() public void ConstructFromDictionary() { Hashtable h = new Hashtable(); - h[FileUtilities.ItemSpecModifiers.Filename] = "foo"; - h[FileUtilities.ItemSpecModifiers.Extension] = "bar"; + h[ItemSpecModifiers.Filename] = "foo"; + h[ItemSpecModifiers.Extension] = "bar"; h["custom"] = "hello"; TaskItem t = new TaskItem("bamboo.baz", h); // item-spec modifiers were not overridden by dictionary passed to constructor - t.GetMetadata(FileUtilities.ItemSpecModifiers.Filename).ShouldBe("bamboo"); - t.GetMetadata(FileUtilities.ItemSpecModifiers.Extension).ShouldBe(".baz"); + t.GetMetadata(ItemSpecModifiers.Filename).ShouldBe("bamboo"); + t.GetMetadata(ItemSpecModifiers.Extension).ShouldBe(".baz"); t.GetMetadata("CUSTOM").ShouldBe("hello"); } @@ -134,7 +134,7 @@ public void CannotChangeModifiers() try { - t.SetMetadata(FileUtilities.ItemSpecModifiers.FullPath, "bazbaz"); + t.SetMetadata(ItemSpecModifiers.FullPath, "bazbaz"); } catch (Exception e) { @@ -154,7 +154,7 @@ public void CannotRemoveModifiers() try { - t.RemoveMetadata(FileUtilities.ItemSpecModifiers.RootDir); + t.RemoveMetadata(ItemSpecModifiers.RootDir); } catch (Exception e) { @@ -169,11 +169,11 @@ public void CheckMetadataCount() { TaskItem t = new TaskItem("foo"); - t.MetadataCount.ShouldBe(FileUtilities.ItemSpecModifiers.All.Length); + t.MetadataCount.ShouldBe(ItemSpecModifiers.All.Length); t.SetMetadata("grog", "RUM"); - t.MetadataCount.ShouldBe(FileUtilities.ItemSpecModifiers.All.Length + 1); + t.MetadataCount.ShouldBe(ItemSpecModifiers.All.Length + 1); } [Fact] @@ -181,7 +181,7 @@ public void NonexistentRequestFullPath() { TaskItem from = new TaskItem(); from.ItemSpec = "Monkey.txt"; - from.GetMetadata(FileUtilities.ItemSpecModifiers.FullPath).ShouldBe( + from.GetMetadata(ItemSpecModifiers.FullPath).ShouldBe( Path.Combine( Directory.GetCurrentDirectory(), "Monkey.txt")); @@ -192,7 +192,7 @@ public void NonexistentRequestRootDir() { TaskItem from = new TaskItem(); from.ItemSpec = "Monkey.txt"; - from.GetMetadata(FileUtilities.ItemSpecModifiers.RootDir).ShouldBe(Path.GetPathRoot(from.GetMetadata(FileUtilities.ItemSpecModifiers.FullPath))); + from.GetMetadata(ItemSpecModifiers.RootDir).ShouldBe(Path.GetPathRoot(from.GetMetadata(ItemSpecModifiers.FullPath))); } [Fact] @@ -200,7 +200,7 @@ public void NonexistentRequestFilename() { TaskItem from = new TaskItem(); from.ItemSpec = "Monkey.txt"; - from.GetMetadata(FileUtilities.ItemSpecModifiers.Filename).ShouldBe("Monkey"); + from.GetMetadata(ItemSpecModifiers.Filename).ShouldBe("Monkey"); } [Fact] @@ -208,7 +208,7 @@ public void NonexistentRequestExtension() { TaskItem from = new TaskItem(); from.ItemSpec = "Monkey.txt"; - from.GetMetadata(FileUtilities.ItemSpecModifiers.Extension).ShouldBe(".txt"); + from.GetMetadata(ItemSpecModifiers.Extension).ShouldBe(".txt"); } [Fact] @@ -216,7 +216,7 @@ public void NonexistentRequestRelativeDir() { TaskItem from = new TaskItem(); from.ItemSpec = "Monkey.txt"; - from.GetMetadata(FileUtilities.ItemSpecModifiers.RelativeDir).Length.ShouldBe(0); + from.GetMetadata(ItemSpecModifiers.RelativeDir).Length.ShouldBe(0); } [Fact] @@ -224,7 +224,7 @@ public void NonexistentRequestDirectory() { TaskItem from = new TaskItem(); from.ItemSpec = NativeMethodsShared.IsWindows ? @"c:\subdir\Monkey.txt" : "/subdir/Monkey.txt"; - from.GetMetadata(FileUtilities.ItemSpecModifiers.Directory).ShouldBe(NativeMethodsShared.IsWindows ? @"subdir\" : "subdir/"); + from.GetMetadata(ItemSpecModifiers.Directory).ShouldBe(NativeMethodsShared.IsWindows ? @"subdir\" : "subdir/"); } [WindowsOnlyFact("UNC is not implemented except under Windows.")] @@ -232,7 +232,7 @@ public void NonexistentRequestDirectoryUNC() { TaskItem from = new TaskItem(); from.ItemSpec = @"\\local\share\subdir\Monkey.txt"; - from.GetMetadata(FileUtilities.ItemSpecModifiers.Directory).ShouldBe(@"subdir\"); + from.GetMetadata(ItemSpecModifiers.Directory).ShouldBe(@"subdir\"); } [Fact] @@ -241,7 +241,7 @@ public void NonexistentRequestRecursiveDir() TaskItem from = new TaskItem(); from.ItemSpec = "Monkey.txt"; - from.GetMetadata(FileUtilities.ItemSpecModifiers.RecursiveDir).Length.ShouldBe(0); + from.GetMetadata(ItemSpecModifiers.RecursiveDir).Length.ShouldBe(0); } [Fact] @@ -249,7 +249,7 @@ public void NonexistentRequestIdentity() { TaskItem from = new TaskItem(); from.ItemSpec = "Monkey.txt"; - from.GetMetadata(FileUtilities.ItemSpecModifiers.Identity).ShouldBe("Monkey.txt"); + from.GetMetadata(ItemSpecModifiers.Identity).ShouldBe("Monkey.txt"); } [Fact] @@ -258,19 +258,19 @@ public void RequestTimeStamps() TaskItem from = new TaskItem(); from.ItemSpec = FileUtilities.GetTemporaryFile(); - from.GetMetadata(FileUtilities.ItemSpecModifiers.ModifiedTime).Length.ShouldBeGreaterThan(0); + from.GetMetadata(ItemSpecModifiers.ModifiedTime).Length.ShouldBeGreaterThan(0); - from.GetMetadata(FileUtilities.ItemSpecModifiers.CreatedTime).Length.ShouldBeGreaterThan(0); + from.GetMetadata(ItemSpecModifiers.CreatedTime).Length.ShouldBeGreaterThan(0); - from.GetMetadata(FileUtilities.ItemSpecModifiers.AccessedTime).Length.ShouldBeGreaterThan(0); + from.GetMetadata(ItemSpecModifiers.AccessedTime).Length.ShouldBeGreaterThan(0); File.Delete(from.ItemSpec); - from.GetMetadata(FileUtilities.ItemSpecModifiers.ModifiedTime).Length.ShouldBe(0); + from.GetMetadata(ItemSpecModifiers.ModifiedTime).Length.ShouldBe(0); - from.GetMetadata(FileUtilities.ItemSpecModifiers.CreatedTime).Length.ShouldBe(0); + from.GetMetadata(ItemSpecModifiers.CreatedTime).Length.ShouldBe(0); - from.GetMetadata(FileUtilities.ItemSpecModifiers.AccessedTime).Length.ShouldBe(0); + from.GetMetadata(ItemSpecModifiers.AccessedTime).Length.ShouldBe(0); } /// diff --git a/src/Utilities/Microsoft.Build.Utilities.csproj b/src/Utilities/Microsoft.Build.Utilities.csproj index f4e986bf314..ed442c49c23 100644 --- a/src/Utilities/Microsoft.Build.Utilities.csproj +++ b/src/Utilities/Microsoft.Build.Utilities.csproj @@ -87,9 +87,6 @@ Shared\IKeyed.cs - - Shared\Modifiers.cs - Shared\InprocTrackingNativeMethods.cs diff --git a/src/Utilities/TaskItem.cs b/src/Utilities/TaskItem.cs index 2fa8a3789a1..c2b9cb60219 100644 --- a/src/Utilities/TaskItem.cs +++ b/src/Utilities/TaskItem.cs @@ -130,7 +130,7 @@ public TaskItem( { // don't import metadata whose names clash with the names of reserved metadata string key = (string)singleMetadata.Key; - if (!FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(key)) + if (!ItemSpecModifiers.IsDerivableItemSpecModifier(key)) { builder[key] = (string)singleMetadata.Value ?? string.Empty; } @@ -153,12 +153,12 @@ public TaskItem( if (!(sourceItem is ITaskItem2 sourceItemAsITaskItem2)) { _itemSpec = EscapingUtilities.Escape(sourceItem.ItemSpec); - _definingProject = EscapingUtilities.EscapeWithCaching(sourceItem.GetMetadata(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath)); + _definingProject = EscapingUtilities.EscapeWithCaching(sourceItem.GetMetadata(ItemSpecModifiers.DefiningProjectFullPath)); } else { _itemSpec = sourceItemAsITaskItem2.EvaluatedIncludeEscaped; - _definingProject = sourceItemAsITaskItem2.GetMetadataValueEscaped(FileUtilities.ItemSpecModifiers.DefiningProjectFullPath); + _definingProject = sourceItemAsITaskItem2.GetMetadataValueEscaped(ItemSpecModifiers.DefiningProjectFullPath); } sourceItem.CopyMetadataTo(this); @@ -217,7 +217,7 @@ public ICollection MetadataNames { get { - int count = (_metadata?.Count ?? 0) + FileUtilities.ItemSpecModifiers.All.Length; + int count = (_metadata?.Count ?? 0) + ItemSpecModifiers.All.Length; var metadataNames = new List(capacity: count); @@ -226,7 +226,7 @@ public ICollection MetadataNames metadataNames.AddRange(_metadata.Keys); } - metadataNames.AddRange(FileUtilities.ItemSpecModifiers.All); + metadataNames.AddRange(ItemSpecModifiers.All); return metadataNames; } @@ -236,7 +236,7 @@ public ICollection MetadataNames /// Gets the number of metadata set on the item. /// /// Count of metadata. - public int MetadataCount => (_metadata?.Count ?? 0) + FileUtilities.ItemSpecModifiers.All.Length; + public int MetadataCount => (_metadata?.Count ?? 0) + ItemSpecModifiers.All.Length; /// /// Gets the backing metadata dictionary in a serializable wrapper. @@ -271,7 +271,7 @@ private SerializableMetadata Metadata public void RemoveMetadata(string metadataName) { ErrorUtilities.VerifyThrowArgumentNull(metadataName); - ErrorUtilities.VerifyThrowArgument(!FileUtilities.ItemSpecModifiers.IsItemSpecModifier(metadataName), + ErrorUtilities.VerifyThrowArgument(!ItemSpecModifiers.IsItemSpecModifier(metadataName), "Shared.CannotChangeItemSpecModifiers", metadataName); _metadata = _metadata?.Remove(metadataName); @@ -307,7 +307,7 @@ public void SetMetadata( // 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), + ErrorUtilities.VerifyThrowArgument(!ItemSpecModifiers.IsDerivableItemSpecModifier(metadataName), "Shared.CannotChangeItemSpecModifiers", metadataName); _metadata ??= ImmutableDictionaryExtensions.EmptyMetadata; @@ -504,11 +504,11 @@ string ITaskItem2.GetMetadataValueEscaped(string metadataName) string metadataValue = null; - if (FileUtilities.ItemSpecModifiers.IsDerivableItemSpecModifier(metadataName)) + if (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, _itemSpec, _definingProject, metadataName, ref _fullPath); + metadataValue = ItemSpecModifiers.GetItemSpecModifier(null, _itemSpec, _definingProject, metadataName, ref _fullPath); } else { From 512378a9ce2c051b220acefa811595c0b5eef5ed Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 10 Mar 2026 11:51:09 -0700 Subject: [PATCH 17/27] Remove shared FileUtilities and update usages to use Framework version - Remove src/Shared/FileUtilities.cs and src/Shared/TempFileUtilities.cs from all project files. - Rename FrameworkFileUtilities to FileUtilities. - Delete src/Shared/FileUtilities.cs and src/Shared/TempFileUtilities.cs - Fix all remaining usages of Microsoft.Build.Shared.FileUtilities to point to Microsoft.Build.Framework.FileUtilities. - Clean up using directives --- .../Construction/ProjectFormatting_Tests.cs | 1 + .../ProjectImportElement_Tests.cs | 7 +- .../Construction/ProjectRootElement_Tests.cs | 11 +- .../Construction/SolutionFile_Tests.cs | 1 + .../WhiteSpacePreservation_Tests.cs | 1 + .../Definition/ProjectCollection_Tests.cs | 1 + .../Definition/ProjectItemDefinition_Tests.cs | 4 +- .../Definition/ProjectItem_Tests.cs | 8 +- .../Definition/ProjectProperty_Tests.cs | 3 +- .../Definition/Project_Tests.cs | 2 +- .../Definition/ProtectImports_Tests.cs | 3 +- .../Instance/ProjectTargetInstance_Tests.cs | 3 +- ...Microsoft.Build.Engine.OM.UnitTests.csproj | 2 - src/Build.OM.UnitTests/TransientIO.cs | 1 + .../SolutionFile_NewParser_Tests.cs | 1 + .../SolutionFile_OldParser_Tests.cs | 1 + .../ToolsetConfigurationReaderTestHelper.cs | 1 + .../EscapingInProjects_Tests.cs | 1 - .../Evaluation/Expander_Tests.cs | 2 +- .../ImportFromMSBuildExtensionsPath_Tests.cs | 1 + .../Evaluation/ItemEvaluation_Tests.cs | 1 + src/Build.UnitTests/ExpressionTree_Tests.cs | 3 +- .../Globbing/MSBuildGlob_Tests.cs | 1 + .../BackEnd/BuildManager/BuildRequestData.cs | 1 + .../BuildManager/CacheSerialization.cs | 1 + .../Components/Communications/CurrentHost.cs | 1 + .../FileAccesses/FileAccessManager.cs | 2 +- .../RequestBuilder/TargetUpToDateChecker.cs | 14 +- .../SdkResolution/SdkResolverManifest.cs | 2 +- .../BackEnd/Shared/ConfigurationMetadata.cs | 1 + .../MultiThreadedTaskEnvironmentDriver.cs | 4 +- .../Checks/UntrustedLocationCheck.cs | 3 +- .../EditorConfig/EditorConfigParser.cs | 6 +- .../Utilities/BuildCheckUtilities.cs | 2 +- .../Construction/ProjectImportElement.cs | 4 +- src/Build/Construction/ProjectRootElement.cs | 2 +- .../Construction/ProjectUsingTaskElement.cs | 6 +- .../Solution/ProjectInSolution.cs | 2 +- .../Solution/SolutionProjectGenerator.cs | 2 +- src/Build/Definition/Toolset.cs | 2 +- .../Errors/InvalidProjectFileException.cs | 1 + .../FunctionCallExpressionNode.cs | 4 +- src/Build/Evaluation/Expander.cs | 14 +- .../Evaluation/Expander/WellKnownFunctions.cs | 4 +- src/Build/Evaluation/IntrinsicFunctions.cs | 2 +- src/Build/Evaluation/ItemSpec.cs | 1 + .../LazyItemEvaluator.LazyItemOperation.cs | 1 + .../Evaluation/ProjectRootElementCache.cs | 1 - src/Build/Globbing/MSBuildGlob.cs | 1 + src/Build/Graph/GraphBuilder.cs | 1 + src/Build/Graph/ProjectGraphEntryPoint.cs | 1 + src/Build/Graph/ProjectGraphNode.cs | 1 + src/Build/Instance/ProjectItemInstance.cs | 4 +- src/Build/Instance/TaskRegistry.cs | 2 +- src/Build/Logging/FileLogger.cs | 2 +- src/Build/Microsoft.Build.csproj | 4 - src/Build/Utilities/FileSpecMatchTester.cs | 2 +- src/Framework/FileSystem/WindowsFileSystem.cs | 2 +- src/Framework/FileUtilities.cs | 21 +- src/Framework/FileUtilities_TempFiles.cs | 2 +- src/Framework/ItemSpecModifiers.cs | 24 +- src/MSBuild/CommandLine/CommandLineParser.cs | 6 +- src/MSBuild/MSBuild.csproj | 6 - src/MSBuild/XMake.cs | 8 +- src/Shared/FileMatcher.cs | 10 +- src/Shared/FileUtilities.cs | 323 ------------------ src/Shared/FrameworkLocationHelper.cs | 2 +- src/Shared/SolutionConfiguration.cs | 1 + src/Shared/TempFileUtilities.cs | 56 --- src/Shared/ToolsetElement.cs | 1 + src/Shared/UnitTests/FileMatcher_Tests.cs | 14 +- src/Shared/UnitTests/FileUtilities_Tests.cs | 52 +-- .../UnitTests/NativeMethodsShared_Tests.cs | 1 + .../TypeLoader_Dependencies_Tests.cs | 4 +- src/Shared/UnitTests/TypeLoader_Tests.cs | 8 +- ...olveAssemblyReferenceCacheSerialization.cs | 1 + src/Tasks.UnitTests/AxTlbBaseTask_Tests.cs | 1 + src/Tasks.UnitTests/CodeTaskFactoryTests.cs | 8 +- src/Tasks.UnitTests/Copy_Tests.cs | 4 +- .../CreateCSharpManifestResourceName_Tests.cs | 2 +- ...teVisualBasicManifestResourceName_Tests.cs | 12 +- .../FindAppConfigFile_Tests.cs | 4 +- src/Tasks.UnitTests/FindInList_Tests.cs | 4 +- src/Tasks.UnitTests/FindUnderPath_Tests.cs | 4 +- .../GetReferencePaths_Tests.cs | 1 + src/Tasks.UnitTests/HintPathResolver_Tests.cs | 1 + src/Tasks.UnitTests/OutputPathTests.cs | 1 + .../ResolveCodeAnalysisRuleSet_Tests.cs | 1 + .../ResGenDependencies_Tests.cs | 1 + src/Tasks/AppConfig/AppConfig.cs | 2 +- .../AssemblyFoldersFromConfigCache.cs | 1 + .../CandidateAssemblyFilesResolver.cs | 1 + .../AssemblyDependency/HintPathResolver.cs | 1 + .../AssemblyDependency/ReferenceTable.cs | 4 +- .../ResolveAssemblyReference.cs | 2 +- src/Tasks/AssignTargetPath.cs | 8 +- src/Tasks/BootstrapperUtil/ResourceUpdater.cs | 3 +- src/Tasks/Copy.cs | 2 +- src/Tasks/CreateCSharpManifestResourceName.cs | 6 +- .../CreateVisualBasicManifestResourceName.cs | 2 +- src/Tasks/DependencyFile.cs | 3 +- src/Tasks/FileIO/WriteLinesToFile.cs | 2 +- src/Tasks/FindInList.cs | 2 +- src/Tasks/GetFrameworkSDKPath.cs | 16 +- src/Tasks/GetSDKReferenceFiles.cs | 4 +- src/Tasks/ListOperators/FindUnderPath.cs | 10 +- src/Tasks/MakeDir.cs | 2 +- src/Tasks/ManifestUtil/ManifestWriter.cs | 2 +- src/Tasks/ManifestUtil/Util.cs | 1 - src/Tasks/Microsoft.Build.Tasks.csproj | 4 - src/Tasks/RedistList.cs | 1 + src/Tasks/ResGenDependencies.cs | 5 +- src/Tasks/ResolveSDKReference.cs | 2 +- .../ResourceHandling/MSBuildResXReader.cs | 2 +- src/Tasks/Touch.cs | 2 +- src/Tasks/Unzip.cs | 2 +- .../RequiresSymbolicLinksFactAttribute.cs | 2 +- .../CommandLineBuilder_Tests.cs | 4 +- .../PlatformManifest_Tests.cs | 1 + .../ToolLocationHelper_Tests.cs | 4 +- .../TrackedDependencies/FileTrackerTests.cs | 4 +- src/Utilities/CommandLineBuilder.cs | 2 +- .../Microsoft.Build.Utilities.csproj | 6 - src/Utilities/TargetPlatformSDK.cs | 2 +- src/Utilities/TaskItem.cs | 6 +- src/Utilities/ToolLocationHelper.cs | 16 +- .../CanonicalTrackedInputFiles.cs | 4 +- .../CanonicalTrackedOutputFiles.cs | 2 +- .../DependencyTableCache.cs | 1 - .../TrackedDependencies/FileTracker.cs | 22 +- .../TrackedDependencies/FlatTrackingData.cs | 2 +- 131 files changed, 274 insertions(+), 643 deletions(-) delete mode 100644 src/Shared/FileUtilities.cs delete mode 100644 src/Shared/TempFileUtilities.cs diff --git a/src/Build.OM.UnitTests/Construction/ProjectFormatting_Tests.cs b/src/Build.OM.UnitTests/Construction/ProjectFormatting_Tests.cs index cc0562f9752..678577ef4a0 100644 --- a/src/Build.OM.UnitTests/Construction/ProjectFormatting_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/ProjectFormatting_Tests.cs @@ -8,6 +8,7 @@ using System.Text; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.UnitTests; using Xunit; diff --git a/src/Build.OM.UnitTests/Construction/ProjectImportElement_Tests.cs b/src/Build.OM.UnitTests/Construction/ProjectImportElement_Tests.cs index a7dd4f9d1d8..2a5cdef3fb3 100644 --- a/src/Build.OM.UnitTests/Construction/ProjectImportElement_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/ProjectImportElement_Tests.cs @@ -7,6 +7,7 @@ using System.Xml; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Xunit; using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException; @@ -159,12 +160,12 @@ public void SettingProjectDirties() try { - file1 = Microsoft.Build.Shared.FileUtilities.GetTemporaryFileName(); + file1 = FileUtilities.GetTemporaryFileName(); ProjectRootElement importProject1 = ProjectRootElement.Create(); importProject1.AddProperty("p", "v1"); importProject1.Save(file1); - file2 = Microsoft.Build.Shared.FileUtilities.GetTemporaryFileName(); + file2 = FileUtilities.GetTemporaryFileName(); ProjectRootElement importProject2 = ProjectRootElement.Create(); importProject2.AddProperty("p", "v2"); importProject2.Save(file2); @@ -203,7 +204,7 @@ public void SettingConditionDirties() try { - file = Microsoft.Build.Shared.FileUtilities.GetTemporaryFileName(); + file = FileUtilities.GetTemporaryFileName(); ProjectRootElement importProject = ProjectRootElement.Create(); importProject.AddProperty("p", "v1"); importProject.Save(file); diff --git a/src/Build.OM.UnitTests/Construction/ProjectRootElement_Tests.cs b/src/Build.OM.UnitTests/Construction/ProjectRootElement_Tests.cs index 95256f674d9..50d577d8ba9 100644 --- a/src/Build.OM.UnitTests/Construction/ProjectRootElement_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/ProjectRootElement_Tests.cs @@ -19,6 +19,7 @@ using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException; using ProjectCollection = Microsoft.Build.Evaluation.ProjectCollection; using Xunit; +using Microsoft.Build.Framework; #nullable disable @@ -253,7 +254,7 @@ public void ConstructOverSameFileReturnsSameEvenWithOneBeingRelativePath4() public void SetFullPathProjectXmlAlreadyLoaded() { ProjectRootElement projectXml1 = ProjectRootElement.Create(); - projectXml1.FullPath = Microsoft.Build.Shared.FileUtilities.GetTemporaryFile(); + projectXml1.FullPath = FileUtilities.GetTemporaryFile(); ProjectRootElement projectXml2 = ProjectRootElement.Create(); projectXml2.FullPath = projectXml1.FullPath; @@ -425,7 +426,7 @@ public void ValidXmlInvalidSyntaxOpenFromDiskTwice() { try { - path = Microsoft.Build.Shared.FileUtilities.GetTemporaryFileName(); + path = FileUtilities.GetTemporaryFileName(); File.WriteAllText(path, content); ProjectRootElement.Open(path); @@ -909,7 +910,7 @@ public void SolutionCanNotBeOpened() try { - tempFileSentinel = Microsoft.Build.Shared.FileUtilities.GetTemporaryFile(); + tempFileSentinel = FileUtilities.GetTemporaryFile(); solutionFile = Path.ChangeExtension(tempFileSentinel, ".sln"); File.Copy(tempFileSentinel, solutionFile); @@ -954,7 +955,7 @@ public void ProjectCanNotBeOpened() try { // Does not have .sln or .vcproj extension so loads as project - projectFile = Microsoft.Build.Shared.FileUtilities.GetTemporaryFile(); + projectFile = FileUtilities.GetTemporaryFile(); security = new FileSecurity(projectFile, System.Security.AccessControl.AccessControlSections.All); security.AddAccessRule(rule); @@ -990,7 +991,7 @@ public void SolutionCorrupt() try { - solutionFile = Microsoft.Build.Shared.FileUtilities.GetTemporaryFileName(); + solutionFile = FileUtilities.GetTemporaryFileName(); // Arbitrary corrupt content string content = @"Microsoft Visual Studio Solution File, Format Version 10.00 diff --git a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs index 836d884c5f1..af2ff258097 100644 --- a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs @@ -6,6 +6,7 @@ using System.Threading; using Microsoft.Build.Construction; using Microsoft.Build.Exceptions; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.VisualStudio.SolutionPersistence; using Microsoft.VisualStudio.SolutionPersistence.Model; diff --git a/src/Build.OM.UnitTests/Construction/WhiteSpacePreservation_Tests.cs b/src/Build.OM.UnitTests/Construction/WhiteSpacePreservation_Tests.cs index 87017af556a..9020696e810 100644 --- a/src/Build.OM.UnitTests/Construction/WhiteSpacePreservation_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/WhiteSpacePreservation_Tests.cs @@ -7,6 +7,7 @@ using System.Text.RegularExpressions; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Xunit; diff --git a/src/Build.OM.UnitTests/Definition/ProjectCollection_Tests.cs b/src/Build.OM.UnitTests/Definition/ProjectCollection_Tests.cs index 168cb67c58e..360edefcac2 100644 --- a/src/Build.OM.UnitTests/Definition/ProjectCollection_Tests.cs +++ b/src/Build.OM.UnitTests/Definition/ProjectCollection_Tests.cs @@ -9,6 +9,7 @@ using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Utilities; using Shouldly; diff --git a/src/Build.OM.UnitTests/Definition/ProjectItemDefinition_Tests.cs b/src/Build.OM.UnitTests/Definition/ProjectItemDefinition_Tests.cs index dcce4d61853..2c53cc130a6 100644 --- a/src/Build.OM.UnitTests/Definition/ProjectItemDefinition_Tests.cs +++ b/src/Build.OM.UnitTests/Definition/ProjectItemDefinition_Tests.cs @@ -121,7 +121,7 @@ public void UpdateMetadataImported() try { - file = Microsoft.Build.Shared.FileUtilities.GetTemporaryFile(); + file = FileUtilities.GetTemporaryFile(); ProjectRootElement import = ProjectRootElement.Create(file); import.AddItemDefinitionGroup().AddItemDefinition("i").AddMetadata("m", "m0"); import.Save(); @@ -151,7 +151,7 @@ public void SetMetadataImported() try { - file = Microsoft.Build.Shared.FileUtilities.GetTemporaryFile(); + file = FileUtilities.GetTemporaryFile(); ProjectRootElement import = ProjectRootElement.Create(file); import.AddItemDefinitionGroup().AddItemDefinition("i").AddMetadata("m", "m0"); import.Save(); diff --git a/src/Build.OM.UnitTests/Definition/ProjectItem_Tests.cs b/src/Build.OM.UnitTests/Definition/ProjectItem_Tests.cs index 600f24ad21e..98874fae443 100644 --- a/src/Build.OM.UnitTests/Definition/ProjectItem_Tests.cs +++ b/src/Build.OM.UnitTests/Definition/ProjectItem_Tests.cs @@ -319,7 +319,7 @@ public void BuiltInMetadataTimes() try { - path = Microsoft.Build.Shared.FileUtilities.GetTemporaryFileName(); + path = FileUtilities.GetTemporaryFileName(); File.WriteAllText(path, String.Empty); FileInfo info = new FileInfo(path); @@ -2053,7 +2053,7 @@ public void RenameImported() try { - file = Microsoft.Build.Shared.FileUtilities.GetTemporaryFileName(); + file = FileUtilities.GetTemporaryFileName(); Project import = new Project(); import.AddItem("i", "i1"); import.Save(file); @@ -2084,7 +2084,7 @@ public void SetMetadataImported() try { - file = Microsoft.Build.Shared.FileUtilities.GetTemporaryFileName(); + file = FileUtilities.GetTemporaryFileName(); Project import = new Project(); import.AddItem("i", "i1"); import.Save(file); @@ -2115,7 +2115,7 @@ public void RemoveMetadataImported() try { - file = Microsoft.Build.Shared.FileUtilities.GetTemporaryFileName(); + file = FileUtilities.GetTemporaryFileName(); Project import = new Project(); ProjectItem item = import.AddItem("i", "i1")[0]; item.SetMetadataValue("m", "m0"); diff --git a/src/Build.OM.UnitTests/Definition/ProjectProperty_Tests.cs b/src/Build.OM.UnitTests/Definition/ProjectProperty_Tests.cs index c028fd294cd..bac78f09a21 100644 --- a/src/Build.OM.UnitTests/Definition/ProjectProperty_Tests.cs +++ b/src/Build.OM.UnitTests/Definition/ProjectProperty_Tests.cs @@ -7,6 +7,7 @@ using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; +using Microsoft.Build.Framework; using Xunit; #nullable disable @@ -260,7 +261,7 @@ public void SetPropertyImported() try { - file = Microsoft.Build.Shared.FileUtilities.GetTemporaryFileName(); + file = FileUtilities.GetTemporaryFileName(); Project import = new Project(); import.SetProperty("p", "v0"); import.Save(file); diff --git a/src/Build.OM.UnitTests/Definition/Project_Tests.cs b/src/Build.OM.UnitTests/Definition/Project_Tests.cs index d1e43441816..10ce7b415a9 100644 --- a/src/Build.OM.UnitTests/Definition/Project_Tests.cs +++ b/src/Build.OM.UnitTests/Definition/Project_Tests.cs @@ -2089,7 +2089,7 @@ public void BuildEvaluationUsesCustomLoggers() ObjectModelHelpers.CleanupFileContents(@" "); - string importFileName = Microsoft.Build.Shared.FileUtilities.GetTemporaryFileName() + ".proj"; + string importFileName = FileUtilities.GetTemporaryFileName() + ".proj"; File.WriteAllText(importFileName, importProjectContent); string projectContent = diff --git a/src/Build.OM.UnitTests/Definition/ProtectImports_Tests.cs b/src/Build.OM.UnitTests/Definition/ProtectImports_Tests.cs index 6a337e3bbc9..786ecef6691 100644 --- a/src/Build.OM.UnitTests/Definition/ProtectImports_Tests.cs +++ b/src/Build.OM.UnitTests/Definition/ProtectImports_Tests.cs @@ -7,6 +7,7 @@ using System.Linq; using Microsoft.Build.Evaluation; +using Microsoft.Build.Framework; using Xunit; #nullable disable @@ -103,7 +104,7 @@ public ProtectImports_Tests() "; importContents = Expand(importContents); - _importFilename = Microsoft.Build.Shared.FileUtilities.GetTemporaryFileName() + ".targets"; + _importFilename = FileUtilities.GetTemporaryFileName() + ".targets"; File.WriteAllText(_importFilename, importContents); } diff --git a/src/Build.OM.UnitTests/Instance/ProjectTargetInstance_Tests.cs b/src/Build.OM.UnitTests/Instance/ProjectTargetInstance_Tests.cs index fb41c9da8a1..8f7dacd23fe 100644 --- a/src/Build.OM.UnitTests/Instance/ProjectTargetInstance_Tests.cs +++ b/src/Build.OM.UnitTests/Instance/ProjectTargetInstance_Tests.cs @@ -6,6 +6,7 @@ using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; +using Microsoft.Build.Framework; using Xunit; #nullable disable @@ -111,7 +112,7 @@ public void FileLocationAvailableEvenAfterEdits() try { - path = Microsoft.Build.Shared.FileUtilities.GetTemporaryFileName(); + path = FileUtilities.GetTemporaryFileName(); ProjectRootElement projectXml = ProjectRootElement.Create(path); projectXml.Save(); diff --git a/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj b/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj index 4720d94db6e..675130444f8 100644 --- a/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj +++ b/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj @@ -45,8 +45,6 @@ BuildEnvironmentHelper.cs - - App.config diff --git a/src/Build.OM.UnitTests/TransientIO.cs b/src/Build.OM.UnitTests/TransientIO.cs index 2bf038c227c..efacda698f1 100644 --- a/src/Build.OM.UnitTests/TransientIO.cs +++ b/src/Build.OM.UnitTests/TransientIO.cs @@ -8,6 +8,7 @@ namespace Microsoft.Build.UnitTests using System; using System.Collections.Generic; using System.IO; + using Microsoft.Build.Framework; using Microsoft.Build.Shared; public class TransientIO : IDisposable diff --git a/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs b/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs index a15fffd391d..873cd2d1b5c 100644 --- a/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Threading; using Microsoft.Build.Construction; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.VisualStudio.SolutionPersistence; using Microsoft.VisualStudio.SolutionPersistence.Model; diff --git a/src/Build.UnitTests/Construction/SolutionFile_OldParser_Tests.cs b/src/Build.UnitTests/Construction/SolutionFile_OldParser_Tests.cs index 0d197044b63..e471c0373ce 100644 --- a/src/Build.UnitTests/Construction/SolutionFile_OldParser_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionFile_OldParser_Tests.cs @@ -8,6 +8,7 @@ using System.Text; using Microsoft.Build.Construction; using Microsoft.Build.Exceptions; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Shouldly; using Xunit; diff --git a/src/Build.UnitTests/Definition/ToolsetConfigurationReaderTestHelper.cs b/src/Build.UnitTests/Definition/ToolsetConfigurationReaderTestHelper.cs index 66d03679314..84a7a8131b1 100644 --- a/src/Build.UnitTests/Definition/ToolsetConfigurationReaderTestHelper.cs +++ b/src/Build.UnitTests/Definition/ToolsetConfigurationReaderTestHelper.cs @@ -4,6 +4,7 @@ using System; using System.Configuration; using System.IO; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; #pragma warning disable 436 diff --git a/src/Build.UnitTests/EscapingInProjects_Tests.cs b/src/Build.UnitTests/EscapingInProjects_Tests.cs index d517bc50c07..90095b266e0 100644 --- a/src/Build.UnitTests/EscapingInProjects_Tests.cs +++ b/src/Build.UnitTests/EscapingInProjects_Tests.cs @@ -18,7 +18,6 @@ #if FEATURE_COMPILE_IN_TESTS using EscapingUtilities = Microsoft.Build.Shared.EscapingUtilities; #endif -using FileUtilities = Microsoft.Build.Shared.FileUtilities; using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException; using ResourceUtilities = Microsoft.Build.Shared.ResourceUtilities; using Shouldly; diff --git a/src/Build.UnitTests/Evaluation/Expander_Tests.cs b/src/Build.UnitTests/Evaluation/Expander_Tests.cs index a7eaef54016..d3261771808 100644 --- a/src/Build.UnitTests/Evaluation/Expander_Tests.cs +++ b/src/Build.UnitTests/Evaluation/Expander_Tests.cs @@ -3455,7 +3455,7 @@ public void PropertyFunctionStaticMethodDirectoryNameOfFileAbove() string result = expander.ExpandIntoStringAndUnescape(@"$([MSBuild]::GetDirectoryNameOfFileAbove($(StartingDirectory), $(FileToFind)))", ExpanderOptions.ExpandProperties, MockElementLocation.Instance); - Assert.Equal(FrameworkFileUtilities.EnsureTrailingSlash(tempPath), FrameworkFileUtilities.EnsureTrailingSlash(result)); + Assert.Equal(FileUtilities.EnsureTrailingSlash(tempPath), FileUtilities.EnsureTrailingSlash(result)); result = expander.ExpandIntoStringAndUnescape(@"$([MSBuild]::GetDirectoryNameOfFileAbove($(StartingDirectory), Hobbits))", ExpanderOptions.ExpandProperties, MockElementLocation.Instance); diff --git a/src/Build.UnitTests/Evaluation/ImportFromMSBuildExtensionsPath_Tests.cs b/src/Build.UnitTests/Evaluation/ImportFromMSBuildExtensionsPath_Tests.cs index 53ca81fcccb..421611fef76 100644 --- a/src/Build.UnitTests/Evaluation/ImportFromMSBuildExtensionsPath_Tests.cs +++ b/src/Build.UnitTests/Evaluation/ImportFromMSBuildExtensionsPath_Tests.cs @@ -8,6 +8,7 @@ using Microsoft.Build.Evaluation; using Microsoft.Build.Exceptions; using Microsoft.Build.Execution; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Xunit; diff --git a/src/Build.UnitTests/Evaluation/ItemEvaluation_Tests.cs b/src/Build.UnitTests/Evaluation/ItemEvaluation_Tests.cs index 64bd31cb639..7e7ee0638a9 100644 --- a/src/Build.UnitTests/Evaluation/ItemEvaluation_Tests.cs +++ b/src/Build.UnitTests/Evaluation/ItemEvaluation_Tests.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using Microsoft.Build.Evaluation; +using Microsoft.Build.Framework; using Microsoft.Build.Internal; using Microsoft.Build.Shared; diff --git a/src/Build.UnitTests/ExpressionTree_Tests.cs b/src/Build.UnitTests/ExpressionTree_Tests.cs index 1c730e8d38d..36aaaf341e6 100644 --- a/src/Build.UnitTests/ExpressionTree_Tests.cs +++ b/src/Build.UnitTests/ExpressionTree_Tests.cs @@ -9,12 +9,11 @@ using Microsoft.Build.Evaluation; using Microsoft.Build.Exceptions; using Microsoft.Build.Execution; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; using Xunit; - - #nullable disable namespace Microsoft.Build.UnitTests diff --git a/src/Build.UnitTests/Globbing/MSBuildGlob_Tests.cs b/src/Build.UnitTests/Globbing/MSBuildGlob_Tests.cs index d6295254e32..9a18627d008 100644 --- a/src/Build.UnitTests/Globbing/MSBuildGlob_Tests.cs +++ b/src/Build.UnitTests/Globbing/MSBuildGlob_Tests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Microsoft.Build.Framework; using Microsoft.Build.Globbing; using Microsoft.Build.Shared; using Xunit; diff --git a/src/Build/BackEnd/BuildManager/BuildRequestData.cs b/src/Build/BackEnd/BuildManager/BuildRequestData.cs index ab64b422770..4a7d5b3d38d 100644 --- a/src/Build/BackEnd/BuildManager/BuildRequestData.cs +++ b/src/Build/BackEnd/BuildManager/BuildRequestData.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Microsoft.Build.Collections; using Microsoft.Build.Experimental.BuildCheck; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; namespace Microsoft.Build.Execution diff --git a/src/Build/BackEnd/BuildManager/CacheSerialization.cs b/src/Build/BackEnd/BuildManager/CacheSerialization.cs index 1b6b2635c71..81a59ee77c9 100644 --- a/src/Build/BackEnd/BuildManager/CacheSerialization.cs +++ b/src/Build/BackEnd/BuildManager/CacheSerialization.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using Microsoft.Build.BackEnd; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Build/BackEnd/Components/Communications/CurrentHost.cs b/src/Build/BackEnd/Components/Communications/CurrentHost.cs index aa54232dc75..463203d6dfe 100644 --- a/src/Build/BackEnd/Components/Communications/CurrentHost.cs +++ b/src/Build/BackEnd/Components/Communications/CurrentHost.cs @@ -3,6 +3,7 @@ #if RUNTIME_TYPE_NETCORE using System.IO; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; using Constants = Microsoft.Build.Framework.Constants; diff --git a/src/Build/BackEnd/Components/FileAccesses/FileAccessManager.cs b/src/Build/BackEnd/Components/FileAccesses/FileAccessManager.cs index d45e5bd870f..6a293ba21cf 100644 --- a/src/Build/BackEnd/Components/FileAccesses/FileAccessManager.cs +++ b/src/Build/BackEnd/Components/FileAccesses/FileAccessManager.cs @@ -45,7 +45,7 @@ public void InitializeComponent(IBuildComponentHost host) { _scheduler = host.GetComponent(BuildComponentType.Scheduler) as IScheduler; _configCache = host.GetComponent(BuildComponentType.ConfigCache) as IConfigCache; - _tempDirectory = FrameworkFileUtilities.EnsureNoTrailingSlash(FileUtilities.TempFileDirectory); + _tempDirectory = FileUtilities.EnsureNoTrailingSlash(FileUtilities.TempFileDirectory); } public void ShutdownComponent() diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetUpToDateChecker.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetUpToDateChecker.cs index 56094d8f078..16afcef0c2e 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TargetUpToDateChecker.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TargetUpToDateChecker.cs @@ -978,7 +978,7 @@ internal static bool IsAnyOutOfDate(out DependencyAnalysisLogDetail dependenc // possibly the outputs list isn't actually the shortest list. However it always is the shortest // in the cases I've seen, and adding this optimization would make the code hard to read. - string oldestOutput = EscapingUtilities.UnescapeAll(FrameworkFileUtilities.FixFilePath(outputs[0].ToString())); + string oldestOutput = EscapingUtilities.UnescapeAll(FileUtilities.FixFilePath(outputs[0].ToString())); ErrorUtilities.ThrowIfTypeDoesNotImplementToString(outputs[0]); DateTime oldestOutputFileTime = DateTime.MinValue; @@ -996,7 +996,7 @@ internal static bool IsAnyOutOfDate(out DependencyAnalysisLogDetail dependenc if (oldestOutputFileTime == DateTime.MinValue) { // First output is missing: we must build the target - string arbitraryInput = EscapingUtilities.UnescapeAll(FrameworkFileUtilities.FixFilePath(inputs[0].ToString())); + string arbitraryInput = EscapingUtilities.UnescapeAll(FileUtilities.FixFilePath(inputs[0].ToString())); ErrorUtilities.ThrowIfTypeDoesNotImplementToString(inputs[0]); dependencyAnalysisDetailEntry = new DependencyAnalysisLogDetail(arbitraryInput, oldestOutput, null, null, OutofdateReason.MissingOutput); return true; @@ -1004,7 +1004,7 @@ internal static bool IsAnyOutOfDate(out DependencyAnalysisLogDetail dependenc for (int i = 1; i < outputs.Count; i++) { - string candidateOutput = EscapingUtilities.UnescapeAll(FrameworkFileUtilities.FixFilePath(outputs[i].ToString())); + string candidateOutput = EscapingUtilities.UnescapeAll(FileUtilities.FixFilePath(outputs[i].ToString())); ErrorUtilities.ThrowIfTypeDoesNotImplementToString(outputs[i]); DateTime candidateOutputFileTime = DateTime.MinValue; try @@ -1022,7 +1022,7 @@ internal static bool IsAnyOutOfDate(out DependencyAnalysisLogDetail dependenc { // An output is missing: we must build the target string arbitraryInput = - EscapingUtilities.UnescapeAll(FrameworkFileUtilities.FixFilePath(inputs[0].ToString())); + EscapingUtilities.UnescapeAll(FileUtilities.FixFilePath(inputs[0].ToString())); ErrorUtilities.ThrowIfTypeDoesNotImplementToString(inputs[0]); dependencyAnalysisDetailEntry = new DependencyAnalysisLogDetail(arbitraryInput, candidateOutput, null, null, OutofdateReason.MissingOutput); return true; @@ -1039,7 +1039,7 @@ internal static bool IsAnyOutOfDate(out DependencyAnalysisLogDetail dependenc // Now compare the oldest output with each input and break out if we find one newer. foreach (T input in inputs) { - string unescapedInput = EscapingUtilities.UnescapeAll(FrameworkFileUtilities.FixFilePath(input.ToString())); + string unescapedInput = EscapingUtilities.UnescapeAll(FileUtilities.FixFilePath(input.ToString())); ErrorUtilities.ThrowIfTypeDoesNotImplementToString(input); DateTime inputFileTime = DateTime.MaxValue; try @@ -1127,8 +1127,8 @@ private void RecordUniqueInputsAndOutputs(IList inputs, IList outputs) /// true, if "input" is newer than "output" private bool IsOutOfDate(string input, string output, string inputItemName, string outputItemName) { - input = EscapingUtilities.UnescapeAll(FrameworkFileUtilities.FixFilePath(input)); - output = EscapingUtilities.UnescapeAll(FrameworkFileUtilities.FixFilePath(output)); + input = EscapingUtilities.UnescapeAll(FileUtilities.FixFilePath(input)); + output = EscapingUtilities.UnescapeAll(FileUtilities.FixFilePath(output)); ProjectErrorUtilities.VerifyThrowInvalidProject(input.AsSpan().IndexOfAny(MSBuildConstants.InvalidPathChars) < 0, _project.ProjectFileLocation, "IllegalCharactersInFileOrDirectory", input, inputItemName); ProjectErrorUtilities.VerifyThrowInvalidProject(output.AsSpan().IndexOfAny(MSBuildConstants.InvalidPathChars) < 0, _project.ProjectFileLocation, "IllegalCharactersInFileOrDirectory", output, outputItemName); bool outOfDate = (CompareLastWriteTimes(input, output, out bool inputDoesNotExist, out bool outputDoesNotExist) == 1) || inputDoesNotExist; diff --git a/src/Build/BackEnd/Components/SdkResolution/SdkResolverManifest.cs b/src/Build/BackEnd/Components/SdkResolution/SdkResolverManifest.cs index fc01468bac7..fbd98e38777 100644 --- a/src/Build/BackEnd/Components/SdkResolution/SdkResolverManifest.cs +++ b/src/Build/BackEnd/Components/SdkResolution/SdkResolverManifest.cs @@ -80,7 +80,7 @@ internal static SdkResolverManifest Load(string filePath, string manifestFolder) { SdkResolverManifest manifest = ParseSdkResolverElement(reader, filePath); - manifest.Path = FrameworkFileUtilities.FixFilePath(manifest.Path); + manifest.Path = FileUtilities.FixFilePath(manifest.Path); if (!System.IO.Path.IsPathRooted(manifest.Path)) { manifest.Path = System.IO.Path.Combine(manifestFolder, manifest.Path); diff --git a/src/Build/BackEnd/Shared/ConfigurationMetadata.cs b/src/Build/BackEnd/Shared/ConfigurationMetadata.cs index a08d818d4a6..1e54edafa33 100644 --- a/src/Build/BackEnd/Shared/ConfigurationMetadata.cs +++ b/src/Build/BackEnd/Shared/ConfigurationMetadata.cs @@ -7,6 +7,7 @@ using Microsoft.Build.Collections; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs b/src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs index db7bc2413e5..e46edd1aa1d 100644 --- a/src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs +++ b/src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs @@ -63,7 +63,7 @@ public AbsolutePath ProjectDirectory _currentDirectory = value.GetCanonicalForm(); // Keep the thread-static in sync for use by Expander and Modifiers during property/item expansion. // This allows Path.GetFullPath and %(FullPath) functions used in project files to resolve relative paths correctly in multithreaded mode. - FrameworkFileUtilities.CurrentThreadWorkingDirectory = _currentDirectory.Value; + FileUtilities.CurrentThreadWorkingDirectory = _currentDirectory.Value; } } @@ -130,7 +130,7 @@ public ProcessStartInfo GetProcessStartInfo() public void Dispose() { // Clear the thread-static to prevent pollution between builds on the same thread. - FrameworkFileUtilities.CurrentThreadWorkingDirectory = null; + FileUtilities.CurrentThreadWorkingDirectory = null; } } } diff --git a/src/Build/BuildCheck/Checks/UntrustedLocationCheck.cs b/src/Build/BuildCheck/Checks/UntrustedLocationCheck.cs index 47c8ee510dc..851b26ece37 100644 --- a/src/Build/BuildCheck/Checks/UntrustedLocationCheck.cs +++ b/src/Build/BuildCheck/Checks/UntrustedLocationCheck.cs @@ -6,6 +6,7 @@ using System.IO; using System.Runtime.InteropServices; using Microsoft.Build.Construction; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; @@ -40,7 +41,7 @@ public override void RegisterActions(IBuildCheckRegistrationContext registration private void EvaluatedPropertiesAction(BuildCheckDataContext context) { if (checkedProjects.Add(context.Data.ProjectFilePath) && - context.Data.ProjectFileDirectory.StartsWith(PathsHelper.Downloads, Shared.FileUtilities.PathComparison)) + context.Data.ProjectFileDirectory.StartsWith(PathsHelper.Downloads, FileUtilities.PathComparison)) { context.ReportResult(BuildCheckResult.Create( SupportedRule, diff --git a/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigParser.cs b/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigParser.cs index 944e03113a8..1fa7752f9a7 100644 --- a/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigParser.cs +++ b/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigParser.cs @@ -5,7 +5,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; -using Microsoft.Build.Shared; +using Microsoft.Build.Framework; using Microsoft.Build.Shared.FileSystem; using static Microsoft.Build.Experimental.BuildCheck.Infrastructure.EditorConfig.EditorConfigGlobsMatcher; @@ -45,7 +45,7 @@ internal List DiscoverEditorConfigFiles(string filePath) { var editorConfigDataFromFilesList = new List(); - var directoryOfTheProject = Path.GetDirectoryName(filePath); + var directoryOfTheProject = Path.GetDirectoryName(filePath)!; // The method will look for the file in parent directory if not found in current until found or the directory is root. var editorConfigFilePath = FileUtilities.GetPathOfFileAbove(EditorconfigFile, directoryOfTheProject); while (editorConfigFilePath != string.Empty) @@ -64,7 +64,7 @@ internal List DiscoverEditorConfigFiles(string filePath) else { // search in upper directory - editorConfigFilePath = FileUtilities.GetPathOfFileAbove(EditorconfigFile, Path.GetDirectoryName(Path.GetDirectoryName(editorConfigFilePath))); + editorConfigFilePath = FileUtilities.GetPathOfFileAbove(EditorconfigFile, Path.GetDirectoryName(Path.GetDirectoryName(editorConfigFilePath))!); } } diff --git a/src/Build/BuildCheck/Utilities/BuildCheckUtilities.cs b/src/Build/BuildCheck/Utilities/BuildCheckUtilities.cs index 01461b7c9ef..1b7b377d84e 100644 --- a/src/Build/BuildCheck/Utilities/BuildCheckUtilities.cs +++ b/src/Build/BuildCheck/Utilities/BuildCheckUtilities.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.IO; -using Microsoft.Build.Shared; +using Microsoft.Build.Framework; namespace Microsoft.Build.Experimental.BuildCheck; diff --git a/src/Build/Construction/ProjectImportElement.cs b/src/Build/Construction/ProjectImportElement.cs index 852ab8e7d05..6bbc04a9e7d 100644 --- a/src/Build/Construction/ProjectImportElement.cs +++ b/src/Build/Construction/ProjectImportElement.cs @@ -52,7 +52,7 @@ internal ProjectImportElement(XmlElementWithLocation xmlElement, ProjectRootElem /// public string Project { - get => FrameworkFileUtilities.FixFilePath(GetAttributeValue(XMakeAttributes.project)); + get => FileUtilities.FixFilePath(GetAttributeValue(XMakeAttributes.project)); set { ErrorUtilities.VerifyThrowArgumentLength(value, XMakeAttributes.project); @@ -71,7 +71,7 @@ public string Project /// public string Sdk { - get => FrameworkFileUtilities.FixFilePath(GetAttributeValue(XMakeAttributes.sdk)); + get => FileUtilities.FixFilePath(GetAttributeValue(XMakeAttributes.sdk)); set { ErrorUtilities.VerifyThrowArgumentLength(value, XMakeAttributes.sdk); diff --git a/src/Build/Construction/ProjectRootElement.cs b/src/Build/Construction/ProjectRootElement.cs index 83a084eb685..57366d1349b 100644 --- a/src/Build/Construction/ProjectRootElement.cs +++ b/src/Build/Construction/ProjectRootElement.cs @@ -1256,7 +1256,7 @@ public ProjectTargetElement AddTarget(string name) /// public ProjectUsingTaskElement AddUsingTask(string name, string assemblyFile, string assemblyName) { - ProjectUsingTaskElement usingTask = CreateUsingTaskElement(name, FrameworkFileUtilities.FixFilePath(assemblyFile), assemblyName); + ProjectUsingTaskElement usingTask = CreateUsingTaskElement(name, FileUtilities.FixFilePath(assemblyFile), assemblyName); AppendChild(usingTask); return usingTask; diff --git a/src/Build/Construction/ProjectUsingTaskElement.cs b/src/Build/Construction/ProjectUsingTaskElement.cs index cecae44497d..9eb1534256f 100644 --- a/src/Build/Construction/ProjectUsingTaskElement.cs +++ b/src/Build/Construction/ProjectUsingTaskElement.cs @@ -48,14 +48,14 @@ private ProjectUsingTaskElement(XmlElementWithLocation xmlElement, ProjectRootEl /// public string AssemblyFile { - get => FrameworkFileUtilities.FixFilePath( + get => FileUtilities.FixFilePath( GetAttributeValue(XMakeAttributes.assemblyFile)); set { ErrorUtilities.VerifyThrowArgumentLength(value, XMakeAttributes.assemblyName); ErrorUtilities.VerifyThrowInvalidOperation(String.IsNullOrEmpty(AssemblyName), "OM_EitherAttributeButNotBoth", ElementName, XMakeAttributes.assemblyFile, XMakeAttributes.assemblyName); - value = FrameworkFileUtilities.FixFilePath(value); + value = FileUtilities.FixFilePath(value); SetOrRemoveAttribute(XMakeAttributes.assemblyFile, value, "Set usingtask AssemblyFile {0}", value); } } @@ -249,7 +249,7 @@ internal static ProjectUsingTaskElement CreateDisconnected(string taskName, stri if (!String.IsNullOrEmpty(assemblyFile)) { - usingTask.AssemblyFile = FrameworkFileUtilities.FixFilePath(assemblyFile); + usingTask.AssemblyFile = FileUtilities.FixFilePath(assemblyFile); } else { diff --git a/src/Build/Construction/Solution/ProjectInSolution.cs b/src/Build/Construction/Solution/ProjectInSolution.cs index 106b21221ac..fc8ecf2c35b 100644 --- a/src/Build/Construction/Solution/ProjectInSolution.cs +++ b/src/Build/Construction/Solution/ProjectInSolution.cs @@ -14,7 +14,7 @@ using System.Text; #else using System.Buffers; -using Microsoft.Build.Shared; +using Microsoft.Build.Framework; #endif using XMakeAttributes = Microsoft.Build.Shared.XMakeAttributes; diff --git a/src/Build/Construction/Solution/SolutionProjectGenerator.cs b/src/Build/Construction/Solution/SolutionProjectGenerator.cs index 8f3735ba170..833ee5b2867 100644 --- a/src/Build/Construction/Solution/SolutionProjectGenerator.cs +++ b/src/Build/Construction/Solution/SolutionProjectGenerator.cs @@ -1308,7 +1308,7 @@ private string GetMetaprojectName(ProjectInSolution project) baseName = project.ProjectName; } - baseName = FrameworkFileUtilities.EnsureNoTrailingSlash(baseName); + baseName = FileUtilities.EnsureNoTrailingSlash(baseName); return GetMetaprojectName(baseName); } diff --git a/src/Build/Definition/Toolset.cs b/src/Build/Definition/Toolset.cs index 45f5863e894..cbba8dbbd56 100644 --- a/src/Build/Definition/Toolset.cs +++ b/src/Build/Definition/Toolset.cs @@ -367,7 +367,7 @@ private set // technically hurt anything, but it doesn't look nice.) string toolsPathToUse = value; - if (FrameworkFileUtilities.EndsWithSlash(toolsPathToUse)) + if (FileUtilities.EndsWithSlash(toolsPathToUse)) { string rootPath = Path.GetPathRoot(Path.GetFullPath(toolsPathToUse)); diff --git a/src/Build/Errors/InvalidProjectFileException.cs b/src/Build/Errors/InvalidProjectFileException.cs index 8d063ba4a38..8878b742d9c 100644 --- a/src/Build/Errors/InvalidProjectFileException.cs +++ b/src/Build/Errors/InvalidProjectFileException.cs @@ -9,6 +9,7 @@ #endif using Microsoft.Build.Framework.BuildException; using Microsoft.Build.Shared; +using Microsoft.Build.Framework; #nullable disable diff --git a/src/Build/Evaluation/Conditionals/FunctionCallExpressionNode.cs b/src/Build/Evaluation/Conditionals/FunctionCallExpressionNode.cs index a28b263a3e6..e819fef644d 100644 --- a/src/Build/Evaluation/Conditionals/FunctionCallExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/FunctionCallExpressionNode.cs @@ -120,7 +120,7 @@ private static string ExpandArgumentForScalarParameter(string function, GenericE // Fix path before expansion if (isFilePath) { - argument = FrameworkFileUtilities.FixFilePath(argument); + argument = FileUtilities.FixFilePath(argument); } IList items = state.ExpandIntoTaskItems(argument); @@ -154,7 +154,7 @@ private List ExpandArgumentAsFileList(GenericExpressionNode argumentNode // Fix path before expansion if (isFilePath) { - argument = FrameworkFileUtilities.FixFilePath(argument); + argument = FileUtilities.FixFilePath(argument); } IList expanded = state.ExpandIntoTaskItems(argument); diff --git a/src/Build/Evaluation/Expander.cs b/src/Build/Evaluation/Expander.cs index 1417a02d2ae..3f6c0228f55 100644 --- a/src/Build/Evaluation/Expander.cs +++ b/src/Build/Evaluation/Expander.cs @@ -1713,7 +1713,7 @@ private static object ExpandMSBuildThisFileProperty(string propertyName, IElemen } else if (String.Equals(propertyName, ReservedPropertyNames.thisFileDirectory, StringComparison.OrdinalIgnoreCase)) { - value = FrameworkFileUtilities.EnsureTrailingSlash(Path.GetDirectoryName(elementLocation.File)); + value = FileUtilities.EnsureTrailingSlash(Path.GetDirectoryName(elementLocation.File)); } else if (String.Equals(propertyName, ReservedPropertyNames.thisFileDirectoryNoRoot, StringComparison.OrdinalIgnoreCase)) { @@ -2552,7 +2552,7 @@ internal static void ItemSpecModifierFunction(IElementLocation elementLocation, // 1. in multiprocess mode we're safe to get the current directory as we'll be running on TaskItems which // only exist within a target where we can trust the current directory // 2. in single process mode we get the project directory set for the thread - string directoryToUse = item.Value.ProjectDirectory ?? FrameworkFileUtilities.CurrentThreadWorkingDirectory ?? Directory.GetCurrentDirectory(); + string directoryToUse = item.Value.ProjectDirectory ?? FileUtilities.CurrentThreadWorkingDirectory ?? Directory.GetCurrentDirectory(); string definingProjectEscaped = item.Value.GetMetadataValueEscaped(ItemSpecModifiers.DefiningProjectFullPath); result = ItemSpecModifiers.GetItemSpecModifier(directoryToUse, item.Key, definingProjectEscaped, functionName); @@ -2610,7 +2610,7 @@ internal static void Exists(IElementLocation elementLocation, string functionNam // 1. in multiprocess mode we're safe to get the current directory as we'll be running on TaskItems which // only exist within a target where we can trust the current directory // 2. in single process mode we get the project directory set for the thread - string baseDirectoryToUse = item.Value.ProjectDirectory ?? FrameworkFileUtilities.CurrentThreadWorkingDirectory ?? String.Empty; + string baseDirectoryToUse = item.Value.ProjectDirectory ?? FileUtilities.CurrentThreadWorkingDirectory ?? String.Empty; rootedPath = Path.Combine(baseDirectoryToUse, unescapedPath); } } @@ -2690,7 +2690,7 @@ internal static void GetPathsOfAllDirectoriesAbove(IElementLocation elementLocat // 1. in multiprocess mode we're safe to get the current directory as we'll be running on TaskItems which // only exist within a target where we can trust the current directory // 2. in single process mode we get the project directory set for the thread - string baseDirectoryToUse = item.Value.ProjectDirectory ?? FrameworkFileUtilities.CurrentThreadWorkingDirectory ?? String.Empty; + string baseDirectoryToUse = item.Value.ProjectDirectory ?? FileUtilities.CurrentThreadWorkingDirectory ?? String.Empty; rootedPath = Path.Combine(baseDirectoryToUse, unescapedPath); } @@ -2769,7 +2769,7 @@ internal static void DirectoryName(IElementLocation elementLocation, bool includ // 1. in multiprocess mode we're safe to get the current directory as we'll be running on TaskItems which // only exist within a target where we can trust the current directory // 2. in single process mode we get the project directory set for the thread - string baseDirectoryToUse = item.Value.ProjectDirectory ?? FrameworkFileUtilities.CurrentThreadWorkingDirectory ?? String.Empty; + string baseDirectoryToUse = item.Value.ProjectDirectory ?? FileUtilities.CurrentThreadWorkingDirectory ?? String.Empty; rootedPath = Path.Combine(baseDirectoryToUse, unescapedPath); } @@ -3320,7 +3320,7 @@ private static string GetMetadataValueFromMatch( // 1. in multiprocess mode we're safe to get the current directory as we'll be running on TaskItems which // only exist within a target where we can trust the current directory // 2. in single process mode we get the project directory set for the thread - string directoryToUse = sourceOfMetadata.ProjectDirectory ?? FrameworkFileUtilities.CurrentThreadWorkingDirectory ?? Directory.GetCurrentDirectory(); + string directoryToUse = sourceOfMetadata.ProjectDirectory ?? FileUtilities.CurrentThreadWorkingDirectory ?? Directory.GetCurrentDirectory(); string definingProjectEscaped = sourceOfMetadata.GetMetadataValueEscaped(ItemSpecModifiers.DefiningProjectFullPath); value = ItemSpecModifiers.GetItemSpecModifier(directoryToUse, itemSpec, definingProjectEscaped, match.Name); @@ -4008,7 +4008,7 @@ internal object Execute(object objectInstance, IPropertyProvider properties, if (_receiverType == typeof(File) || _receiverType == typeof(Directory) || _receiverType == typeof(Path)) { - argumentValue = FrameworkFileUtilities.FixFilePath(argumentValue); + argumentValue = FileUtilities.FixFilePath(argumentValue); } args[n] = EscapingUtilities.UnescapeAll(argumentValue); diff --git a/src/Build/Evaluation/Expander/WellKnownFunctions.cs b/src/Build/Evaluation/Expander/WellKnownFunctions.cs index 497b2eef1a9..7fb9c62b886 100644 --- a/src/Build/Evaluation/Expander/WellKnownFunctions.cs +++ b/src/Build/Evaluation/Expander/WellKnownFunctions.cs @@ -105,8 +105,8 @@ internal static bool TryExecutePathFunction(string methodName, out object? retur { if (ParseArgs.TryGetArg(args, out string? arg0) && arg0 != null) { - returnVal = !string.IsNullOrEmpty(FrameworkFileUtilities.CurrentThreadWorkingDirectory) - ? Path.GetFullPath(Path.Combine(FrameworkFileUtilities.CurrentThreadWorkingDirectory, arg0)) + returnVal = !string.IsNullOrEmpty(FileUtilities.CurrentThreadWorkingDirectory) + ? Path.GetFullPath(Path.Combine(FileUtilities.CurrentThreadWorkingDirectory, arg0)) : Path.GetFullPath(arg0); return true; } diff --git a/src/Build/Evaluation/IntrinsicFunctions.cs b/src/Build/Evaluation/IntrinsicFunctions.cs index df3783f5ba9..5393497e564 100644 --- a/src/Build/Evaluation/IntrinsicFunctions.cs +++ b/src/Build/Evaluation/IntrinsicFunctions.cs @@ -525,7 +525,7 @@ internal static bool DoesTaskHostExist(string runtime, string architecture) /// The specified path with a trailing slash. internal static string EnsureTrailingSlash(string path) { - return FrameworkFileUtilities.EnsureTrailingSlash(path); + return FileUtilities.EnsureTrailingSlash(path); } /// diff --git a/src/Build/Evaluation/ItemSpec.cs b/src/Build/Evaluation/ItemSpec.cs index ea5bbfd0330..c894a8b5f87 100644 --- a/src/Build/Evaluation/ItemSpec.cs +++ b/src/Build/Evaluation/ItemSpec.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Build.Framework; using Microsoft.Build.Globbing; using Microsoft.Build.Internal; using Microsoft.Build.Shared; diff --git a/src/Build/Evaluation/LazyItemEvaluator.LazyItemOperation.cs b/src/Build/Evaluation/LazyItemEvaluator.LazyItemOperation.cs index 78909e1f660..7ef2740b5b3 100644 --- a/src/Build/Evaluation/LazyItemEvaluator.LazyItemOperation.cs +++ b/src/Build/Evaluation/LazyItemEvaluator.LazyItemOperation.cs @@ -8,6 +8,7 @@ using System.Linq; using Microsoft.Build.Construction; using Microsoft.Build.Eventing; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Build/Evaluation/ProjectRootElementCache.cs b/src/Build/Evaluation/ProjectRootElementCache.cs index c97ebad4bff..92c36003026 100644 --- a/src/Build/Evaluation/ProjectRootElementCache.cs +++ b/src/Build/Evaluation/ProjectRootElementCache.cs @@ -12,7 +12,6 @@ using Microsoft.Build.Construction; using Microsoft.Build.Framework; using Microsoft.Build.Internal; -using Microsoft.Build.Shared; using ErrorUtilities = Microsoft.Build.Shared.ErrorUtilities; using OutOfProcNode = Microsoft.Build.Execution.OutOfProcNode; diff --git a/src/Build/Globbing/MSBuildGlob.cs b/src/Build/Globbing/MSBuildGlob.cs index 38915e44cd3..930a16f9eaf 100644 --- a/src/Build/Globbing/MSBuildGlob.cs +++ b/src/Build/Globbing/MSBuildGlob.cs @@ -6,6 +6,7 @@ using System.IO; using System.Text.RegularExpressions; using Microsoft.Build.Collections; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.NET.StringTools; diff --git a/src/Build/Graph/GraphBuilder.cs b/src/Build/Graph/GraphBuilder.cs index 3a30af61030..35ecbb5122a 100644 --- a/src/Build/Graph/GraphBuilder.cs +++ b/src/Build/Graph/GraphBuilder.cs @@ -15,6 +15,7 @@ using Microsoft.Build.Evaluation; using Microsoft.Build.Exceptions; using Microsoft.Build.Execution; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Build/Graph/ProjectGraphEntryPoint.cs b/src/Build/Graph/ProjectGraphEntryPoint.cs index cb5ee4d3c30..fc392d02987 100644 --- a/src/Build/Graph/ProjectGraphEntryPoint.cs +++ b/src/Build/Graph/ProjectGraphEntryPoint.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Build/Graph/ProjectGraphNode.cs b/src/Build/Graph/ProjectGraphNode.cs index 022b63737c1..6028f9ab8a7 100644 --- a/src/Build/Graph/ProjectGraphNode.cs +++ b/src/Build/Graph/ProjectGraphNode.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using Microsoft.Build.BackEnd; using Microsoft.Build.Execution; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; namespace Microsoft.Build.Graph diff --git a/src/Build/Instance/ProjectItemInstance.cs b/src/Build/Instance/ProjectItemInstance.cs index 9ad5c055209..b8a70ceac25 100644 --- a/src/Build/Instance/ProjectItemInstance.cs +++ b/src/Build/Instance/ProjectItemInstance.cs @@ -866,8 +866,8 @@ internal TaskItem( ErrorUtilities.VerifyThrowArgumentLength(includeEscaped); ErrorUtilities.VerifyThrowArgumentLength(includeBeforeWildcardExpansionEscaped); - _includeEscaped = FrameworkFileUtilities.FixFilePath(includeEscaped); - _includeBeforeWildcardExpansionEscaped = FrameworkFileUtilities.FixFilePath(includeBeforeWildcardExpansionEscaped); + _includeEscaped = FileUtilities.FixFilePath(includeEscaped); + _includeBeforeWildcardExpansionEscaped = FileUtilities.FixFilePath(includeBeforeWildcardExpansionEscaped); _directMetadata = (directMetadata == null || directMetadata.Count == 0) ? null : directMetadata; // If the metadata was all removed, toss the dictionary _itemDefinitions = itemDefinitions; _projectDirectory = projectDirectory; diff --git a/src/Build/Instance/TaskRegistry.cs b/src/Build/Instance/TaskRegistry.cs index ccdc34b700f..24d15831948 100644 --- a/src/Build/Instance/TaskRegistry.cs +++ b/src/Build/Instance/TaskRegistry.cs @@ -353,7 +353,7 @@ private static void RegisterTasksFromUsingTaskElement // don't want paths from imported projects being interpreted relative to the main project file. try { - assemblyFile = FrameworkFileUtilities.FixFilePath(assemblyFile); + assemblyFile = FileUtilities.FixFilePath(assemblyFile); if (assemblyFile != null && !Path.IsPathRooted(assemblyFile)) { diff --git a/src/Build/Logging/FileLogger.cs b/src/Build/Logging/FileLogger.cs index 60fca6f6868..a2d306a1341 100644 --- a/src/Build/Logging/FileLogger.cs +++ b/src/Build/Logging/FileLogger.cs @@ -198,7 +198,7 @@ private void ApplyFileLoggerParameter(string parameterName, string parameterValu switch (parameterName.ToUpperInvariant()) { case "LOGFILE": - _logFileName = FrameworkFileUtilities.FixFilePath(parameterValue); + _logFileName = FileUtilities.FixFilePath(parameterValue); break; case "APPEND": _append = true; diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 78b911686f9..ea9699329e0 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -667,10 +667,6 @@ SharedUtilities\FileMatcher.cs - - SharedUtilities\FileUtilities.cs - - SharedUtilities\FrameworkLocationHelper.cs diff --git a/src/Build/Utilities/FileSpecMatchTester.cs b/src/Build/Utilities/FileSpecMatchTester.cs index 76b19b3d1a4..8c5afe9c08d 100644 --- a/src/Build/Utilities/FileSpecMatchTester.cs +++ b/src/Build/Utilities/FileSpecMatchTester.cs @@ -132,7 +132,7 @@ private static void CreateRegexOrFilenamePattern(string unescapedFileSpec, strin ? Directory.GetCurrentDirectory() : FileUtilities.GetFullPathNoThrow(absoluteFixedDirPart); - normalizedFixedDirPart = FrameworkFileUtilities.EnsureTrailingSlash(normalizedFixedDirPart); + normalizedFixedDirPart = FileUtilities.EnsureTrailingSlash(normalizedFixedDirPart); var recombinedFileSpec = string.Concat(normalizedFixedDirPart, wildcardDirectoryPart, filenamePart); diff --git a/src/Framework/FileSystem/WindowsFileSystem.cs b/src/Framework/FileSystem/WindowsFileSystem.cs index 27de39f3a8c..c215adc4733 100644 --- a/src/Framework/FileSystem/WindowsFileSystem.cs +++ b/src/Framework/FileSystem/WindowsFileSystem.cs @@ -55,7 +55,7 @@ public override IEnumerable EnumerateFileSystemEntries(string path, stri public override bool DirectoryExists(string path) { - if (!string.IsNullOrEmpty(path) && FrameworkFileUtilities.IsPathTooLong(path)) + if (!string.IsNullOrEmpty(path) && FileUtilities.IsPathTooLong(path)) { // If the path is too long, we can't check if it exists on windows throw new PathTooLongException(SR.FormatPathTooLong(path, NativeMethods.MaxPath)); diff --git a/src/Framework/FileUtilities.cs b/src/Framework/FileUtilities.cs index c81e6499103..95b4a042e23 100644 --- a/src/Framework/FileUtilities.cs +++ b/src/Framework/FileUtilities.cs @@ -28,14 +28,11 @@ 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 partial class FrameworkFileUtilities + internal static partial class FileUtilities { private const char UnixDirectorySeparator = '/'; private const char WindowsDirectorySeparator = '\\'; @@ -1457,7 +1454,7 @@ internal static string CombinePaths(string root, params string[] paths) return paths.Aggregate(root, Path.Combine); } - internal static string TrimTrailingSlashes(string s) + internal static string TrimTrailingSlashes(this string s) { return s.TrimEnd(Slashes); } @@ -1465,12 +1462,12 @@ internal static string TrimTrailingSlashes(string s) /// /// Replace all backward slashes to forward slashes /// - internal static string ToSlash(string s) + internal static string ToSlash(this string s) { return s.Replace('\\', '/'); } - internal static string ToBackslash(string s) + internal static string ToBackslash(this string s) { return s.Replace('/', '\\'); } @@ -1480,20 +1477,20 @@ internal static string ToBackslash(string s) /// /// /// - internal static string ToPlatformSlash(string s) + internal static string ToPlatformSlash(this string s) { var separator = Path.DirectorySeparatorChar; return s.Replace(separator == '/' ? '\\' : '/', separator); } - internal static string WithTrailingSlash(string s) + internal static string WithTrailingSlash(this string s) { return EnsureTrailingSlash(s); } - internal static string NormalizeForPathComparison(string s) - => TrimTrailingSlashes(ToPlatformSlash(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) @@ -1710,7 +1707,7 @@ internal static void ClearFileExistenceCache() FileExistenceCache.Clear(); } - internal static void ReadFromStream(Stream stream, byte[] content, int startIndex, int length) + internal static void ReadFromStream(this Stream stream, byte[] content, int startIndex, int length) { stream.ReadExactly(content, startIndex, length); } diff --git a/src/Framework/FileUtilities_TempFiles.cs b/src/Framework/FileUtilities_TempFiles.cs index d0ee572e95d..b00fa5d3a95 100644 --- a/src/Framework/FileUtilities_TempFiles.cs +++ b/src/Framework/FileUtilities_TempFiles.cs @@ -11,7 +11,7 @@ namespace Microsoft.Build.Framework; -internal static partial class FrameworkFileUtilities +internal static partial class FileUtilities { private static Lazy tempFileDirectory = CreateTempFileDirectoryLazy(); diff --git a/src/Framework/ItemSpecModifiers.cs b/src/Framework/ItemSpecModifiers.cs index ce6efe2caac..5a2aa8e0818 100644 --- a/src/Framework/ItemSpecModifiers.cs +++ b/src/Framework/ItemSpecModifiers.cs @@ -177,10 +177,10 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS if (currentDirectory == null) { - currentDirectory = FrameworkFileUtilities.CurrentThreadWorkingDirectory ?? string.Empty; + currentDirectory = FileUtilities.CurrentThreadWorkingDirectory ?? string.Empty; } - modifiedItemSpec = FrameworkFileUtilities.GetFullPath(itemSpec, currentDirectory); + modifiedItemSpec = FileUtilities.GetFullPath(itemSpec, currentDirectory); fullPath = modifiedItemSpec; ThrowForUrl(modifiedItemSpec, itemSpec, currentDirectory); @@ -191,7 +191,7 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS modifiedItemSpec = Path.GetPathRoot(fullPath); - if (!FrameworkFileUtilities.EndsWithSlash(modifiedItemSpec)) + if (!FileUtilities.EndsWithSlash(modifiedItemSpec)) { FrameworkErrorUtilities.VerifyThrow( FileUtilitiesRegex.StartsWithUncPattern(modifiedItemSpec), @@ -214,7 +214,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(FileUtilities.FixFilePath(itemSpec)); } } else if (string.Equals(modifier, Extension, StringComparison.OrdinalIgnoreCase)) @@ -233,13 +233,13 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS } else if (string.Equals(modifier, RelativeDir, StringComparison.OrdinalIgnoreCase)) { - modifiedItemSpec = FrameworkFileUtilities.GetDirectory(itemSpec); + modifiedItemSpec = FileUtilities.GetDirectory(itemSpec); } else if (string.Equals(modifier, Directory, StringComparison.OrdinalIgnoreCase)) { GetItemSpecModifier(currentDirectory, itemSpec, definingProjectEscaped, FullPath, ref fullPath); - modifiedItemSpec = FrameworkFileUtilities.GetDirectory(fullPath); + modifiedItemSpec = FileUtilities.GetDirectory(fullPath); if (NativeMethods.IsWindows) { @@ -255,7 +255,7 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS if (length != -1) { - FrameworkErrorUtilities.VerifyThrow((modifiedItemSpec.Length > length) && FrameworkFileUtilities.IsSlash(modifiedItemSpec[length]), + FrameworkErrorUtilities.VerifyThrow((modifiedItemSpec.Length > length) && FileUtilities.IsSlash(modifiedItemSpec[length]), "Root directory must have a trailing slash."); modifiedItemSpec = modifiedItemSpec.Substring(length + 1); @@ -263,7 +263,7 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS } else { - FrameworkErrorUtilities.VerifyThrow(!string.IsNullOrEmpty(modifiedItemSpec) && FrameworkFileUtilities.IsSlash(modifiedItemSpec[0]), + FrameworkErrorUtilities.VerifyThrow(!string.IsNullOrEmpty(modifiedItemSpec) && FileUtilities.IsSlash(modifiedItemSpec[0]), "Expected a full non-windows path rooted at '/'."); // A full unix path is always rooted at @@ -287,11 +287,11 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS // to unescape first. string unescapedItemSpec = EscapingUtilities.UnescapeAll(itemSpec); - FileInfo info = FrameworkFileUtilities.GetFileInfoNoThrow(unescapedItemSpec); + FileInfo info = FileUtilities.GetFileInfoNoThrow(unescapedItemSpec); if (info != null) { - modifiedItemSpec = info.LastWriteTime.ToString(FrameworkFileUtilities.FileTimeFormat, null); + modifiedItemSpec = info.LastWriteTime.ToString(FileUtilities.FileTimeFormat, null); } else { @@ -307,7 +307,7 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS if (FileSystems.Default.FileExists(unescapedItemSpec)) { - modifiedItemSpec = File.GetCreationTime(unescapedItemSpec).ToString(FrameworkFileUtilities.FileTimeFormat, null); + modifiedItemSpec = File.GetCreationTime(unescapedItemSpec).ToString(FileUtilities.FileTimeFormat, null); } else { @@ -323,7 +323,7 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS if (FileSystems.Default.FileExists(unescapedItemSpec)) { - modifiedItemSpec = File.GetLastAccessTime(unescapedItemSpec).ToString(FrameworkFileUtilities.FileTimeFormat, null); + modifiedItemSpec = File.GetLastAccessTime(unescapedItemSpec).ToString(FileUtilities.FileTimeFormat, null); } else { diff --git a/src/MSBuild/CommandLine/CommandLineParser.cs b/src/MSBuild/CommandLine/CommandLineParser.cs index f01bdd6e8cf..69ba921f643 100644 --- a/src/MSBuild/CommandLine/CommandLineParser.cs +++ b/src/MSBuild/CommandLine/CommandLineParser.cs @@ -328,7 +328,7 @@ private void GatherResponseFileSwitch(string unquotedCommandLineArg, CommandLine { try { - string responseFile = FrameworkFileUtilities.FixFilePath(unquotedCommandLineArg.Substring(1)); + string responseFile = FileUtilities.FixFilePath(unquotedCommandLineArg.Substring(1)); if (responseFile.Length == 0) { @@ -358,7 +358,7 @@ private void GatherResponseFileSwitch(string unquotedCommandLineArg, CommandLine if (!isRepeatedResponseFile) { - var responseFileDirectory = FrameworkFileUtilities.EnsureTrailingSlash(Path.GetDirectoryName(responseFile)); + var responseFileDirectory = FileUtilities.EnsureTrailingSlash(Path.GetDirectoryName(responseFile)); includedResponseFiles.Add(responseFile); List argsFromResponseFile; @@ -550,7 +550,7 @@ private static string GetProjectDirectory(string[] projectSwitchParameters) if (projectSwitchParameters.Length == 1) { - var projectFile = FrameworkFileUtilities.FixFilePath(projectSwitchParameters[0]); + var projectFile = FileUtilities.FixFilePath(projectSwitchParameters[0]); if (FileSystems.Default.DirectoryExists(projectFile)) { diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj index a7ed51c829a..f190035aa5b 100644 --- a/src/MSBuild/MSBuild.csproj +++ b/src/MSBuild/MSBuild.csproj @@ -68,9 +68,6 @@ FileDelegates.cs - - FileUtilities.cs - @@ -87,9 +84,6 @@ - - TempFileUtilities.cs - diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index 6f81ddceb72..8519eb7bb0d 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -3103,7 +3103,7 @@ internal static string ProcessProjectSwitch( if (parameters.Length == 1) { - projectFile = FrameworkFileUtilities.FixFilePath(parameters[0]); + projectFile = FileUtilities.FixFilePath(parameters[0]); if (FileSystems.Default.DirectoryExists(projectFile)) { @@ -3689,7 +3689,7 @@ internal static void ProcessDistributedFileLogger( // Check to see if the logfile parameter has been set, if not set it to the current directory string logFileParameter = ExtractAnyLoggerParameter(fileParameters, "logfile"); - string logFileName = FrameworkFileUtilities.FixFilePath(ExtractAnyParameterValue(logFileParameter)); + string logFileName = FileUtilities.FixFilePath(ExtractAnyParameterValue(logFileParameter)); try { @@ -3953,7 +3953,7 @@ private static LoggerDescription ParseLoggingParameter(string parameter, string } // figure out whether the assembly's identity (strong/weak name), or its filename/path is provided - string testFile = FrameworkFileUtilities.FixFilePath(loggerAssemblySpec); + string testFile = FileUtilities.FixFilePath(loggerAssemblySpec); if (FileSystems.Default.FileExists(testFile)) { loggerAssemblyFile = testFile; @@ -4111,7 +4111,7 @@ private static string ProcessValidateSwitch(string[] parameters) foreach (string parameter in parameters) { InitializationException.VerifyThrow(schemaFile == null, "MultipleSchemasError", parameter); - string fileName = FrameworkFileUtilities.FixFilePath(parameter); + string fileName = FileUtilities.FixFilePath(parameter); InitializationException.VerifyThrow(FileSystems.Default.FileExists(fileName), "SchemaNotFoundError", fileName); schemaFile = Path.Combine(Directory.GetCurrentDirectory(), fileName); diff --git a/src/Shared/FileMatcher.cs b/src/Shared/FileMatcher.cs index d08e6549791..584d0b955d9 100644 --- a/src/Shared/FileMatcher.cs +++ b/src/Shared/FileMatcher.cs @@ -46,7 +46,7 @@ internal class FileMatcher #endif // on OSX both System.IO.Path separators are '/', so we have to use the literals - internal static readonly char[] directorySeparatorCharacters = FrameworkFileUtilities.Slashes; + internal static readonly char[] directorySeparatorCharacters = FileUtilities.Slashes; // until Cloudbuild switches to EvaluationContext, we need to keep their dependence on global glob caching via an environment variable private static readonly Lazy>> s_cachedGlobExpansions = new Lazy>>(() => new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase)); @@ -233,7 +233,7 @@ internal static bool HasPropertyOrItemReferences(string filespec) /// private static IReadOnlyList GetAccessibleFileSystemEntries(IFileSystem fileSystem, FileSystemEntity entityType, string path, string pattern, string projectDirectory, bool stripProjectDirectory) { - path = FrameworkFileUtilities.FixFilePath(path); + path = FileUtilities.FixFilePath(path); switch (entityType) { case FileSystemEntity.Files: return GetAccessibleFiles(fileSystem, path, pattern, projectDirectory, stripProjectDirectory); @@ -592,7 +592,7 @@ private static void PreprocessFileSpecForSplitting( out string wildcardDirectoryPart, out string filenamePart) { - filespec = FrameworkFileUtilities.FixFilePath(filespec); + filespec = FileUtilities.FixFilePath(filespec); int indexOfLastDirectorySeparator = filespec.LastIndexOfAny(directorySeparatorCharacters); if (-1 == indexOfLastDirectorySeparator) { @@ -2145,7 +2145,7 @@ private SearchAction GetFileSearchData( wildcard[wildcardLength - 1] == '*') { // Check that there are no other slashes in the wildcard. - if (wildcard.IndexOfAny(FrameworkFileUtilities.Slashes, 3, wildcardLength - 6) == -1) + if (wildcard.IndexOfAny(FileUtilities.Slashes, 3, wildcardLength - 6) == -1) { directoryPattern = wildcard.Substring(3, wildcardLength - 6); } @@ -2679,7 +2679,7 @@ private static bool IsSubdirectoryOf(string possibleChild, string possibleParent /// True in case of a match (e.g. directoryPath = "dir/subdir" and pattern = "s*"), false otherwise. private static bool DirectoryEndsWithPattern(string directoryPath, string pattern) { - int index = directoryPath.LastIndexOfAny(FrameworkFileUtilities.Slashes); + int index = directoryPath.LastIndexOfAny(FileUtilities.Slashes); return (index != -1 && IsMatch(directoryPath.AsSpan(index + 1), pattern)); } diff --git a/src/Shared/FileUtilities.cs b/src/Shared/FileUtilities.cs deleted file mode 100644 index 4c3be19ce69..00000000000 --- a/src/Shared/FileUtilities.cs +++ /dev/null @@ -1,323 +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 NET -using System.Buffers; -#endif -using System.Collections.Generic; -using System.IO; -using System.Runtime.CompilerServices; -using System.Text; -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 - { - /// - internal static void ClearCacheDirectoryPath() - => FrameworkFileUtilities.ClearCacheDirectoryPath(); - - /// - internal static StringComparison PathComparison - => FrameworkFileUtilities.PathComparison; - - /// - internal static StringComparer PathComparer - => FrameworkFileUtilities.PathComparer; - - /// - public static bool GetIsFileSystemCaseSensitive() - => FrameworkFileUtilities.GetIsFileSystemCaseSensitive(); - - /// -#if NET - internal static SearchValues InvalidPathChars -#else - internal static char[] InvalidPathChars -#endif - => FrameworkFileUtilities.InvalidPathChars; - - /// - internal static char[] InvalidFileNameCharsArray - => FrameworkFileUtilities.InvalidFileNameCharsArray; - - /// -#if NET - internal static SearchValues InvalidFileNameChars -#else - internal static char[] InvalidFileNameChars -#endif - => FrameworkFileUtilities.InvalidFileNameChars; - - /// - internal static string DirectorySeparatorString - => FrameworkFileUtilities.DirectorySeparatorString; - - /// - internal static string GetCacheDirectory() - => FrameworkFileUtilities.GetCacheDirectory(); - - /// - internal static string GetHexHash(string stringToHash) - => FrameworkFileUtilities.GetHexHash(stringToHash); - - /// - internal static int GetPathsHash(IEnumerable assemblyPaths) - => FrameworkFileUtilities.GetPathsHash(assemblyPaths); - - /// - internal static bool CanWriteToDirectory(string directory) - => FrameworkFileUtilities.CanWriteToDirectory(directory); - - /// - internal static void ClearCacheDirectory() - => FrameworkFileUtilities.ClearCacheDirectory(); - - /// - internal static string EnsureNoLeadingOrTrailingSlash(string path, int start) - => FrameworkFileUtilities.EnsureNoLeadingOrTrailingSlash(path, start); - - /// - internal static string EnsureTrailingNoLeadingSlash(string path, int start) - => FrameworkFileUtilities.EnsureTrailingNoLeadingSlash(path, start); - - /// - internal static string EnsureSingleQuotes(string path) - => FrameworkFileUtilities.EnsureSingleQuotes(path); - - /// - internal static string EnsureDoubleQuotes(string path) - => FrameworkFileUtilities.EnsureDoubleQuotes(path); - - /// - internal static string EnsureQuotes(string path, bool isSingleQuote = true) - => FrameworkFileUtilities.EnsureQuotes(path, isSingleQuote); - - /// - internal static string TrimAndStripAnyQuotes(string path) - => FrameworkFileUtilities.TrimAndStripAnyQuotes(path); - - /// - internal static string GetDirectoryNameOfFullPath(string fullPath) - => FrameworkFileUtilities.GetDirectoryNameOfFullPath(fullPath); - - /// - internal static string TruncatePathToTrailingSegments(string path, int trailingSegmentsToKeep) - => FrameworkFileUtilities.TruncatePathToTrailingSegments(path, trailingSegmentsToKeep); - - /// - internal static bool ContainsRelativePathSegments(string path) - => FrameworkFileUtilities.ContainsRelativePathSegments(path); - - /// - internal static string NormalizePath(string path) - => FrameworkFileUtilities.NormalizePath(path); - - /// - internal static string NormalizePath(string directory, string file) - => FrameworkFileUtilities.NormalizePath(directory, file); - - /// - internal static string NormalizePath(params string[] paths) - => FrameworkFileUtilities.NormalizePath(paths); - - /// - internal static string NormalizePathSeparatorsToForwardSlash(string path) - => FrameworkFileUtilities.NormalizePathSeparatorsToForwardSlash(path); - - /// - internal static string MaybeAdjustFilePath(string value, string baseDirectory = "") - => FrameworkFileUtilities.MaybeAdjustFilePath(value, baseDirectory); - - /// - internal static ReadOnlyMemory MaybeAdjustFilePath(ReadOnlyMemory value, string baseDirectory = "") - => FrameworkFileUtilities.MaybeAdjustFilePath(value, baseDirectory); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool IsAnySlash(char c) - => FrameworkFileUtilities.IsAnySlash(c); - - /// - internal static bool LooksLikeUnixFilePath(string value, string baseDirectory = "") - => FrameworkFileUtilities.LooksLikeUnixFilePath(value, baseDirectory); - - /// - internal static bool LooksLikeUnixFilePath(ReadOnlySpan value, string baseDirectory = "") - => FrameworkFileUtilities.LooksLikeUnixFilePath(value, baseDirectory); - - /// - internal static string GetDirectory(string fileSpec) - => FrameworkFileUtilities.GetDirectory(fileSpec); - - /// - internal static void DeleteSubdirectoriesNoThrow(string directory) - => FrameworkFileUtilities.DeleteSubdirectoriesNoThrow(directory); - - /// - internal static bool HasExtension(string fileName, string[] allowedExtensions) - => FrameworkFileUtilities.HasExtension(fileName, allowedExtensions); - - /// - internal static string FileTimeFormat - => FrameworkFileUtilities.FileTimeFormat; - - /// - internal static string GetFullPath(string fileSpec, string currentDirectory, bool escape = true) - => FrameworkFileUtilities.GetFullPath(fileSpec, currentDirectory, escape); - - /// - internal static string GetFullPathNoThrow(string path) - => FrameworkFileUtilities.GetFullPathNoThrow(path); - - /// - internal static bool ComparePathsNoThrow(string first, string second, string currentDirectory, bool alwaysIgnoreCase = false) - => FrameworkFileUtilities.ComparePathsNoThrow(first, second, currentDirectory, alwaysIgnoreCase); - - /// - internal static string NormalizePathForComparisonNoThrow(string path, string currentDirectory) - => FrameworkFileUtilities.NormalizePathForComparisonNoThrow(path, currentDirectory); - - /// - internal static bool PathIsInvalid(string path) - => FrameworkFileUtilities.PathIsInvalid(path); - - /// - internal static void DeleteNoThrow(string path) - => FrameworkFileUtilities.DeleteNoThrow(path); - - /// - internal static void DeleteDirectoryNoThrow(string path, bool recursive, int retryCount = 0, int retryTimeOut = 0) - => FrameworkFileUtilities.DeleteDirectoryNoThrow(path, recursive, retryCount, retryTimeOut); - - /// - internal static void DeleteWithoutTrailingBackslash(string path, bool recursive = false) - => FrameworkFileUtilities.DeleteWithoutTrailingBackslash(path, recursive); - - /// - internal static FileInfo GetFileInfoNoThrow(string filePath) - => FrameworkFileUtilities.GetFileInfoNoThrow(filePath); - - /// - internal static bool DirectoryExistsNoThrow(string fullPath, IFileSystem fileSystem = null) - => FrameworkFileUtilities.DirectoryExistsNoThrow(fullPath, fileSystem); - - /// - internal static bool FileExistsNoThrow(string fullPath, IFileSystem fileSystem = null) - => FrameworkFileUtilities.FileExistsNoThrow(fullPath, fileSystem); - - /// - internal static bool FileOrDirectoryExistsNoThrow(string fullPath, IFileSystem fileSystem = null) - => FrameworkFileUtilities.FileOrDirectoryExistsNoThrow(fullPath, fileSystem); - - /// - internal static bool IsSolutionFilename(string filename) - => FrameworkFileUtilities.IsSolutionFilename(filename); - - /// - internal static bool IsSolutionFilterFilename(string filename) - => FrameworkFileUtilities.IsSolutionFilterFilename(filename); - - /// - internal static bool IsSolutionXFilename(string filename) - => FrameworkFileUtilities.IsSolutionXFilename(filename); - - /// - internal static bool IsVCProjFilename(string filename) - => FrameworkFileUtilities.IsVCProjFilename(filename); - - /// - internal static bool IsDspFilename(string filename) - => FrameworkFileUtilities.IsDspFilename(filename); - - /// - internal static bool IsMetaprojectFilename(string filename) - => FrameworkFileUtilities.IsMetaprojectFilename(filename); - - /// - internal static bool IsBinaryLogFilename(string filename) - => FrameworkFileUtilities.IsBinaryLogFilename(filename); - - /// - internal static string MakeRelative(string basePath, string path) - => FrameworkFileUtilities.MakeRelative(basePath, path); - - /// - internal static string AttemptToShortenPath(string path) - => FrameworkFileUtilities.AttemptToShortenPath(path); - - /// - internal static string GetFolderAbove(string path, int count = 1) - => FrameworkFileUtilities.GetFolderAbove(path, count); - - /// - internal static string CombinePaths(string root, params string[] paths) - => FrameworkFileUtilities.CombinePaths(root, paths); - - /// - internal static string TrimTrailingSlashes(this string s) - => FrameworkFileUtilities.TrimTrailingSlashes(s); - - /// - internal static string ToSlash(this string s) - => FrameworkFileUtilities.ToSlash(s); - - /// - internal static string ToBackslash(this string s) - => FrameworkFileUtilities.ToBackslash(s); - - /// - internal static string ToPlatformSlash(this string s) - => FrameworkFileUtilities.ToPlatformSlash(s); - - /// - internal static string WithTrailingSlash(this string s) - => FrameworkFileUtilities.WithTrailingSlash(s); - - /// - internal static string NormalizeForPathComparison(this string s) - => FrameworkFileUtilities.NormalizeForPathComparison(s); - - /// - internal static bool PathsEqual(string path1, string path2) - => FrameworkFileUtilities.PathsEqual(path1, path2); - - /// - internal static StreamWriter OpenWrite(string path, bool append, Encoding encoding = null) - => FrameworkFileUtilities.OpenWrite(path, append, encoding); - - /// - internal static StreamReader OpenRead(string path, Encoding encoding = null, bool detectEncodingFromByteOrderMarks = true) - => FrameworkFileUtilities.OpenRead(path, encoding, detectEncodingFromByteOrderMarks); - - /// - internal static string GetDirectoryNameOfFileAbove(string startingDirectory, string fileName, IFileSystem fileSystem = null) - => FrameworkFileUtilities.GetDirectoryNameOfFileAbove(startingDirectory, fileName, fileSystem); - - /// - internal static string GetPathOfFileAbove(string file, string startingDirectory, IFileSystem fileSystem = null) - => FrameworkFileUtilities.GetPathOfFileAbove(file, startingDirectory, fileSystem); - - /// - internal static void EnsureDirectoryExists(string directoryPath) - => FrameworkFileUtilities.EnsureDirectoryExists(directoryPath); - - /// - internal static void ClearFileExistenceCache() - => FrameworkFileUtilities.ClearFileExistenceCache(); - - /// - internal static void ReadFromStream(this Stream stream, byte[] content, int startIndex, int length) - => FrameworkFileUtilities.ReadFromStream(stream, content, startIndex, length); - } -} diff --git a/src/Shared/FrameworkLocationHelper.cs b/src/Shared/FrameworkLocationHelper.cs index 6762aad8d66..9945522ae59 100644 --- a/src/Shared/FrameworkLocationHelper.cs +++ b/src/Shared/FrameworkLocationHelper.cs @@ -1574,7 +1574,7 @@ public virtual string GetPathToDotNetFrameworkReferenceAssemblies() string referencePath = GenerateReferenceAssemblyPath(FrameworkLocationHelper.programFilesReferenceAssemblyLocation, this.FrameworkName); if (FileSystems.Default.DirectoryExists(referencePath)) { - this._pathToDotNetFrameworkReferenceAssemblies = FrameworkFileUtilities.EnsureTrailingSlash(referencePath); + this._pathToDotNetFrameworkReferenceAssemblies = FileUtilities.EnsureTrailingSlash(referencePath); } } diff --git a/src/Shared/SolutionConfiguration.cs b/src/Shared/SolutionConfiguration.cs index 7242a8ca89f..22b086a8c71 100644 --- a/src/Shared/SolutionConfiguration.cs +++ b/src/Shared/SolutionConfiguration.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Xml; +using Microsoft.Build.Framework; namespace Microsoft.Build.Shared { diff --git a/src/Shared/TempFileUtilities.cs b/src/Shared/TempFileUtilities.cs deleted file mode 100644 index 1b3535ebbd9..00000000000 --- a/src/Shared/TempFileUtilities.cs +++ /dev/null @@ -1,56 +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.Framework; - -#nullable disable - -namespace Microsoft.Build.Shared -{ - /// - /// This class contains utility methods for file IO. - /// It is in a separate file so that it can be selectively included into an assembly. - /// - internal static partial class FileUtilities - { - /// - internal static string TempFileDirectory - => FrameworkFileUtilities.TempFileDirectory; - - /// - internal static void ClearTempFileDirectory() - => FrameworkFileUtilities.ClearTempFileDirectory(); - - /// - internal static string GetTemporaryDirectory(bool createDirectory = true, string subfolder = null) - => FrameworkFileUtilities.GetTemporaryDirectory(createDirectory, subfolder); - - /// - internal static string GetTemporaryFileName() - => FrameworkFileUtilities.GetTemporaryFileName(); - - /// - internal static string GetTemporaryFileName(string extension) - => FrameworkFileUtilities.GetTemporaryFileName(extension); - - /// - internal static string GetTemporaryFile() - => FrameworkFileUtilities.GetTemporaryFile(); - - /// - internal static string GetTemporaryFile(string fileName, string extension, bool createFile) - => FrameworkFileUtilities.GetTemporaryFile(fileName, extension, createFile); - - /// - internal static string GetTemporaryFile(string extension) - => FrameworkFileUtilities.GetTemporaryFile(extension); - - /// - internal static string GetTemporaryFile(string directory, string fileName, string extension, bool createFile = true) - => FrameworkFileUtilities.GetTemporaryFile(directory, fileName, extension, createFile); - - /// - internal static void CopyDirectory(string source, string dest) - => FrameworkFileUtilities.CopyDirectory(source, dest); - } -} diff --git a/src/Shared/ToolsetElement.cs b/src/Shared/ToolsetElement.cs index 913bbbf1a5c..9b6945eaf86 100644 --- a/src/Shared/ToolsetElement.cs +++ b/src/Shared/ToolsetElement.cs @@ -6,6 +6,7 @@ using System.Configuration; using System.IO; using Microsoft.Build.Collections; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Shared/UnitTests/FileMatcher_Tests.cs b/src/Shared/UnitTests/FileMatcher_Tests.cs index dc90922a428..c0bea972965 100644 --- a/src/Shared/UnitTests/FileMatcher_Tests.cs +++ b/src/Shared/UnitTests/FileMatcher_Tests.cs @@ -1873,8 +1873,8 @@ public void GetFileSpecInfoCommon( { if (NativeMethodsShared.IsUnixLike) { - expectedFixedDirectoryPart = FrameworkFileUtilities.FixFilePath(expectedFixedDirectoryPart); - expectedWildcardDirectoryPart = FrameworkFileUtilities.FixFilePath(expectedWildcardDirectoryPart); + expectedFixedDirectoryPart = FileUtilities.FixFilePath(expectedFixedDirectoryPart); + expectedWildcardDirectoryPart = FileUtilities.FixFilePath(expectedWildcardDirectoryPart); } TestGetFileSpecInfo( filespec, @@ -2299,11 +2299,11 @@ private bool IsMatchingDirectory(string path, string candidate) { if (String.Compare(normalizedPath, 0, normalizedCandidate, 0, normalizedPath.Length, StringComparison.OrdinalIgnoreCase) == 0) { - if (FrameworkFileUtilities.EndsWithSlash(normalizedPath)) + if (FileUtilities.EndsWithSlash(normalizedPath)) { return true; } - else if (FrameworkFileUtilities.IsSlash(normalizedCandidate[normalizedPath.Length])) + else if (FileUtilities.IsSlash(normalizedCandidate[normalizedPath.Length])) { return true; } @@ -2508,9 +2508,9 @@ private static void ValidateSplitFileSpec( out wildcardDirectoryPart, out filenamePart); - expectedFixedDirectoryPart = FrameworkFileUtilities.FixFilePath(expectedFixedDirectoryPart); - expectedWildcardDirectoryPart = FrameworkFileUtilities.FixFilePath(expectedWildcardDirectoryPart); - expectedFilenamePart = FrameworkFileUtilities.FixFilePath(expectedFilenamePart); + expectedFixedDirectoryPart = FileUtilities.FixFilePath(expectedFixedDirectoryPart); + expectedWildcardDirectoryPart = FileUtilities.FixFilePath(expectedWildcardDirectoryPart); + expectedFilenamePart = FileUtilities.FixFilePath(expectedFilenamePart); if ( diff --git a/src/Shared/UnitTests/FileUtilities_Tests.cs b/src/Shared/UnitTests/FileUtilities_Tests.cs index 622978c47b2..ca1d5623348 100644 --- a/src/Shared/UnitTests/FileUtilities_Tests.cs +++ b/src/Shared/UnitTests/FileUtilities_Tests.cs @@ -219,20 +219,20 @@ public void GetFileInfoNoThrowNonexistent() [Trait("Category", "netcore-linux-failing")] public void EndsWithSlash() { - Assert.True(FrameworkFileUtilities.EndsWithSlash(@"C:\foo\")); - Assert.True(FrameworkFileUtilities.EndsWithSlash(@"C:\")); - Assert.True(FrameworkFileUtilities.EndsWithSlash(@"\")); + Assert.True(FileUtilities.EndsWithSlash(@"C:\foo\")); + Assert.True(FileUtilities.EndsWithSlash(@"C:\")); + Assert.True(FileUtilities.EndsWithSlash(@"\")); - Assert.True(FrameworkFileUtilities.EndsWithSlash(@"http://www.microsoft.com/")); - Assert.True(FrameworkFileUtilities.EndsWithSlash(@"//server/share/")); - Assert.True(FrameworkFileUtilities.EndsWithSlash(@"/")); + Assert.True(FileUtilities.EndsWithSlash(@"http://www.microsoft.com/")); + Assert.True(FileUtilities.EndsWithSlash(@"//server/share/")); + Assert.True(FileUtilities.EndsWithSlash(@"/")); - Assert.False(FrameworkFileUtilities.EndsWithSlash(@"C:\foo")); - Assert.False(FrameworkFileUtilities.EndsWithSlash(@"C:")); - Assert.False(FrameworkFileUtilities.EndsWithSlash(@"foo")); + Assert.False(FileUtilities.EndsWithSlash(@"C:\foo")); + Assert.False(FileUtilities.EndsWithSlash(@"C:")); + Assert.False(FileUtilities.EndsWithSlash(@"foo")); // confirm that empty string doesn't barf - Assert.False(FrameworkFileUtilities.EndsWithSlash(String.Empty)); + Assert.False(FileUtilities.EndsWithSlash(String.Empty)); } /// @@ -246,16 +246,16 @@ public void GetDirectoryWithTrailingSlash() Assert.Equal(NativeMethodsShared.IsWindows ? @"c:\" : "/", FileUtilities.GetDirectory(NativeMethodsShared.IsWindows ? @"c:\" : "/")); Assert.Equal(NativeMethodsShared.IsWindows ? @"c:\" : "/", FileUtilities.GetDirectory(NativeMethodsShared.IsWindows ? @"c:\foo" : "/foo")); Assert.Equal(NativeMethodsShared.IsWindows ? @"c:" : "/", FileUtilities.GetDirectory(NativeMethodsShared.IsWindows ? @"c:" : "/")); - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"\"), FileUtilities.GetDirectory(@"\")); - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"\"), FileUtilities.GetDirectory(@"\foo")); - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"..\"), FileUtilities.GetDirectory(@"..\foo")); - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"\foo\"), FileUtilities.GetDirectory(@"\foo\")); - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"\\server\share"), FileUtilities.GetDirectory(@"\\server\share")); - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"\\server\share\"), FileUtilities.GetDirectory(@"\\server\share\")); - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"\\server\share\"), FileUtilities.GetDirectory(@"\\server\share\file")); - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"\\server\share\directory\"), FileUtilities.GetDirectory(@"\\server\share\directory\")); - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"foo\"), FileUtilities.GetDirectory(@"foo\bar")); - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"\foo\bar\"), FileUtilities.GetDirectory(@"\foo\bar\")); + Assert.Equal(FileUtilities.FixFilePath(@"\"), FileUtilities.GetDirectory(@"\")); + Assert.Equal(FileUtilities.FixFilePath(@"\"), FileUtilities.GetDirectory(@"\foo")); + Assert.Equal(FileUtilities.FixFilePath(@"..\"), FileUtilities.GetDirectory(@"..\foo")); + Assert.Equal(FileUtilities.FixFilePath(@"\foo\"), FileUtilities.GetDirectory(@"\foo\")); + Assert.Equal(FileUtilities.FixFilePath(@"\\server\share"), FileUtilities.GetDirectory(@"\\server\share")); + Assert.Equal(FileUtilities.FixFilePath(@"\\server\share\"), FileUtilities.GetDirectory(@"\\server\share\")); + Assert.Equal(FileUtilities.FixFilePath(@"\\server\share\"), FileUtilities.GetDirectory(@"\\server\share\file")); + Assert.Equal(FileUtilities.FixFilePath(@"\\server\share\directory\"), FileUtilities.GetDirectory(@"\\server\share\directory\")); + Assert.Equal(FileUtilities.FixFilePath(@"foo\"), FileUtilities.GetDirectory(@"foo\bar")); + Assert.Equal(FileUtilities.FixFilePath(@"\foo\bar\"), FileUtilities.GetDirectory(@"\foo\bar\")); Assert.Equal(String.Empty, FileUtilities.GetDirectory("foo")); } @@ -322,14 +322,14 @@ public void HasExtension_UsesOrdinalIgnoreCase() public void EnsureTrailingSlash() { // Doesn't have a trailing slash to start with. - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"foo\bar\"), FrameworkFileUtilities.EnsureTrailingSlash(@"foo\bar")); // "test 1" - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"foo/bar\"), FrameworkFileUtilities.EnsureTrailingSlash(@"foo/bar")); // "test 2" + Assert.Equal(FileUtilities.FixFilePath(@"foo\bar\"), FileUtilities.EnsureTrailingSlash(@"foo\bar")); // "test 1" + Assert.Equal(FileUtilities.FixFilePath(@"foo/bar\"), FileUtilities.EnsureTrailingSlash(@"foo/bar")); // "test 2" // Already has a trailing slash to start with. - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"foo/bar/"), FrameworkFileUtilities.EnsureTrailingSlash(@"foo/bar/")); // test 3" - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"foo\bar\"), FrameworkFileUtilities.EnsureTrailingSlash(@"foo\bar\")); // test 4" - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"foo/bar\"), FrameworkFileUtilities.EnsureTrailingSlash(@"foo/bar\")); // test 5" - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"foo\bar/"), FrameworkFileUtilities.EnsureTrailingSlash(@"foo\bar/")); // "test 5" + Assert.Equal(FileUtilities.FixFilePath(@"foo/bar/"), FileUtilities.EnsureTrailingSlash(@"foo/bar/")); // test 3" + Assert.Equal(FileUtilities.FixFilePath(@"foo\bar\"), FileUtilities.EnsureTrailingSlash(@"foo\bar\")); // test 4" + Assert.Equal(FileUtilities.FixFilePath(@"foo/bar\"), FileUtilities.EnsureTrailingSlash(@"foo/bar\")); // test 5" + Assert.Equal(FileUtilities.FixFilePath(@"foo\bar/"), FileUtilities.EnsureTrailingSlash(@"foo\bar/")); // "test 5" } /// diff --git a/src/Shared/UnitTests/NativeMethodsShared_Tests.cs b/src/Shared/UnitTests/NativeMethodsShared_Tests.cs index fd5e9a82ed8..579229649ce 100644 --- a/src/Shared/UnitTests/NativeMethodsShared_Tests.cs +++ b/src/Shared/UnitTests/NativeMethodsShared_Tests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Runtime.InteropServices; using System.Runtime.Versioning; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Xunit; diff --git a/src/Shared/UnitTests/TypeLoader_Dependencies_Tests.cs b/src/Shared/UnitTests/TypeLoader_Dependencies_Tests.cs index 52ce9eddc1e..5f21b94fbe8 100644 --- a/src/Shared/UnitTests/TypeLoader_Dependencies_Tests.cs +++ b/src/Shared/UnitTests/TypeLoader_Dependencies_Tests.cs @@ -23,7 +23,7 @@ public class TypeLoader_Dependencies_Tests [Fact] public void LoadAssemblyAndDependency_InsideProjectFolder() { - using (var dir = new FrameworkFileUtilities.TempWorkingDirectory(ProjectFileFolder)) + using (var dir = new FileUtilities.TempWorkingDirectory(ProjectFileFolder)) { string projectFilePath = Path.Combine(dir.Path, ProjectFileName); @@ -41,7 +41,7 @@ public void LoadAssemblyAndDependency_InsideProjectFolder() [Fact] public void LoadAssemblyAndDependency_OutsideProjectFolder() { - using (var dir = new FrameworkFileUtilities.TempWorkingDirectory(ProjectFileFolder)) + using (var dir = new FileUtilities.TempWorkingDirectory(ProjectFileFolder)) { string projectFilePath = Path.Combine(dir.Path, ProjectFileName); diff --git a/src/Shared/UnitTests/TypeLoader_Tests.cs b/src/Shared/UnitTests/TypeLoader_Tests.cs index 03b9c1137d8..048ba6ceebd 100644 --- a/src/Shared/UnitTests/TypeLoader_Tests.cs +++ b/src/Shared/UnitTests/TypeLoader_Tests.cs @@ -63,7 +63,7 @@ public void Regress_Mutation_ParameterOrderDoesntMatter() [Fact] public void LoadNonExistingAssembly() { - using var dir = new FrameworkFileUtilities.TempWorkingDirectory(ProjectFileFolder); + using var dir = new FileUtilities.TempWorkingDirectory(ProjectFileFolder); string projectFilePath = Path.Combine(dir.Path, ProjectFileName); @@ -80,7 +80,7 @@ public void LoadNonExistingAssembly() [Fact] public void LoadInsideAsssembly() { - using (var dir = new FrameworkFileUtilities.TempWorkingDirectory(ProjectFileFolder)) + using (var dir = new FileUtilities.TempWorkingDirectory(ProjectFileFolder)) { string projectFilePath = Path.Combine(dir.Path, ProjectFileName); @@ -119,7 +119,7 @@ public void LoadTaskDependingOnMSBuild() [Fact] public void LoadOutsideAssembly() { - using (var dir = new FrameworkFileUtilities.TempWorkingDirectory(ProjectFileFolder)) + using (var dir = new FileUtilities.TempWorkingDirectory(ProjectFileFolder)) { string projectFilePath = Path.Combine(dir.Path, ProjectFileName); string originalDLLPath = Path.Combine(dir.Path, DLLFileName); @@ -144,7 +144,7 @@ public void LoadOutsideAssembly() [Fact(Skip = "https://github.com/dotnet/msbuild/issues/325")] public void LoadInsideAssemblyWhenGivenOutsideAssemblyWithSameName() { - using (var dir = new FrameworkFileUtilities.TempWorkingDirectory(ProjectFileFolder)) + using (var dir = new FileUtilities.TempWorkingDirectory(ProjectFileFolder)) { string projectFilePath = Path.Combine(dir.Path, ProjectFileName); string originalDLLPath = Path.Combine(dir.Path, DLLFileName); diff --git a/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs b/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs index 5607a413d21..641a8164086 100644 --- a/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs +++ b/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Runtime.Versioning; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Tasks; using Microsoft.Build.Utilities; diff --git a/src/Tasks.UnitTests/AxTlbBaseTask_Tests.cs b/src/Tasks.UnitTests/AxTlbBaseTask_Tests.cs index 99df3fbf783..f40dd8d215d 100644 --- a/src/Tasks.UnitTests/AxTlbBaseTask_Tests.cs +++ b/src/Tasks.UnitTests/AxTlbBaseTask_Tests.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Tasks; using Microsoft.Build.Utilities; diff --git a/src/Tasks.UnitTests/CodeTaskFactoryTests.cs b/src/Tasks.UnitTests/CodeTaskFactoryTests.cs index 3b9b1740d64..2ab31ff10f8 100644 --- a/src/Tasks.UnitTests/CodeTaskFactoryTests.cs +++ b/src/Tasks.UnitTests/CodeTaskFactoryTests.cs @@ -1206,15 +1206,15 @@ public void BuildTaskSimpleCodeFactoryTempDirectoryDoesntExist() { // Ensure we're getting the right temp path (%TMP% == GetTempPath()) Assert.Equal( - FrameworkFileUtilities.EnsureTrailingSlash(Path.GetTempPath()), - FrameworkFileUtilities.EnsureTrailingSlash(Path.GetFullPath(oldTempPath))); + FileUtilities.EnsureTrailingSlash(Path.GetTempPath()), + FileUtilities.EnsureTrailingSlash(Path.GetFullPath(oldTempPath))); Assert.False(Directory.Exists(newTempPath)); Environment.SetEnvironmentVariable("TMP", newTempPath); Assert.Equal( - FrameworkFileUtilities.EnsureTrailingSlash(newTempPath), - FrameworkFileUtilities.EnsureTrailingSlash(Path.GetTempPath())); + FileUtilities.EnsureTrailingSlash(newTempPath), + FileUtilities.EnsureTrailingSlash(Path.GetTempPath())); MockLogger mockLogger = Helpers.BuildProjectWithNewOMExpectSuccess(projectFileContents); mockLogger.AssertLogContains("Hello, World!"); diff --git a/src/Tasks.UnitTests/Copy_Tests.cs b/src/Tasks.UnitTests/Copy_Tests.cs index 63062fcf3a5..19044e99b41 100644 --- a/src/Tasks.UnitTests/Copy_Tests.cs +++ b/src/Tasks.UnitTests/Copy_Tests.cs @@ -2508,8 +2508,8 @@ public void SuccessAfterOneRetryContinueToNextFile(bool isUseHardLinks, bool isU // Copy calls to different destinations can come in any order when running in parallel. // Use .OriginalValue to compare against the original input path (before Path.GetFullPath resolution). // TaskItem normalizes paths via FileUtilities.FixFilePath, so we need to do the same for comparison. - Assert.Contains(copyFunctor.FilesCopiedSuccessfully, f => f.Path.OriginalValue == FrameworkFileUtilities.FixFilePath("c:\\source")); - Assert.Contains(copyFunctor.FilesCopiedSuccessfully, f => f.Path.OriginalValue == FrameworkFileUtilities.FixFilePath("c:\\source2")); + Assert.Contains(copyFunctor.FilesCopiedSuccessfully, f => f.Path.OriginalValue == FileUtilities.FixFilePath("c:\\source")); + Assert.Contains(copyFunctor.FilesCopiedSuccessfully, f => f.Path.OriginalValue == FileUtilities.FixFilePath("c:\\source2")); } /// diff --git a/src/Tasks.UnitTests/CreateCSharpManifestResourceName_Tests.cs b/src/Tasks.UnitTests/CreateCSharpManifestResourceName_Tests.cs index c0b37c2f639..a29f5b938fc 100644 --- a/src/Tasks.UnitTests/CreateCSharpManifestResourceName_Tests.cs +++ b/src/Tasks.UnitTests/CreateCSharpManifestResourceName_Tests.cs @@ -326,7 +326,7 @@ public void CulturedBitmapWithRootNamespace() binaryStream: null, log: null); - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"fr\RootNamespace.SubFolder.SplashScreen.bmp"), result); + Assert.Equal(FileUtilities.FixFilePath(@"fr\RootNamespace.SubFolder.SplashScreen.bmp"), result); } /// diff --git a/src/Tasks.UnitTests/CreateVisualBasicManifestResourceName_Tests.cs b/src/Tasks.UnitTests/CreateVisualBasicManifestResourceName_Tests.cs index d7ce809ecfa..567eea82ab6 100644 --- a/src/Tasks.UnitTests/CreateVisualBasicManifestResourceName_Tests.cs +++ b/src/Tasks.UnitTests/CreateVisualBasicManifestResourceName_Tests.cs @@ -217,7 +217,7 @@ public void RootnamespaceWithCulture() { string result = CreateVisualBasicManifestResourceName.CreateManifestNameImpl( - fileName: FrameworkFileUtilities.FixFilePath(@"SubFolder\MyForm.en-GB.ResX"), + fileName: FileUtilities.FixFilePath(@"SubFolder\MyForm.en-GB.ResX"), linkFileName: null, // Link file name prependCultureAsDirectory: @@ -283,7 +283,7 @@ public void BitmapWithRootNamespace() { string result = CreateVisualBasicManifestResourceName.CreateManifestNameImpl( - fileName: FrameworkFileUtilities.FixFilePath(@"SubFolder\SplashScreen.bmp"), + fileName: FileUtilities.FixFilePath(@"SubFolder\SplashScreen.bmp"), linkFileName: null, // Link file name prependCultureAsDirectory: true, rootNamespace: "RootNamespace", // Root namespace @@ -303,7 +303,7 @@ public void CulturedBitmapWithRootNamespace() { string result = CreateVisualBasicManifestResourceName.CreateManifestNameImpl( - fileName: FrameworkFileUtilities.FixFilePath(@"SubFolder\SplashScreen.fr.bmp"), + fileName: FileUtilities.FixFilePath(@"SubFolder\SplashScreen.fr.bmp"), linkFileName: null, // Link file name prependCultureAsDirectory: true, rootNamespace: "RootNamespace", // Root namespace @@ -312,7 +312,7 @@ public void CulturedBitmapWithRootNamespace() binaryStream: null, log: null); - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"fr\RootNamespace.SplashScreen.bmp"), result); + Assert.Equal(FileUtilities.FixFilePath(@"fr\RootNamespace.SplashScreen.bmp"), result); } /// @@ -323,7 +323,7 @@ public void CulturedBitmapWithRootNamespaceNoDirectoryPrefix() { string result = CreateVisualBasicManifestResourceName.CreateManifestNameImpl( - fileName: FrameworkFileUtilities.FixFilePath(@"SubFolder\SplashScreen.fr.bmp"), + fileName: FileUtilities.FixFilePath(@"SubFolder\SplashScreen.fr.bmp"), linkFileName: null, // Link file name prependCultureAsDirectory: false, rootNamespace: "RootNamespace", // Root namespace @@ -614,7 +614,7 @@ public void CulturedResourcesFileWithRootNamespaceWithinSubfolder() { string result = CreateVisualBasicManifestResourceName.CreateManifestNameImpl( - fileName: FrameworkFileUtilities.FixFilePath(@"SubFolder\MyResource.fr.resources"), + fileName: FileUtilities.FixFilePath(@"SubFolder\MyResource.fr.resources"), linkFileName: null, // Link file name prependCultureAsDirectory: false, rootNamespace: "RootNamespace", // Root namespace diff --git a/src/Tasks.UnitTests/FindAppConfigFile_Tests.cs b/src/Tasks.UnitTests/FindAppConfigFile_Tests.cs index 7f6a1a10600..0965bb7c461 100644 --- a/src/Tasks.UnitTests/FindAppConfigFile_Tests.cs +++ b/src/Tasks.UnitTests/FindAppConfigFile_Tests.cs @@ -48,7 +48,7 @@ public void FoundInSecondBelowProjectDirectory() f.SecondaryList = new ITaskItem[] { new TaskItem("foo\\app.config"), new TaskItem("xxx") }; f.TargetPath = "targetpath"; Assert.True(f.Execute()); - Assert.Equal(FrameworkFileUtilities.FixFilePath("foo\\app.config"), f.AppConfigFile.ItemSpec); + Assert.Equal(FileUtilities.FixFilePath("foo\\app.config"), f.AppConfigFile.ItemSpec); Assert.Equal("targetpath", f.AppConfigFile.GetMetadata("TargetPath")); } @@ -74,7 +74,7 @@ public void MatchFileNameOnlyWithAnInvalidPath() f.TargetPath = "targetpath"; Assert.True(f.Execute()); // Should ignore the invalid paths - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"foo\\app.config"), f.AppConfigFile.ItemSpec); + Assert.Equal(FileUtilities.FixFilePath(@"foo\\app.config"), f.AppConfigFile.ItemSpec); } // For historical reasons, we should return the last one in the list diff --git a/src/Tasks.UnitTests/FindInList_Tests.cs b/src/Tasks.UnitTests/FindInList_Tests.cs index 6cdece8d848..c051964fdfa 100644 --- a/src/Tasks.UnitTests/FindInList_Tests.cs +++ b/src/Tasks.UnitTests/FindInList_Tests.cs @@ -117,7 +117,7 @@ public void MatchFileNameOnly() f.MatchFileNameOnly = true; f.List = new ITaskItem[] { new TaskItem(@"c:\foo\a.cs"), new TaskItem("b.cs") }; Assert.True(f.Execute()); - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"c:\foo\a.cs"), f.ItemFound.ItemSpec); + Assert.Equal(FileUtilities.FixFilePath(@"c:\foo\a.cs"), f.ItemFound.ItemSpec); } [Fact] @@ -132,7 +132,7 @@ public void MatchFileNameOnlyWithAnInvalidPath() Assert.True(f.Execute()); Console.WriteLine(e.Log); // Should ignore the invalid paths - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"foo\a.cs"), f.ItemFound.ItemSpec); + Assert.Equal(FileUtilities.FixFilePath(@"foo\a.cs"), f.ItemFound.ItemSpec); } } } diff --git a/src/Tasks.UnitTests/FindUnderPath_Tests.cs b/src/Tasks.UnitTests/FindUnderPath_Tests.cs index 0f9197579ab..d8b98b63ce5 100644 --- a/src/Tasks.UnitTests/FindUnderPath_Tests.cs +++ b/src/Tasks.UnitTests/FindUnderPath_Tests.cs @@ -33,8 +33,8 @@ public void BasicFilter() Assert.True(success); Assert.Single(t.InPath); Assert.Single(t.OutOfPath); - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"C:\MyProject\File1.txt"), t.InPath[0].ItemSpec); - Assert.Equal(FrameworkFileUtilities.FixFilePath(@"C:\SomeoneElsesProject\File2.txt"), t.OutOfPath[0].ItemSpec); + Assert.Equal(FileUtilities.FixFilePath(@"C:\MyProject\File1.txt"), t.InPath[0].ItemSpec); + Assert.Equal(FileUtilities.FixFilePath(@"C:\SomeoneElsesProject\File2.txt"), t.OutOfPath[0].ItemSpec); } /// diff --git a/src/Tasks.UnitTests/GetReferencePaths_Tests.cs b/src/Tasks.UnitTests/GetReferencePaths_Tests.cs index 056f0080963..f4352c0322a 100644 --- a/src/Tasks.UnitTests/GetReferencePaths_Tests.cs +++ b/src/Tasks.UnitTests/GetReferencePaths_Tests.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Tasks; using Microsoft.Build.Utilities; diff --git a/src/Tasks.UnitTests/HintPathResolver_Tests.cs b/src/Tasks.UnitTests/HintPathResolver_Tests.cs index 9548e32f421..7849b653fdb 100644 --- a/src/Tasks.UnitTests/HintPathResolver_Tests.cs +++ b/src/Tasks.UnitTests/HintPathResolver_Tests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.UnitTests; using Shouldly; diff --git a/src/Tasks.UnitTests/OutputPathTests.cs b/src/Tasks.UnitTests/OutputPathTests.cs index b1b897c20dc..ea7cfbb1f33 100644 --- a/src/Tasks.UnitTests/OutputPathTests.cs +++ b/src/Tasks.UnitTests/OutputPathTests.cs @@ -5,6 +5,7 @@ using System.IO; using Microsoft.Build.Evaluation; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.UnitTests; diff --git a/src/Tasks.UnitTests/ResolveCodeAnalysisRuleSet_Tests.cs b/src/Tasks.UnitTests/ResolveCodeAnalysisRuleSet_Tests.cs index a8eb139f408..c4635039b1b 100644 --- a/src/Tasks.UnitTests/ResolveCodeAnalysisRuleSet_Tests.cs +++ b/src/Tasks.UnitTests/ResolveCodeAnalysisRuleSet_Tests.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Tasks; using Xunit; diff --git a/src/Tasks.UnitTests/ResourceHandling/ResGenDependencies_Tests.cs b/src/Tasks.UnitTests/ResourceHandling/ResGenDependencies_Tests.cs index b676ff9817c..acd43836a7f 100644 --- a/src/Tasks.UnitTests/ResourceHandling/ResGenDependencies_Tests.cs +++ b/src/Tasks.UnitTests/ResourceHandling/ResGenDependencies_Tests.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Reflection; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Tasks; using Microsoft.Build.Tasks.ResourceHandling; diff --git a/src/Tasks/AppConfig/AppConfig.cs b/src/Tasks/AppConfig/AppConfig.cs index 498f2a23d10..44affce2eae 100644 --- a/src/Tasks/AppConfig/AppConfig.cs +++ b/src/Tasks/AppConfig/AppConfig.cs @@ -4,7 +4,7 @@ using System; using System.IO; using System.Xml; - +using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Tasks/AssemblyDependency/AssemblyFoldersFromConfig/AssemblyFoldersFromConfigCache.cs b/src/Tasks/AssemblyDependency/AssemblyFoldersFromConfig/AssemblyFoldersFromConfigCache.cs index ded0c8b46bf..45f0d257c12 100644 --- a/src/Tasks/AssemblyDependency/AssemblyFoldersFromConfig/AssemblyFoldersFromConfigCache.cs +++ b/src/Tasks/AssemblyDependency/AssemblyFoldersFromConfig/AssemblyFoldersFromConfigCache.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Tasks/AssemblyDependency/CandidateAssemblyFilesResolver.cs b/src/Tasks/AssemblyDependency/CandidateAssemblyFilesResolver.cs index 328bb6984a2..99bda929a54 100644 --- a/src/Tasks/AssemblyDependency/CandidateAssemblyFilesResolver.cs +++ b/src/Tasks/AssemblyDependency/CandidateAssemblyFilesResolver.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Reflection; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Tasks/AssemblyDependency/HintPathResolver.cs b/src/Tasks/AssemblyDependency/HintPathResolver.cs index 96a3b920491..08c6d97ef52 100644 --- a/src/Tasks/AssemblyDependency/HintPathResolver.cs +++ b/src/Tasks/AssemblyDependency/HintPathResolver.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Tasks/AssemblyDependency/ReferenceTable.cs b/src/Tasks/AssemblyDependency/ReferenceTable.cs index ca34cbccf33..37de75c0dec 100644 --- a/src/Tasks/AssemblyDependency/ReferenceTable.cs +++ b/src/Tasks/AssemblyDependency/ReferenceTable.cs @@ -1720,7 +1720,7 @@ private bool FindAssociatedFiles() { // We don't look for associated files for FX assemblies. bool hasFrameworkPath = false; - string referenceDirectoryName = FrameworkFileUtilities.EnsureTrailingSlash(reference.DirectoryName); + string referenceDirectoryName = FileUtilities.EnsureTrailingSlash(reference.DirectoryName); foreach (string frameworkPath in _frameworkPaths) { @@ -2768,7 +2768,7 @@ private ITaskItem SetItemMetadata(List relatedItems, List // Set up the satellites. foreach (string satelliteFile in satellites) { - relatedItemBase.SetMetadata(ItemMetadataNames.destinationSubDirectory, FrameworkFileUtilities.EnsureTrailingSlash(Path.GetDirectoryName(satelliteFile))); + relatedItemBase.SetMetadata(ItemMetadataNames.destinationSubDirectory, FileUtilities.EnsureTrailingSlash(Path.GetDirectoryName(satelliteFile))); AddRelatedItem(satelliteItems, relatedItemBase, Path.Combine(reference.DirectoryName, satelliteFile)); } } diff --git a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs index 9c685aa4bbb..30d999a4ec4 100644 --- a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs +++ b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs @@ -2246,7 +2246,7 @@ internal bool Execute( { for (int i = 0; i < _targetFrameworkDirectories.Length; i++) { - _targetFrameworkDirectories[i] = FrameworkFileUtilities.EnsureTrailingSlash(_targetFrameworkDirectories[i]); + _targetFrameworkDirectories[i] = FileUtilities.EnsureTrailingSlash(_targetFrameworkDirectories[i]); } } diff --git a/src/Tasks/AssignTargetPath.cs b/src/Tasks/AssignTargetPath.cs index f017a02fefe..f8f42c9945d 100644 --- a/src/Tasks/AssignTargetPath.cs +++ b/src/Tasks/AssignTargetPath.cs @@ -67,10 +67,10 @@ public override bool Execute() // Ensure trailing slash otherwise c:\bin appears to match part of c:\bin2\foo // Also ensure that relative segments in the path are resolved. fullRootPath = - TaskEnvironment.GetAbsolutePath(FrameworkFileUtilities.EnsureTrailingSlash(RootFolder)).GetCanonicalForm(); + TaskEnvironment.GetAbsolutePath(FileUtilities.EnsureTrailingSlash(RootFolder)).GetCanonicalForm(); // Ensure trailing slash for comparison. Current directory is already canonical, so we don't need to call GetCanonicalForm on it. - AbsolutePath currentDirectory = FrameworkFileUtilities.EnsureTrailingSlash(TaskEnvironment.ProjectDirectory); + AbsolutePath currentDirectory = FileUtilities.EnsureTrailingSlash(TaskEnvironment.ProjectDirectory); // Check if the root folder is the same as the current directory - AbsolutePath handles OS-aware case sensitivity. isRootFolderSameAsCurrentDirectory = fullRootPath == currentDirectory; @@ -82,10 +82,10 @@ public override bool Execute() // Ensure trailing slash otherwise c:\bin appears to match part of c:\bin2\foo // Also ensure that relative segments in the path are resolved and throw on illegal characters in Path.GetFullPath to preserve pre-existing behavior. fullRootPathString = - Path.GetFullPath(TaskEnvironment.GetAbsolutePath(FrameworkFileUtilities.EnsureTrailingSlash(RootFolder))); + Path.GetFullPath(TaskEnvironment.GetAbsolutePath(FileUtilities.EnsureTrailingSlash(RootFolder))); // Ensure trailing slash for comparison. Current directory is already canonical, so we don't need to call GetCanonicalForm on it. - AbsolutePath currentDirectory = FrameworkFileUtilities.EnsureTrailingSlash(TaskEnvironment.ProjectDirectory); + AbsolutePath currentDirectory = FileUtilities.EnsureTrailingSlash(TaskEnvironment.ProjectDirectory); // Check if the root folder is the same as the current directory. // Perform a case-insensitive comparison to match Path.GetFullPath behavior on Windows, even on case-sensitive file systems, diff --git a/src/Tasks/BootstrapperUtil/ResourceUpdater.cs b/src/Tasks/BootstrapperUtil/ResourceUpdater.cs index afee6d8a725..99b9c5ad0db 100644 --- a/src/Tasks/BootstrapperUtil/ResourceUpdater.cs +++ b/src/Tasks/BootstrapperUtil/ResourceUpdater.cs @@ -7,8 +7,7 @@ using System.IO; using System.Runtime.InteropServices; using System.Threading; -using Microsoft.Build.Shared; - +using Microsoft.Build.Framework; namespace Microsoft.Build.Tasks.Deployment.Bootstrapper { diff --git a/src/Tasks/Copy.cs b/src/Tasks/Copy.cs index 853fe698800..36d2a3d20f9 100644 --- a/src/Tasks/Copy.cs +++ b/src/Tasks/Copy.cs @@ -847,7 +847,7 @@ private bool InitializeDestinationFiles() foreach (ITaskItem sourceFolder in SourceFolders) { ErrorUtilities.VerifyThrowArgumentLength(sourceFolder.ItemSpec); - AbsolutePath src = FrameworkFileUtilities.NormalizePath(TaskEnvironment.GetAbsolutePath(sourceFolder.ItemSpec)); + AbsolutePath src = FileUtilities.NormalizePath(TaskEnvironment.GetAbsolutePath(sourceFolder.ItemSpec)); string srcName = Path.GetFileName(src); (string[] filesInFolder, _, _, string globFailure) = FileMatcher.Default.GetFiles(src, "**"); diff --git a/src/Tasks/CreateCSharpManifestResourceName.cs b/src/Tasks/CreateCSharpManifestResourceName.cs index 85a5b1b1ec2..c7f838b16ef 100644 --- a/src/Tasks/CreateCSharpManifestResourceName.cs +++ b/src/Tasks/CreateCSharpManifestResourceName.cs @@ -97,13 +97,13 @@ internal static string CreateManifestNameImpl( bool enableCustomCulture = false) { // Use the link file name if there is one, otherwise, fall back to file name. - string embeddedFileName = FrameworkFileUtilities.FixFilePath(linkFileName); + string embeddedFileName = FileUtilities.FixFilePath(linkFileName); if (string.IsNullOrEmpty(embeddedFileName)) { - embeddedFileName = FrameworkFileUtilities.FixFilePath(fileName); + embeddedFileName = FileUtilities.FixFilePath(fileName); } - dependentUponFileName = FrameworkFileUtilities.FixFilePath(dependentUponFileName); + dependentUponFileName = FileUtilities.FixFilePath(dependentUponFileName); Culture.ItemCultureInfo info; if (!string.IsNullOrEmpty(culture) && enableCustomCulture) diff --git a/src/Tasks/CreateVisualBasicManifestResourceName.cs b/src/Tasks/CreateVisualBasicManifestResourceName.cs index 24c59241ee0..d2cf7f405ef 100644 --- a/src/Tasks/CreateVisualBasicManifestResourceName.cs +++ b/src/Tasks/CreateVisualBasicManifestResourceName.cs @@ -102,7 +102,7 @@ internal static string CreateManifestNameImpl( embeddedFileName = fileName; } - dependentUponFileName = FrameworkFileUtilities.FixFilePath(dependentUponFileName); + dependentUponFileName = FileUtilities.FixFilePath(dependentUponFileName); Culture.ItemCultureInfo info; if (!string.IsNullOrEmpty(culture) && enableCustomCulture) diff --git a/src/Tasks/DependencyFile.cs b/src/Tasks/DependencyFile.cs index 0e591aa7f97..a69f6938c05 100644 --- a/src/Tasks/DependencyFile.cs +++ b/src/Tasks/DependencyFile.cs @@ -4,7 +4,6 @@ using System; using System.IO; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; #nullable disable @@ -61,7 +60,7 @@ internal bool Exists /// The file name. internal DependencyFile(string filename) { - this.filename = FrameworkFileUtilities.FixFilePath(filename); + this.filename = FileUtilities.FixFilePath(filename); if (FileSystems.Default.FileExists(FileName)) { diff --git a/src/Tasks/FileIO/WriteLinesToFile.cs b/src/Tasks/FileIO/WriteLinesToFile.cs index 560264ad909..82c6517eb93 100644 --- a/src/Tasks/FileIO/WriteLinesToFile.cs +++ b/src/Tasks/FileIO/WriteLinesToFile.cs @@ -72,7 +72,7 @@ public override bool Execute() } ErrorUtilities.VerifyThrowArgumentLength(File.ItemSpec); - AbsolutePath filePath = FrameworkFileUtilities.NormalizePath(TaskEnvironment.GetAbsolutePath(File.ItemSpec)); + AbsolutePath filePath = FileUtilities.NormalizePath(TaskEnvironment.GetAbsolutePath(File.ItemSpec)); string contentsAsString = string.Empty; if (Lines != null && Lines.Length > 0) diff --git a/src/Tasks/FindInList.cs b/src/Tasks/FindInList.cs index 493990ca1a9..84538f53b6e 100644 --- a/src/Tasks/FindInList.cs +++ b/src/Tasks/FindInList.cs @@ -105,7 +105,7 @@ private bool IsMatchingItem(StringComparison comparison, ITaskItem item) { try { - var path = FrameworkFileUtilities.FixFilePath(item.ItemSpec); + var path = FileUtilities.FixFilePath(item.ItemSpec); string filename = (MatchFileNameOnly ? Path.GetFileName(path) : path); if (String.Equals(filename, ItemSpecToFind, comparison)) diff --git a/src/Tasks/GetFrameworkSDKPath.cs b/src/Tasks/GetFrameworkSDKPath.cs index 0161001d0e8..15e327abb92 100644 --- a/src/Tasks/GetFrameworkSDKPath.cs +++ b/src/Tasks/GetFrameworkSDKPath.cs @@ -63,7 +63,7 @@ public string Path } else { - path = FrameworkFileUtilities.EnsureTrailingSlash(path); + path = FileUtilities.EnsureTrailingSlash(path); Log.LogMessageFromResources(MessageImportance.Low, "GetFrameworkSdkPath.FoundSDK", path); } @@ -110,7 +110,7 @@ public string FrameworkSdkVersion20Path } else { - path = FrameworkFileUtilities.EnsureTrailingSlash(path); + path = FileUtilities.EnsureTrailingSlash(path); Log.LogMessageFromResources(MessageImportance.Low, "GetFrameworkSdkPath.FoundSDK", path); } @@ -153,7 +153,7 @@ public string FrameworkSdkVersion35Path } else { - path = FrameworkFileUtilities.EnsureTrailingSlash(path); + path = FileUtilities.EnsureTrailingSlash(path); Log.LogMessageFromResources(MessageImportance.Low, "GetFrameworkSdkPath.FoundSDK", path); } @@ -196,7 +196,7 @@ public string FrameworkSdkVersion40Path } else { - path = FrameworkFileUtilities.EnsureTrailingSlash(path); + path = FileUtilities.EnsureTrailingSlash(path); Log.LogMessageFromResources(MessageImportance.Low, "GetFrameworkSdkPath.FoundSDK", path); } @@ -239,7 +239,7 @@ public string FrameworkSdkVersion45Path } else { - path = FrameworkFileUtilities.EnsureTrailingSlash(path); + path = FileUtilities.EnsureTrailingSlash(path); Log.LogMessageFromResources(MessageImportance.Low, "GetFrameworkSdkPath.FoundSDK", path); } @@ -282,7 +282,7 @@ public string FrameworkSdkVersion451Path } else { - path = FrameworkFileUtilities.EnsureTrailingSlash(path); + path = FileUtilities.EnsureTrailingSlash(path); Log.LogMessageFromResources(MessageImportance.Low, "GetFrameworkSdkPath.FoundSDK", path); } @@ -325,7 +325,7 @@ public string FrameworkSdkVersion46Path } else { - path = FrameworkFileUtilities.EnsureTrailingSlash(path); + path = FileUtilities.EnsureTrailingSlash(path); Log.LogMessageFromResources(MessageImportance.Low, "GetFrameworkSdkPath.FoundSDK", path); } @@ -368,7 +368,7 @@ public string FrameworkSdkVersion461Path } else { - path = FrameworkFileUtilities.EnsureTrailingSlash(path); + path = FileUtilities.EnsureTrailingSlash(path); Log.LogMessageFromResources(MessageImportance.Low, "GetFrameworkSdkPath.FoundSDK", path); } diff --git a/src/Tasks/GetSDKReferenceFiles.cs b/src/Tasks/GetSDKReferenceFiles.cs index 6099546f9af..d1786ad7f1f 100644 --- a/src/Tasks/GetSDKReferenceFiles.cs +++ b/src/Tasks/GetSDKReferenceFiles.cs @@ -567,7 +567,7 @@ private void GenerateOutputItems() /// private void GatherReferenceAssemblies(HashSet resolvedFiles, ITaskItem sdkReference, string path, SDKInfo info) { - if (info.DirectoryToFileList != null && info.DirectoryToFileList.TryGetValue(FrameworkFileUtilities.EnsureNoTrailingSlash(path), out List referenceFiles) && referenceFiles != null) + if (info.DirectoryToFileList != null && info.DirectoryToFileList.TryGetValue(FileUtilities.EnsureNoTrailingSlash(path), out List referenceFiles) && referenceFiles != null) { foreach (string file in referenceFiles) { @@ -619,7 +619,7 @@ private void GatherRedistFiles(HashSet resolvedRedistFiles, foreach (KeyValuePair> directoryToFileList in info.DirectoryToFileList) { // Add a trailing slash to ensure we don't match the start of a platform (e.g. ...\ARM matching ...\ARM64) - if (FrameworkFileUtilities.EnsureTrailingSlash(directoryToFileList.Key).StartsWith(FrameworkFileUtilities.EnsureTrailingSlash(redistFilePath), StringComparison.OrdinalIgnoreCase)) + if (FileUtilities.EnsureTrailingSlash(directoryToFileList.Key).StartsWith(FileUtilities.EnsureTrailingSlash(redistFilePath), StringComparison.OrdinalIgnoreCase)) { List redistFiles = directoryToFileList.Value; string targetPathRoot = sdkReference.GetMetadata("CopyRedistToSubDirectory"); diff --git a/src/Tasks/ListOperators/FindUnderPath.cs b/src/Tasks/ListOperators/FindUnderPath.cs index e7370e40a39..46d280dd332 100644 --- a/src/Tasks/ListOperators/FindUnderPath.cs +++ b/src/Tasks/ListOperators/FindUnderPath.cs @@ -67,16 +67,16 @@ public override bool Execute() { conePath = Strings.WeakIntern( - TaskEnvironment.GetAbsolutePath(FrameworkFileUtilities.FixFilePath(Path.ItemSpec)).GetCanonicalForm()); + TaskEnvironment.GetAbsolutePath(FileUtilities.FixFilePath(Path.ItemSpec)).GetCanonicalForm()); } else { conePath = Strings.WeakIntern( - System.IO.Path.GetFullPath(TaskEnvironment.GetAbsolutePath(FrameworkFileUtilities.FixFilePath(Path.ItemSpec)))); + System.IO.Path.GetFullPath(TaskEnvironment.GetAbsolutePath(FileUtilities.FixFilePath(Path.ItemSpec)))); } - conePath = FrameworkFileUtilities.EnsureTrailingSlash(conePath); + conePath = FileUtilities.EnsureTrailingSlash(conePath); } catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) { @@ -98,13 +98,13 @@ public override bool Execute() { fullPath = Strings.WeakIntern( - TaskEnvironment.GetAbsolutePath(FrameworkFileUtilities.FixFilePath(item.ItemSpec)).GetCanonicalForm()); + TaskEnvironment.GetAbsolutePath(FileUtilities.FixFilePath(item.ItemSpec)).GetCanonicalForm()); } else { fullPath = Strings.WeakIntern( - System.IO.Path.GetFullPath(TaskEnvironment.GetAbsolutePath(FrameworkFileUtilities.FixFilePath(item.ItemSpec)))); + System.IO.Path.GetFullPath(TaskEnvironment.GetAbsolutePath(FileUtilities.FixFilePath(item.ItemSpec)))); } } catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) diff --git a/src/Tasks/MakeDir.cs b/src/Tasks/MakeDir.cs index bf0c67ffa95..b16b810d5b2 100644 --- a/src/Tasks/MakeDir.cs +++ b/src/Tasks/MakeDir.cs @@ -63,7 +63,7 @@ public override bool Execute() // For speed, eliminate duplicates caused by poor targets authoring, don't absolutize yet to save allocation if (!directoriesSet.Contains(directory.ItemSpec)) { - absolutePath = TaskEnvironment.GetAbsolutePath(FrameworkFileUtilities.FixFilePath(directory.ItemSpec)); + absolutePath = TaskEnvironment.GetAbsolutePath(FileUtilities.FixFilePath(directory.ItemSpec)); // Only log a message if we actually need to create the folder if (!FileUtilities.DirectoryExistsNoThrow(absolutePath)) { diff --git a/src/Tasks/ManifestUtil/ManifestWriter.cs b/src/Tasks/ManifestUtil/ManifestWriter.cs index 71d3a1478b6..13dd871912b 100644 --- a/src/Tasks/ManifestUtil/ManifestWriter.cs +++ b/src/Tasks/ManifestUtil/ManifestWriter.cs @@ -6,7 +6,7 @@ using System.IO; using System.Runtime.InteropServices; using System.Xml.Serialization; -using Microsoft.Build.Shared; +using Microsoft.Build.Framework; #nullable disable diff --git a/src/Tasks/ManifestUtil/Util.cs b/src/Tasks/ManifestUtil/Util.cs index a2d89b437c7..22d48fab01c 100644 --- a/src/Tasks/ManifestUtil/Util.cs +++ b/src/Tasks/ManifestUtil/Util.cs @@ -15,7 +15,6 @@ using System.Security.Cryptography; using System.Text; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; using Microsoft.Win32; diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj index a4975e7435f..2f01d8ac5e5 100644 --- a/src/Tasks/Microsoft.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.Build.Tasks.csproj @@ -111,7 +111,6 @@ TaskLoggingHelperExtension.cs - MetadataConversionUtilities.cs @@ -121,9 +120,6 @@ ExceptionHandling.cs - - FileUtilities.cs - StringUtils.cs diff --git a/src/Tasks/RedistList.cs b/src/Tasks/RedistList.cs index d89440d05aa..91fc81d0921 100644 --- a/src/Tasks/RedistList.cs +++ b/src/Tasks/RedistList.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Text; using System.Xml; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; using Microsoft.Build.Utilities; diff --git a/src/Tasks/ResGenDependencies.cs b/src/Tasks/ResGenDependencies.cs index 4c329b8824c..8e3811d00da 100644 --- a/src/Tasks/ResGenDependencies.cs +++ b/src/Tasks/ResGenDependencies.cs @@ -8,14 +8,13 @@ #if FEATURE_RESXREADER_LIVEDESERIALIZATION using System.Collections; using System.Resources; -using Microsoft.Build.Shared; #endif using System.Xml; using Microsoft.Build.BackEnd; +using Microsoft.Build.Framework; using Microsoft.Build.Shared.FileSystem; using Microsoft.Build.Tasks.ResourceHandling; using Microsoft.Build.Utilities; -using Microsoft.Build.Framework; #nullable disable @@ -371,7 +370,7 @@ internal bool AllOutputFilesAreUpToDate() Debug.Assert(outputFiles != null, "OutputFiles hasn't been set"); foreach (string outputFileName in outputFiles) { - var outputFile = new FileInfo(FrameworkFileUtilities.FixFilePath(outputFileName)); + var outputFile = new FileInfo(FileUtilities.FixFilePath(outputFileName)); if (!outputFile.Exists || outputFile.LastWriteTime < LastModified) { return false; diff --git a/src/Tasks/ResolveSDKReference.cs b/src/Tasks/ResolveSDKReference.cs index 7b759a91a79..9d2c288a93e 100644 --- a/src/Tasks/ResolveSDKReference.cs +++ b/src/Tasks/ResolveSDKReference.cs @@ -916,7 +916,7 @@ public void Resolve(Dictionary sdks, string targetConfigurati _prefer32BitFromProject = prefer32Bit; // There must be a trailing slash or else the ExpandSDKReferenceAssemblies will not work. - ResolvedPath = FrameworkFileUtilities.EnsureTrailingSlash(sdk.ItemSpec); + ResolvedPath = FileUtilities.EnsureTrailingSlash(sdk.ItemSpec); System.Version.TryParse(sdk.GetMetadata(SDKPlatformVersion), out Version targetPlatformVersionFromItem); diff --git a/src/Tasks/ResourceHandling/MSBuildResXReader.cs b/src/Tasks/ResourceHandling/MSBuildResXReader.cs index 9de27e4c4b7..b4166560bfe 100644 --- a/src/Tasks/ResourceHandling/MSBuildResXReader.cs +++ b/src/Tasks/ResourceHandling/MSBuildResXReader.cs @@ -229,7 +229,7 @@ private static void AddLinkedResource(string resxFilename, bool pathsRelativeToB { string[] fileRefInfo = ParseResxFileRefString(value); - string fileName = FrameworkFileUtilities.FixFilePath(fileRefInfo[0]); + string fileName = FileUtilities.FixFilePath(fileRefInfo[0]); string fileRefType = fileRefInfo[1]; if (pathsRelativeToBasePath) diff --git a/src/Tasks/Touch.cs b/src/Tasks/Touch.cs index 7e1e68f175c..3ca82c7c951 100644 --- a/src/Tasks/Touch.cs +++ b/src/Tasks/Touch.cs @@ -98,7 +98,7 @@ internal bool ExecuteImpl( AbsolutePath path; try { - path = TaskEnvironment.GetAbsolutePath(FrameworkFileUtilities.FixFilePath(file.ItemSpec)); + path = TaskEnvironment.GetAbsolutePath(FileUtilities.FixFilePath(file.ItemSpec)); } catch (ArgumentException ex) { diff --git a/src/Tasks/Unzip.cs b/src/Tasks/Unzip.cs index b45d66de1a4..fd262b1007c 100644 --- a/src/Tasks/Unzip.cs +++ b/src/Tasks/Unzip.cs @@ -176,7 +176,7 @@ public override bool Execute() /// The to extract files to. private void Extract(ZipArchive sourceArchive, DirectoryInfo destinationDirectory) { - string fullDestinationDirectoryPath = Path.GetFullPath(FrameworkFileUtilities.EnsureTrailingSlash(destinationDirectory.FullName)); + string fullDestinationDirectoryPath = Path.GetFullPath(FileUtilities.EnsureTrailingSlash(destinationDirectory.FullName)); foreach (ZipArchiveEntry zipArchiveEntry in sourceArchive.Entries.TakeWhile(i => !_cancellationToken.IsCancellationRequested)) { diff --git a/src/UnitTests.Shared/RequiresSymbolicLinksFactAttribute.cs b/src/UnitTests.Shared/RequiresSymbolicLinksFactAttribute.cs index 8f98ab38e0a..f824ac9d495 100644 --- a/src/UnitTests.Shared/RequiresSymbolicLinksFactAttribute.cs +++ b/src/UnitTests.Shared/RequiresSymbolicLinksFactAttribute.cs @@ -3,7 +3,7 @@ using System; using System.IO; - +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Xunit; diff --git a/src/Utilities.UnitTests/CommandLineBuilder_Tests.cs b/src/Utilities.UnitTests/CommandLineBuilder_Tests.cs index 7f07523a3dc..ae1686a23ca 100644 --- a/src/Utilities.UnitTests/CommandLineBuilder_Tests.cs +++ b/src/Utilities.UnitTests/CommandLineBuilder_Tests.cs @@ -108,11 +108,11 @@ public void AppendLiteralSwitchWithSpacesInParameter() public void AppendTwoStringsEnsureNoSpace() { CommandLineBuilder c = new CommandLineBuilder(); - c.AppendFileNamesIfNotNull(new[] { "Form1.resx", FrameworkFileUtilities.FixFilePath("built\\Form1.resources") }, ","); + c.AppendFileNamesIfNotNull(new[] { "Form1.resx", FileUtilities.FixFilePath("built\\Form1.resources") }, ","); // There shouldn't be a space before or after the comma // Tools like resgen require comma-delimited lists to be bumped up next to each other. - c.ShouldBe(FrameworkFileUtilities.FixFilePath(@"Form1.resx,built\Form1.resources")); + c.ShouldBe(FileUtilities.FixFilePath(@"Form1.resx,built\Form1.resources")); } /* diff --git a/src/Utilities.UnitTests/PlatformManifest_Tests.cs b/src/Utilities.UnitTests/PlatformManifest_Tests.cs index 2a0cdb3360d..2b313ef8249 100644 --- a/src/Utilities.UnitTests/PlatformManifest_Tests.cs +++ b/src/Utilities.UnitTests/PlatformManifest_Tests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Utilities; using Shouldly; diff --git a/src/Utilities.UnitTests/ToolLocationHelper_Tests.cs b/src/Utilities.UnitTests/ToolLocationHelper_Tests.cs index a1b959497c8..d18d9a48ff6 100644 --- a/src/Utilities.UnitTests/ToolLocationHelper_Tests.cs +++ b/src/Utilities.UnitTests/ToolLocationHelper_Tests.cs @@ -2198,7 +2198,7 @@ public void GetPathToStandardLibraries64Bit40() } string pathToFramework = ToolLocationHelper.GetPathToStandardLibraries(".NetFramework", "v4.0", string.Empty, "x86"); - string dotNet40Path = FrameworkFileUtilities.EnsureNoTrailingSlash(referencePaths[0]); + string dotNet40Path = FileUtilities.EnsureNoTrailingSlash(referencePaths[0]); pathToFramework.ShouldBe(dotNet40Path, StringCompareShould.IgnoreCase); pathToFramework = ToolLocationHelper.GetPathToStandardLibraries(".NetFramework", "v4.0", string.Empty, "x64"); @@ -2281,7 +2281,7 @@ public void GetPathToStandardLibraries32Bit40() } string pathToFramework = ToolLocationHelper.GetPathToStandardLibraries(".NetFramework", "v4.0", string.Empty, "x86"); - string dotNet40Path = FrameworkFileUtilities.EnsureNoTrailingSlash(referencePaths[0]); + string dotNet40Path = FileUtilities.EnsureNoTrailingSlash(referencePaths[0]); pathToFramework.ShouldBe(dotNet40Path, StringCompareShould.IgnoreCase); pathToFramework = ToolLocationHelper.GetPathToStandardLibraries(".NetFramework", "v4.0", string.Empty, "x64"); diff --git a/src/Utilities.UnitTests/TrackedDependencies/FileTrackerTests.cs b/src/Utilities.UnitTests/TrackedDependencies/FileTrackerTests.cs index f5cdd7f1e79..6eae1c71a1a 100644 --- a/src/Utilities.UnitTests/TrackedDependencies/FileTrackerTests.cs +++ b/src/Utilities.UnitTests/TrackedDependencies/FileTrackerTests.cs @@ -900,12 +900,12 @@ public void FileTrackerFileIsExcludedFromDependencies() // The short path to temp string tempShortPath = NativeMethodsShared.IsUnixLike ? tempPath - : FrameworkFileUtilities.EnsureTrailingSlash( + : FileUtilities.EnsureTrailingSlash( NativeMethodsShared.GetShortFilePath(tempPath).ToUpperInvariant()); // The long path to temp string tempLongPath = NativeMethodsShared.IsUnixLike ? tempPath - : FrameworkFileUtilities.EnsureTrailingSlash( + : FileUtilities.EnsureTrailingSlash( NativeMethodsShared.GetLongFilePath(tempPath).ToUpperInvariant()); // We don't want to be including these as dependencies or outputs: diff --git a/src/Utilities/CommandLineBuilder.cs b/src/Utilities/CommandLineBuilder.cs index 481dc35c62e..07ac12544e9 100644 --- a/src/Utilities/CommandLineBuilder.cs +++ b/src/Utilities/CommandLineBuilder.cs @@ -336,7 +336,7 @@ protected void AppendFileNameWithQuoting(string fileName) // their own quotes. Quotes are illegal. VerifyThrowNoEmbeddedDoubleQuotes(string.Empty, fileName); - fileName = FrameworkFileUtilities.FixFilePath(fileName); + fileName = FileUtilities.FixFilePath(fileName); if (fileName.Length != 0 && fileName[0] == '-') { AppendTextWithQuoting("." + Path.DirectorySeparatorChar + fileName); diff --git a/src/Utilities/Microsoft.Build.Utilities.csproj b/src/Utilities/Microsoft.Build.Utilities.csproj index ed442c49c23..ea53f266bf8 100644 --- a/src/Utilities/Microsoft.Build.Utilities.csproj +++ b/src/Utilities/Microsoft.Build.Utilities.csproj @@ -75,9 +75,6 @@ Shared\ExceptionHandling.cs - - Shared\FileUtilities.cs - Shared\FileMatcher.cs @@ -111,9 +108,6 @@ Shared\TaskLoggingHelper.cs - - Shared\TempFileUtilities.cs - Shared\Tracing.cs diff --git a/src/Utilities/TargetPlatformSDK.cs b/src/Utilities/TargetPlatformSDK.cs index ea7b3408cd7..3aa41ed671e 100644 --- a/src/Utilities/TargetPlatformSDK.cs +++ b/src/Utilities/TargetPlatformSDK.cs @@ -105,7 +105,7 @@ public Version MinOSVersion public string Path { get => _path; - set => _path = value != null ? FrameworkFileUtilities.EnsureTrailingSlash(value) : null; + set => _path = value != null ? FileUtilities.EnsureTrailingSlash(value) : null; } /// diff --git a/src/Utilities/TaskItem.cs b/src/Utilities/TaskItem.cs index c2b9cb60219..2cfb2f9d97b 100644 --- a/src/Utilities/TaskItem.cs +++ b/src/Utilities/TaskItem.cs @@ -103,7 +103,7 @@ public TaskItem( { ErrorUtilities.VerifyThrowArgumentNull(itemSpec); - _itemSpec = treatAsFilePath ? FrameworkFileUtilities.FixFilePath(itemSpec) : itemSpec; + _itemSpec = treatAsFilePath ? FileUtilities.FixFilePath(itemSpec) : itemSpec; } /// @@ -185,7 +185,7 @@ public string ItemSpec { ErrorUtilities.VerifyThrowArgumentNull(value, nameof(ItemSpec)); - _itemSpec = FrameworkFileUtilities.FixFilePath(value); + _itemSpec = FileUtilities.FixFilePath(value); _fullPath = null; } } @@ -204,7 +204,7 @@ string ITaskItem2.EvaluatedIncludeEscaped set { - _itemSpec = FrameworkFileUtilities.FixFilePath(value); + _itemSpec = FileUtilities.FixFilePath(value); _fullPath = null; } } diff --git a/src/Utilities/ToolLocationHelper.cs b/src/Utilities/ToolLocationHelper.cs index 9e7ebbf82cd..d84f3954661 100644 --- a/src/Utilities/ToolLocationHelper.cs +++ b/src/Utilities/ToolLocationHelper.cs @@ -664,7 +664,7 @@ public static IList GetSDKReferenceFolders(string sdkRoot, string target string legacyWindowsMetadataLocation = Path.Combine(sdkRoot, "Windows Metadata"); if (FileUtilities.DirectoryExistsNoThrow(legacyWindowsMetadataLocation)) { - legacyWindowsMetadataLocation = FrameworkFileUtilities.EnsureTrailingSlash(legacyWindowsMetadataLocation); + legacyWindowsMetadataLocation = FileUtilities.EnsureTrailingSlash(legacyWindowsMetadataLocation); referenceDirectories.Add(legacyWindowsMetadataLocation); } @@ -1767,7 +1767,7 @@ public static string GetPathToStandardLibraries(string targetFrameworkIdentifier { // We found the framework reference assembly directory with mscorlib in it // that's our standard lib path, so return it, with no trailing slash. - return FrameworkFileUtilities.EnsureNoTrailingSlash(referenceAssemblyDirectory); + return FileUtilities.EnsureNoTrailingSlash(referenceAssemblyDirectory); } } @@ -1841,7 +1841,7 @@ public static string GetPathToStandardLibraries(string targetFrameworkIdentifier { // We found the framework reference assembly directory with mscorlib in it // that's our standard lib path, so return it, with no trailing slash. - return FrameworkFileUtilities.EnsureNoTrailingSlash(legacyMsCorlib20Path); + return FileUtilities.EnsureNoTrailingSlash(legacyMsCorlib20Path); } // If for some reason the 2.0 framework is not installed in its default location then maybe someone is using the ".net 4.0" reference assembly @@ -1858,7 +1858,7 @@ public static string GetPathToStandardLibraries(string targetFrameworkIdentifier { // We found the framework reference assembly directory with mscorlib in it // that's our standard lib path, so return it, with no trailing slash. - return FrameworkFileUtilities.EnsureNoTrailingSlash(referenceAssemblyDirectory); + return FileUtilities.EnsureNoTrailingSlash(referenceAssemblyDirectory); } } @@ -2427,7 +2427,7 @@ private static void AddSDKPath(string sdkRoot, string contentFolderName, string if (FileUtilities.DirectoryExistsNoThrow(referenceAssemblyPath)) { - referenceAssemblyPath = FrameworkFileUtilities.EnsureTrailingSlash(referenceAssemblyPath); + referenceAssemblyPath = FileUtilities.EnsureTrailingSlash(referenceAssemblyPath); contentDirectories.Add(referenceAssemblyPath); } } @@ -2553,7 +2553,7 @@ internal static void GatherExtensionSDKs(DirectoryInfo extensionSdksDirectory, T string pathToSDKManifest = Path.Combine(sdkVersionDirectory.FullName, "SDKManifest.xml"); if (FileUtilities.FileExistsNoThrow(pathToSDKManifest)) { - targetPlatformSDK.ExtensionSDKs.Add(SDKKey, FrameworkFileUtilities.EnsureTrailingSlash(sdkVersionDirectory.FullName)); + targetPlatformSDK.ExtensionSDKs.Add(SDKKey, FileUtilities.EnsureTrailingSlash(sdkVersionDirectory.FullName)); } else { @@ -2826,7 +2826,7 @@ internal static void GatherSDKsFromRegistryImpl(Dictionary - private static readonly string s_tempPath = FrameworkFileUtilities.EnsureTrailingSlash(Path.GetTempPath()); + private static readonly string s_tempPath = FileUtilities.EnsureTrailingSlash(Path.GetTempPath()); // The short path to temp - private static readonly string s_tempShortPath = FrameworkFileUtilities.EnsureTrailingSlash(NativeMethodsShared.GetShortFilePath(s_tempPath).ToUpperInvariant()); + private static readonly string s_tempShortPath = FileUtilities.EnsureTrailingSlash(NativeMethodsShared.GetShortFilePath(s_tempPath).ToUpperInvariant()); // The long path to temp - private static readonly string s_tempLongPath = FrameworkFileUtilities.EnsureTrailingSlash(NativeMethodsShared.GetLongFilePath(s_tempPath).ToUpperInvariant()); + private static readonly string s_tempLongPath = FileUtilities.EnsureTrailingSlash(NativeMethodsShared.GetLongFilePath(s_tempPath).ToUpperInvariant()); // The path to ApplicationData (is equal to %USERPROFILE%\Application Data folder in Windows XP and %USERPROFILE%\AppData\Roaming in Vista and later) - private static readonly string s_applicationDataPath = FrameworkFileUtilities.EnsureTrailingSlash(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData).ToUpperInvariant()); + private static readonly string s_applicationDataPath = FileUtilities.EnsureTrailingSlash(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData).ToUpperInvariant()); // The path to LocalApplicationData (is equal to %USERPROFILE%\Local Settings\Application Data folder in Windows XP and %USERPROFILE%\AppData\Local in Vista and later). - private static readonly string s_localApplicationDataPath = FrameworkFileUtilities.EnsureTrailingSlash(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData).ToUpperInvariant()); + private static readonly string s_localApplicationDataPath = FileUtilities.EnsureTrailingSlash(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData).ToUpperInvariant()); // The path to the LocalLow folder. In Vista and later, user application data is organized across %USERPROFILE%\AppData\LocalLow, %USERPROFILE%\AppData\Local (%LOCALAPPDATA%) // and %USERPROFILE%\AppData\Roaming (%APPDATA%). The LocalLow folder is not present in XP. - private static readonly string s_localLowApplicationDataPath = FrameworkFileUtilities.EnsureTrailingSlash(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "AppData\\LocalLow").ToUpperInvariant()); + private static readonly string s_localLowApplicationDataPath = FileUtilities.EnsureTrailingSlash(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "AppData\\LocalLow").ToUpperInvariant()); // The path to the common Application Data, which is also used by some programs (e.g. antivirus) that we wish to ignore. // Is equal to C:\Documents and Settings\All Users\Application Data on XP, and C:\ProgramData on Vista+. @@ -127,18 +127,18 @@ private static List InitializeCommonApplicationDataPaths() { List commonApplicationDataPaths = new(); - string defaultCommonApplicationDataPath = FrameworkFileUtilities.EnsureTrailingSlash(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData).ToUpperInvariant()); + string defaultCommonApplicationDataPath = FileUtilities.EnsureTrailingSlash(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData).ToUpperInvariant()); commonApplicationDataPaths.Add(defaultCommonApplicationDataPath); string defaultRootDirectory = Path.GetPathRoot(defaultCommonApplicationDataPath); - string alternativeCommonApplicationDataPath1 = FrameworkFileUtilities.EnsureTrailingSlash(Path.Combine(defaultRootDirectory, @"Documents and Settings\All Users\Application Data").ToUpperInvariant()); + string alternativeCommonApplicationDataPath1 = FileUtilities.EnsureTrailingSlash(Path.Combine(defaultRootDirectory, @"Documents and Settings\All Users\Application Data").ToUpperInvariant()); if (!alternativeCommonApplicationDataPath1.Equals(defaultCommonApplicationDataPath, StringComparison.Ordinal)) { commonApplicationDataPaths.Add(alternativeCommonApplicationDataPath1); } - string alternativeCommonApplicationDataPath2 = FrameworkFileUtilities.EnsureTrailingSlash(Path.Combine(defaultRootDirectory, @"Users\All Users\Application Data").ToUpperInvariant()); + string alternativeCommonApplicationDataPath2 = FileUtilities.EnsureTrailingSlash(Path.Combine(defaultRootDirectory, @"Users\All Users\Application Data").ToUpperInvariant()); if (!alternativeCommonApplicationDataPath2.Equals(defaultCommonApplicationDataPath, StringComparison.Ordinal)) { @@ -281,7 +281,7 @@ public static bool FileIsUnderPath(string fileName, string path) // Ensure that the path has a trailing slash that we are checking under // By default the paths that we check for most often will have, so this will // return fast and not allocate memory in the process - return FileIsUnderNormalizedPath(fileName, FrameworkFileUtilities.EnsureTrailingSlash(path)); + return FileIsUnderNormalizedPath(fileName, FileUtilities.EnsureTrailingSlash(path)); } internal static bool FileIsUnderNormalizedPath(string fileName, string path) @@ -615,7 +615,7 @@ public static string TrackerResponseFileArguments(string dllName, string interme { intermediateDirectory = FileUtilities.NormalizePath(intermediateDirectory); // If the intermediate directory ends up with a trailing slash, then be rid of it! - if (FrameworkFileUtilities.EndsWithSlash(intermediateDirectory)) + if (FileUtilities.EndsWithSlash(intermediateDirectory)) { intermediateDirectory = Path.GetDirectoryName(intermediateDirectory); } diff --git a/src/Utilities/TrackedDependencies/FlatTrackingData.cs b/src/Utilities/TrackedDependencies/FlatTrackingData.cs index e12cee660ff..633dc4c02ac 100644 --- a/src/Utilities/TrackedDependencies/FlatTrackingData.cs +++ b/src/Utilities/TrackedDependencies/FlatTrackingData.cs @@ -323,7 +323,7 @@ private void InternalConstruct(ITask ownerTask, ITaskItem[] tlogFilesLocal, ITas // our "starts with" comparison doesn't pick up incomplete matches, such as C:\Foo matching C:\FooFile.txt foreach (string excludePath in excludedInputPaths) { - string fullexcludePath = FrameworkFileUtilities.EnsureTrailingSlash(FileUtilities.NormalizePath(excludePath)).ToUpperInvariant(); + string fullexcludePath = FileUtilities.EnsureTrailingSlash(FileUtilities.NormalizePath(excludePath)).ToUpperInvariant(); _excludedInputPaths.Add(fullexcludePath); } } From 2a69cd0a9903af45a568be16e3c0ea4b19657bb3 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 10 Mar 2026 13:24:47 -0700 Subject: [PATCH 18/27] Update ExceptionHandling usages to DebugUtils Update usages of ExceptionHandling members that were delegating to DebugUtills to just use DebugUtils directly. --- .../BackEnd/DebugUtils_tests.cs | 8 +++--- .../BackEnd/TaskExecutionHost_Tests.cs | 6 ++-- .../BackEnd/BuildManager/BuildManager.cs | 10 +++---- .../BuildRequestEngine/BuildRequestEngine.cs | 2 +- .../Communications/NodeEndpointInProc.cs | 4 +-- .../Components/Logging/EventSourceSink.cs | 6 ++-- .../Components/Logging/LoggingService.cs | 3 +- .../RequestBuilder/RequestBuilder.cs | 3 +- src/Build/BackEnd/Node/InProcNode.cs | 3 +- src/MSBuild/XMake.cs | 2 +- src/Shared/ExceptionHandling.cs | 28 ------------------- src/Shared/NodeEndpointOutOfProcBase.cs | 16 +++++------ src/Shared/NodePipeServer.cs | 3 +- src/Shared/TaskLoader.cs | 3 +- 14 files changed, 37 insertions(+), 60 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/DebugUtils_tests.cs b/src/Build.UnitTests/BackEnd/DebugUtils_tests.cs index c10a0600e87..cd8e07ac4fd 100644 --- a/src/Build.UnitTests/BackEnd/DebugUtils_tests.cs +++ b/src/Build.UnitTests/BackEnd/DebugUtils_tests.cs @@ -18,15 +18,15 @@ public class DebugUtils_Tests [Fact] public void DumpExceptionToFileShouldWriteInDebugDumpPath() { - ExceptionHandling.ResetDebugDumpPathInRunningTests = true; - var exceptionFilesBefore = Directory.GetFiles(ExceptionHandling.DebugDumpPath, "MSBuild_*failure.txt"); + DebugUtils.ResetDebugDumpPathInRunningTests = true; + var exceptionFilesBefore = Directory.GetFiles(DebugUtils.DebugDumpPath, "MSBuild_*failure.txt"); string[] exceptionFiles = null; try { - ExceptionHandling.DumpExceptionToFile(new Exception("hello world")); - exceptionFiles = Directory.GetFiles(ExceptionHandling.DebugDumpPath, "MSBuild_*failure.txt"); + DebugUtils.DumpExceptionToFile(new Exception("hello world")); + exceptionFiles = Directory.GetFiles(DebugUtils.DebugDumpPath, "MSBuild_*failure.txt"); } finally { diff --git a/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs b/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs index 6b11c8b62fd..cedfcf48166 100644 --- a/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs @@ -1093,11 +1093,11 @@ public void TaskExceptionHandlingTest(Type exceptionType, bool isCritical) ml.AssertLogDoesntContain(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("UnhandledMSBuildError", string.Empty)); ml.AssertLogContains(testExceptionMessage); - File.Exists(ExceptionHandling.DumpFilePath).ShouldBe(isCritical, - $"{ExceptionHandling.DumpFilePath} expected to exist: {isCritical}"); + File.Exists(DebugUtils.DumpFilePath).ShouldBe(isCritical, $"{DebugUtils.DumpFilePath} expected to exist: {isCritical}"); + if (isCritical) { - FileUtilities.DeleteNoThrow(ExceptionHandling.DumpFilePath); + FileUtilities.DeleteNoThrow(DebugUtils.DumpFilePath); } // Reset DebugPath to not affect other tests diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 8e7efc9c055..4d66c10a09f 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -1231,7 +1231,7 @@ private void EndBuildTelemetry() /// private void RecordCrashTelemetry(Exception exception, bool isUnhandled) { - string? host = _buildTelemetry?.BuildEngineHost ?? BuildEnvironmentState.GetHostName(); + string? host = _buildTelemetry?.BuildEngineHost ?? BuildEnvironmentState.GetHostName(); int? activeNodeCount; int? submissionCount; @@ -1766,7 +1766,7 @@ private void ProcessWorkQueue(Action action) { // On the off chance we get an exception from our exception handler (oh, the irony!), we want to know about it (and still not kill this block // which could lead to a somewhat mysterious hang.) - ExceptionHandling.DumpExceptionToFile(e); + DebugUtils.DumpExceptionToFile(e); } } @@ -2669,8 +2669,8 @@ private void HandleNodeShutdown(int node, NodeShutdown shutdownPacket) foreach (BuildSubmissionBase submission in _buildSubmissions.Values) { BuildEventContext buildEventContext = new BuildEventContext(submission.SubmissionId, BuildEventContext.InvalidNodeId, BuildEventContext.InvalidProjectInstanceId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId); - string exception = ExceptionHandling.ReadAnyExceptionFromFile(_instantiationTimeUtc); - loggingService?.LogError(buildEventContext, new BuildEventFileInfo(string.Empty) /* no project file */, "ChildExitedPrematurely", node, ExceptionHandling.DebugDumpPath, exception); + string exception = DebugUtils.ReadAnyExceptionFromFile(_instantiationTimeUtc); + loggingService?.LogError(buildEventContext, new BuildEventFileInfo(string.Empty) /* no project file */, "ChildExitedPrematurely", node, DebugUtils.DebugDumpPath, exception); } } else if (shutdownPacket.Reason == NodeShutdownReason.Error && _buildSubmissions.Values.Count == 0) @@ -2679,7 +2679,7 @@ private void HandleNodeShutdown(int node, NodeShutdown shutdownPacket) if (shutdownPacket.Exception != null) { ILoggingService loggingService = ((IBuildComponentHost)this).GetComponent(BuildComponentType.LoggingService); - loggingService?.LogError(BuildEventContext.Invalid, new BuildEventFileInfo(string.Empty) /* no project file */, "ChildExitedPrematurely", node, ExceptionHandling.DebugDumpPath, shutdownPacket.Exception.ToString()); + loggingService?.LogError(BuildEventContext.Invalid, new BuildEventFileInfo(string.Empty) /* no project file */, "ChildExitedPrematurely", node, DebugUtils.DebugDumpPath, shutdownPacket.Exception.ToString()); OnThreadException(shutdownPacket.Exception); } } diff --git a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs index 8c66af36507..616075f775a 100644 --- a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs +++ b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs @@ -1439,7 +1439,7 @@ private void QueueAction(Action action, bool isLastTask) // Dump all engine exceptions to a temp file // so that we have something to go on in the // event of a failure - ExceptionHandling.DumpExceptionToFile(e); + DebugUtils.DumpExceptionToFile(e); // Raise the exception to the host, so that it can signal termination of the build. RaiseEngineException(e); diff --git a/src/Build/BackEnd/Components/Communications/NodeEndpointInProc.cs b/src/Build/BackEnd/Components/Communications/NodeEndpointInProc.cs index 36805407d21..a41eb93edd2 100644 --- a/src/Build/BackEnd/Components/Communications/NodeEndpointInProc.cs +++ b/src/Build/BackEnd/Components/Communications/NodeEndpointInProc.cs @@ -8,7 +8,7 @@ #endif using System.Threading; using Microsoft.Build.Shared; - +using Microsoft.Build.Shared.Debugging; using BuildParameters = Microsoft.Build.Execution.BuildParameters; #nullable disable @@ -462,7 +462,7 @@ private void PacketPumpProc() // Dump all engine exceptions to a temp file // so that we have something to go on in the // event of a failure - ExceptionHandling.DumpExceptionToFile(e); + DebugUtils.DumpExceptionToFile(e); throw; } } diff --git a/src/Build/BackEnd/Components/Logging/EventSourceSink.cs b/src/Build/BackEnd/Components/Logging/EventSourceSink.cs index cfa0640c7c0..989db6b6fa0 100644 --- a/src/Build/BackEnd/Components/Logging/EventSourceSink.cs +++ b/src/Build/BackEnd/Components/Logging/EventSourceSink.cs @@ -8,7 +8,7 @@ using Microsoft.Build.Framework; using Microsoft.Build.Framework.Telemetry; using Microsoft.Build.Shared; - +using Microsoft.Build.Shared.Debugging; using InternalLoggerException = Microsoft.Build.Exceptions.InternalLoggerException; namespace Microsoft.Build.BackEnd.Logging @@ -421,7 +421,7 @@ private void RaiseAnyEvent(BuildEventArgs buildEvent) // We ought to dump this further up the stack, but if for example a task is logging an event within a // catch(Exception) block and not rethrowing it, there's the possibility that this exception could // just get silently eaten. So better to have duplicates than to not log the problem at all. :) - ExceptionHandling.DumpExceptionToFile(exception); + DebugUtils.DumpExceptionToFile(exception); throw; } @@ -431,7 +431,7 @@ private void RaiseAnyEvent(BuildEventArgs buildEvent) // We ought to dump this further up the stack, but if for example a task is logging an event within a // catch(Exception) block and not rethrowing it, there's the possibility that this exception could // just get silently eaten. So better to have duplicates than to not log the problem at all. :) - ExceptionHandling.DumpExceptionToFile(exception); + DebugUtils.DumpExceptionToFile(exception); if (ExceptionHandling.IsCriticalException(exception)) { diff --git a/src/Build/BackEnd/Components/Logging/LoggingService.cs b/src/Build/BackEnd/Components/Logging/LoggingService.cs index 24d8dcb0944..1fbf8e00cdb 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingService.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingService.cs @@ -14,6 +14,7 @@ using Microsoft.Build.Framework; using Microsoft.Build.Logging; using Microsoft.Build.Shared; +using Microsoft.Build.Shared.Debugging; using InternalLoggerException = Microsoft.Build.Exceptions.InternalLoggerException; using LoggerDescription = Microsoft.Build.Logging.LoggerDescription; @@ -1530,7 +1531,7 @@ private void LoggingEventProcessor(object loggingEvent) // Dump all engine exceptions to a temp file // so that we have something to go on in the // event of a failure - ExceptionHandling.DumpExceptionToFile(e); + DebugUtils.DumpExceptionToFile(e); // Catch all exceptions in order to pass them over to the engine thread. Due to // hosts expecting to get logger exceptions on the same thread the engine was called from. diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index 2709e004fcb..409ee34f360 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -21,6 +21,7 @@ using Microsoft.Build.Framework; using Microsoft.Build.Internal; using Microsoft.Build.Shared; +using Microsoft.Build.Shared.Debugging; using Microsoft.Build.TelemetryInfra; using NodeLoggingContext = Microsoft.Build.BackEnd.Logging.NodeLoggingContext; using ProjectLoggingContext = Microsoft.Build.BackEnd.Logging.ProjectLoggingContext; @@ -844,7 +845,7 @@ private async Task RequestThreadProc(bool setThreadParameters) // Dump all engine exceptions to a temp file // so that we have something to go on in the // event of a failure - ExceptionHandling.DumpExceptionToFile(ex); + DebugUtils.DumpExceptionToFile(ex); // This includes InternalErrorException, which we definitely want a callstack for. // Fortunately the default console UnhandledExceptionHandler will log the callstack even diff --git a/src/Build/BackEnd/Node/InProcNode.cs b/src/Build/BackEnd/Node/InProcNode.cs index b86e29247f3..3aea5d39fa4 100644 --- a/src/Build/BackEnd/Node/InProcNode.cs +++ b/src/Build/BackEnd/Node/InProcNode.cs @@ -11,6 +11,7 @@ using Microsoft.Build.Framework; using Microsoft.Build.Internal; using Microsoft.Build.Shared; +using Microsoft.Build.Shared.Debugging; using ILoggingService = Microsoft.Build.BackEnd.Logging.ILoggingService; using NodeLoggingContext = Microsoft.Build.BackEnd.Logging.NodeLoggingContext; @@ -187,7 +188,7 @@ public NodeEngineShutdownReason Run(out Exception shutdownException) // Dump all engine exceptions to a temp file // so that we have something to go on in the // event of a failure - ExceptionHandling.DumpExceptionToFile(e); + DebugUtils.DumpExceptionToFile(e); // This is fatal: process will terminate: make sure the // debugger launches diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index 8519eb7bb0d..4fab975382b 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -660,7 +660,7 @@ public static ExitType Execute(string[] commandLine) ErrorUtilities.VerifyThrowArgumentLength(commandLine); - AppDomain.CurrentDomain.UnhandledException += ExceptionHandling.UnhandledExceptionHandler; + AppDomain.CurrentDomain.UnhandledException += DebugUtils.UnhandledExceptionHandler; ExitType exitType = ExitType.Success; diff --git a/src/Shared/ExceptionHandling.cs b/src/Shared/ExceptionHandling.cs index 7843999d229..ff3eefd51c9 100644 --- a/src/Shared/ExceptionHandling.cs +++ b/src/Shared/ExceptionHandling.cs @@ -4,9 +4,7 @@ #nullable disable using System; -using System.Diagnostics.CodeAnalysis; using Microsoft.Build.Framework; -using Microsoft.Build.Shared.Debugging; namespace Microsoft.Build.Shared { @@ -15,19 +13,6 @@ namespace Microsoft.Build.Shared /// internal static class ExceptionHandling { - /// - internal static bool ResetDebugDumpPathInRunningTests - { - get => DebugUtils.ResetDebugDumpPathInRunningTests; - set => DebugUtils.ResetDebugDumpPathInRunningTests = value; - } - - /// - internal static string DebugDumpPath => DebugUtils.DebugDumpPath; - - /// - internal static string DumpFilePath => DebugUtils.DumpFilePath; - /// internal static bool IsCriticalException(Exception e) => FrameworkExceptionHandling.IsCriticalException(e); @@ -63,18 +48,5 @@ internal static bool NotExpectedRegistryException(Exception e) /// internal static bool NotExpectedFunctionException(Exception e) => FrameworkExceptionHandling.NotExpectedFunctionException(e); - - /// - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "It is called by the CLR")] - internal static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e) - => DebugUtils.UnhandledExceptionHandler(sender, e); - - /// - internal static void DumpExceptionToFile(Exception ex) - => DebugUtils.DumpExceptionToFile(ex); - - /// - internal static string ReadAnyExceptionFromFile(DateTime fromTimeUtc) - => DebugUtils.ReadAnyExceptionFromFile(fromTimeUtc); } } diff --git a/src/Shared/NodeEndpointOutOfProcBase.cs b/src/Shared/NodeEndpointOutOfProcBase.cs index cfb964f38dd..7d6e27a1fec 100644 --- a/src/Shared/NodeEndpointOutOfProcBase.cs +++ b/src/Shared/NodeEndpointOutOfProcBase.cs @@ -7,19 +7,19 @@ #endif using System.Diagnostics.CodeAnalysis; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipes; using System.Threading; using Microsoft.Build.Internal; using Microsoft.Build.Shared; -using System.IO.Pipes; -using System.IO; -using System.Collections.Generic; +using Microsoft.Build.Shared.Debugging; #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; @@ -514,7 +514,7 @@ private void PacketPumpProc() localPipeServer.Disconnect(); } - ExceptionHandling.DumpExceptionToFile(e); + DebugUtils.DumpExceptionToFile(e); ChangeLinkStatus(LinkStatus.Failed); return; } @@ -663,7 +663,7 @@ private void RunReadLoop( { // Lost communications. Abort (but allow node reuse) CommunicationsUtilities.Trace("Exception reading from server. {0}", e); - ExceptionHandling.DumpExceptionToFile(e); + DebugUtils.DumpExceptionToFile(e); ChangeLinkStatus(LinkStatus.Inactive); exitLoop = true; break; @@ -722,7 +722,7 @@ private void RunReadLoop( { // Error while deserializing or handling packet. Abort. CommunicationsUtilities.Trace("Exception while deserializing packet {0}: {1}", packetType, e); - ExceptionHandling.DumpExceptionToFile(e); + DebugUtils.DumpExceptionToFile(e); ChangeLinkStatus(LinkStatus.Failed); exitLoop = true; break; @@ -781,7 +781,7 @@ private void RunReadLoop( { // Error while deserializing or handling packet. Abort. CommunicationsUtilities.Trace("Exception while serializing packets: {0}", e); - ExceptionHandling.DumpExceptionToFile(e); + DebugUtils.DumpExceptionToFile(e); ChangeLinkStatus(LinkStatus.Failed); exitLoop = true; break; diff --git a/src/Shared/NodePipeServer.cs b/src/Shared/NodePipeServer.cs index c251080b156..42b1bbab0d7 100644 --- a/src/Shared/NodePipeServer.cs +++ b/src/Shared/NodePipeServer.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Microsoft.Build.BackEnd; using Microsoft.Build.Shared; +using Microsoft.Build.Shared.Debugging; namespace Microsoft.Build.Internal { @@ -155,7 +156,7 @@ internal async Task WaitForConnectionAsync(CancellationToken cancell _pipeServer.Disconnect(); } - ExceptionHandling.DumpExceptionToFile(e); + DebugUtils.DumpExceptionToFile(e); return LinkStatus.Failed; } } diff --git a/src/Shared/TaskLoader.cs b/src/Shared/TaskLoader.cs index 602a36871ed..e8c22514dd5 100644 --- a/src/Shared/TaskLoader.cs +++ b/src/Shared/TaskLoader.cs @@ -4,6 +4,7 @@ using System; using System.Reflection; using Microsoft.Build.Framework; +using Microsoft.Build.Shared.Debugging; namespace Microsoft.Build.Shared { @@ -115,7 +116,7 @@ bool isOutOfProc } // Hook up last minute dumping of any exceptions - taskAppDomain.UnhandledException += ExceptionHandling.UnhandledExceptionHandler; + taskAppDomain.UnhandledException += DebugUtils.UnhandledExceptionHandler; appDomainCreated?.Invoke(taskAppDomain); } } From ea19214bbc32b39cfe0cfeccb61e79d739905b01 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 10 Mar 2026 13:47:31 -0700 Subject: [PATCH 19/27] Remove shared ExceptionHandling and update usages to Framework version - Remove src/Shared/ExceptionHandling.cs from all project files. - Rename FrameworkExceptionHandling to ExceptionHandling. - Delete src/Shared/ExceptionHandling.cs - Fix all remaining usages of Microsoft.Build.Shared.ExceptionHandling to point to Microsoft.Build.Framework.ExceptionHandling. - Clean up using directives --- ...Microsoft.Build.Engine.OM.UnitTests.csproj | 1 - .../BackEnd/BuildManager/BuildManager.cs | 2 +- .../ProjectCache/ProjectCacheService.cs | 2 +- .../Construction/Solution/SolutionFile.cs | 3 +- src/Build/Microsoft.Build.csproj | 3 -- src/Build/Utilities/RegistryKeyWrapper.cs | 2 +- src/Framework/ExceptionHandling.cs | 2 +- src/Framework/FileUtilities.cs | 12 ++--- src/Framework/FileUtilities_TempFiles.cs | 2 +- src/Framework/ItemSpecModifiers.cs | 2 +- src/MSBuild/MSBuild.csproj | 1 - src/Shared/ExceptionHandling.cs | 52 ------------------- src/Shared/NodeEndpointOutOfProcBase.cs | 3 +- src/Shared/NodePipeServer.cs | 2 +- src/Shared/RegisteredTaskObjectCacheBase.cs | 1 - src/Shared/TaskLoader.cs | 2 + src/Tasks/AppConfig/AppConfig.cs | 1 - src/Tasks/AppConfig/BindingRedirect.cs | 1 + .../AssemblyDependency/AssemblyInformation.cs | 4 +- src/Tasks/AssemblyDependency/Resolver.cs | 1 + src/Tasks/GetReferenceAssemblyPaths.cs | 3 +- src/Tasks/ManifestUtil/LauncherBuilder.cs | 2 +- src/Tasks/Microsoft.Build.Tasks.csproj | 3 -- src/Tasks/Move.cs | 1 - src/Tasks/ResolveKeySource.cs | 1 - src/Tasks/StateFileBase.cs | 2 +- src/Tasks/TlbReference.cs | 1 - src/Tasks/XamlTaskFactory/TaskParser.cs | 1 + .../Microsoft.Build.Utilities.csproj | 3 -- src/Utilities/PlatformManifest.cs | 1 + src/Utilities/SDKManifest.cs | 1 + 31 files changed, 30 insertions(+), 88 deletions(-) delete mode 100644 src/Shared/ExceptionHandling.cs diff --git a/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj b/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj index 675130444f8..9acc79843a2 100644 --- a/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj +++ b/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj @@ -45,7 +45,6 @@ BuildEnvironmentHelper.cs - App.config Designer diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 4d66c10a09f..c83533a6a5f 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -38,7 +38,7 @@ using Microsoft.Build.Shared.FileSystem; using Microsoft.Build.TelemetryInfra; using Microsoft.NET.StringTools; -using ExceptionHandling = Microsoft.Build.Shared.ExceptionHandling; +using ExceptionHandling = Microsoft.Build.Framework.ExceptionHandling; using ForwardingLoggerRecord = Microsoft.Build.Logging.ForwardingLoggerRecord; using LoggerDescription = Microsoft.Build.Logging.LoggerDescription; diff --git a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs index 0d35062f96b..c58a4d0798e 100644 --- a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs +++ b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs @@ -24,7 +24,7 @@ using Microsoft.Build.Graph; using Microsoft.Build.Internal; using Microsoft.Build.Shared; -using ExceptionHandling = Microsoft.Build.Shared.ExceptionHandling; +using ExceptionHandling = Microsoft.Build.Framework.ExceptionHandling; #pragma warning disable CS0618 // Type or member is obsolete, this class is adapting to both Experimental and new plugin APIs namespace Microsoft.Build.ProjectCache diff --git a/src/Build/Construction/Solution/SolutionFile.cs b/src/Build/Construction/Solution/SolutionFile.cs index 42888852c35..ff5c243a0fa 100644 --- a/src/Build/Construction/Solution/SolutionFile.cs +++ b/src/Build/Construction/Solution/SolutionFile.cs @@ -21,7 +21,6 @@ using Microsoft.VisualStudio.SolutionPersistence.Serializer; using BuildEventFileInfo = Microsoft.Build.Shared.BuildEventFileInfo; using ErrorUtilities = Microsoft.Build.Shared.ErrorUtilities; -using ExceptionUtilities = Microsoft.Build.Shared.ExceptionHandling; using ProjectFileErrorUtilities = Microsoft.Build.Shared.ProjectFileErrorUtilities; using ResourceUtilities = Microsoft.Build.Shared.ResourceUtilities; using VisualStudioConstants = Microsoft.Build.Shared.VisualStudioConstants; @@ -746,7 +745,7 @@ internal void ParseSolutionFile() SolutionReader = new StreamReader(fileStream, Encoding.GetEncoding(0)); // HIGHCHAR: If solution files have no byte-order marks, then assume ANSI rather than ASCII. ParseSolution(); } - catch (Exception e) when (ExceptionUtilities.IsIoRelatedException(e)) + catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) { ProjectFileErrorUtilities.ThrowInvalidProjectFile(new BuildEventFileInfo(_solutionFile), "InvalidProjectFile", e.Message); } diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index ea9699329e0..3bf8513c8f7 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -661,9 +661,6 @@ SharedUtilities\EventArgsFormatting.cs - - SharedUtilities\ExceptionHandling.cs - SharedUtilities\FileMatcher.cs diff --git a/src/Build/Utilities/RegistryKeyWrapper.cs b/src/Build/Utilities/RegistryKeyWrapper.cs index ac4ecb68421..a14b9b5d94a 100644 --- a/src/Build/Utilities/RegistryKeyWrapper.cs +++ b/src/Build/Utilities/RegistryKeyWrapper.cs @@ -4,7 +4,7 @@ #if FEATURE_WIN32_REGISTRY using System; - +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Win32; using RegistryException = Microsoft.Build.Exceptions.RegistryException; diff --git a/src/Framework/ExceptionHandling.cs b/src/Framework/ExceptionHandling.cs index 646833a7319..29c290bd63f 100644 --- a/src/Framework/ExceptionHandling.cs +++ b/src/Framework/ExceptionHandling.cs @@ -13,7 +13,7 @@ namespace Microsoft.Build.Framework; -internal static class FrameworkExceptionHandling +internal static class ExceptionHandling { /// /// If the given exception is "ignorable under some circumstances" return false. diff --git a/src/Framework/FileUtilities.cs b/src/Framework/FileUtilities.cs index 95b4a042e23..814b2430bf4 100644 --- a/src/Framework/FileUtilities.cs +++ b/src/Framework/FileUtilities.cs @@ -882,7 +882,7 @@ internal static void DeleteSubdirectoriesNoThrow(string directory) DeleteDirectoryNoThrow(dir, recursive: true, retryCount: 1); } } - catch (Exception ex) when (FrameworkExceptionHandling.IsIoRelatedException(ex)) + catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) { // If we can't enumerate the directories, ignore. Other cases should be handled by DeleteDirectoryNoThrow. } @@ -965,7 +965,7 @@ internal static string GetFullPathNoThrow(string path) { path = NormalizePath(path); } - catch (Exception ex) when (FrameworkExceptionHandling.IsIoRelatedException(ex)) + catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) { } @@ -1045,7 +1045,7 @@ internal static void DeleteNoThrow(string path) { File.Delete(FixFilePath(path)); } - catch (Exception ex) when (FrameworkExceptionHandling.IsIoRelatedException(ex)) + catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) { } } @@ -1082,7 +1082,7 @@ internal static void DeleteDirectoryNoThrow(string path, bool recursive, int ret break; } } - catch (Exception ex) when (FrameworkExceptionHandling.IsIoRelatedException(ex)) + catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) { } @@ -1150,7 +1150,7 @@ internal static void DeleteWithoutTrailingBackslash(string path, bool recursive { fileInfo = new FileInfo(filePath); } - catch (Exception e) when (FrameworkExceptionHandling.IsIoRelatedException(e)) + catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) { // Invalid or inaccessible path: treat as if nonexistent file, just as File.Exists does return null; @@ -1407,7 +1407,7 @@ private static bool IsRootedNoThrow(string path) { return Path.IsPathRooted(FixFilePath(path)); } - catch (Exception ex) when (FrameworkExceptionHandling.IsIoRelatedException(ex)) + catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) { return false; } diff --git a/src/Framework/FileUtilities_TempFiles.cs b/src/Framework/FileUtilities_TempFiles.cs index b00fa5d3a95..956bb8ad882 100644 --- a/src/Framework/FileUtilities_TempFiles.cs +++ b/src/Framework/FileUtilities_TempFiles.cs @@ -192,7 +192,7 @@ internal static string GetTemporaryFile(string directory, string fileName, strin return file; } - catch (Exception ex) when (FrameworkExceptionHandling.IsIoRelatedException(ex)) + catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) { throw new IOException(SR.FormatFailedCreatingTempFile(ex.Message), ex); } diff --git a/src/Framework/ItemSpecModifiers.cs b/src/Framework/ItemSpecModifiers.cs index 5a2aa8e0818..7e87652adb7 100644 --- a/src/Framework/ItemSpecModifiers.cs +++ b/src/Framework/ItemSpecModifiers.cs @@ -377,7 +377,7 @@ internal static string GetItemSpecModifier(string currentDirectory, string itemS throw new InternalErrorException($"\"{modifier}\" is not a valid item-spec modifier."); } } - catch (Exception e) when (FrameworkExceptionHandling.IsIoRelatedException(e)) + catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) { throw new InvalidOperationException(SR.FormatInvalidFilespecForTransform(modifier, itemSpec, e.Message)); } diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj index f190035aa5b..6047716de27 100644 --- a/src/MSBuild/MSBuild.csproj +++ b/src/MSBuild/MSBuild.csproj @@ -82,7 +82,6 @@ - diff --git a/src/Shared/ExceptionHandling.cs b/src/Shared/ExceptionHandling.cs deleted file mode 100644 index ff3eefd51c9..00000000000 --- a/src/Shared/ExceptionHandling.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. - -#nullable disable - -using System; -using Microsoft.Build.Framework; - -namespace Microsoft.Build.Shared -{ - /// - /// Utility methods for classifying and handling exceptions. - /// - internal static class ExceptionHandling - { - /// - internal static bool IsCriticalException(Exception e) - => FrameworkExceptionHandling.IsCriticalException(e); - - /// - internal static bool NotExpectedException(Exception e) - => FrameworkExceptionHandling.NotExpectedException(e); - - /// - internal static bool IsIoRelatedException(Exception e) - => FrameworkExceptionHandling.IsIoRelatedException(e); - - /// - internal static bool IsXmlException(Exception e) - => FrameworkExceptionHandling.IsXmlException(e); - - /// - internal static bool NotExpectedIoOrXmlException(Exception e) - => FrameworkExceptionHandling.NotExpectedIoOrXmlException(e); - - /// - internal static bool NotExpectedReflectionException(Exception e) - => FrameworkExceptionHandling.NotExpectedReflectionException(e); - - /// - internal static bool NotExpectedSerializationException(Exception e) - => FrameworkExceptionHandling.NotExpectedSerializationException(e); - - /// - internal static bool NotExpectedRegistryException(Exception e) - => FrameworkExceptionHandling.NotExpectedRegistryException(e); - - /// - internal static bool NotExpectedFunctionException(Exception e) - => FrameworkExceptionHandling.NotExpectedFunctionException(e); - } -} diff --git a/src/Shared/NodeEndpointOutOfProcBase.cs b/src/Shared/NodeEndpointOutOfProcBase.cs index 7d6e27a1fec..b7ff1eeffee 100644 --- a/src/Shared/NodeEndpointOutOfProcBase.cs +++ b/src/Shared/NodeEndpointOutOfProcBase.cs @@ -5,12 +5,13 @@ #if NET using System.Collections.Frozen; #endif -using System.Diagnostics.CodeAnalysis; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Pipes; using System.Threading; +using Microsoft.Build.Framework; using Microsoft.Build.Internal; using Microsoft.Build.Shared; using Microsoft.Build.Shared.Debugging; diff --git a/src/Shared/NodePipeServer.cs b/src/Shared/NodePipeServer.cs index 42b1bbab0d7..8187ca4db59 100644 --- a/src/Shared/NodePipeServer.cs +++ b/src/Shared/NodePipeServer.cs @@ -11,7 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Build.BackEnd; -using Microsoft.Build.Shared; +using Microsoft.Build.Framework; using Microsoft.Build.Shared.Debugging; namespace Microsoft.Build.Internal diff --git a/src/Shared/RegisteredTaskObjectCacheBase.cs b/src/Shared/RegisteredTaskObjectCacheBase.cs index 10391f5d336..fa91fb2a6ae 100644 --- a/src/Shared/RegisteredTaskObjectCacheBase.cs +++ b/src/Shared/RegisteredTaskObjectCacheBase.cs @@ -8,7 +8,6 @@ #nullable disable #if BUILD_ENGINE -using Microsoft.Build.Shared; namespace Microsoft.Build.BackEnd.Components.Caching #else namespace Microsoft.Build.Shared diff --git a/src/Shared/TaskLoader.cs b/src/Shared/TaskLoader.cs index e8c22514dd5..262d3b0988b 100644 --- a/src/Shared/TaskLoader.cs +++ b/src/Shared/TaskLoader.cs @@ -4,7 +4,9 @@ using System; using System.Reflection; using Microsoft.Build.Framework; +#if FEATURE_APPDOMAIN using Microsoft.Build.Shared.Debugging; +#endif namespace Microsoft.Build.Shared { diff --git a/src/Tasks/AppConfig/AppConfig.cs b/src/Tasks/AppConfig/AppConfig.cs index 44affce2eae..47790663d04 100644 --- a/src/Tasks/AppConfig/AppConfig.cs +++ b/src/Tasks/AppConfig/AppConfig.cs @@ -5,7 +5,6 @@ using System.IO; using System.Xml; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Tasks/AppConfig/BindingRedirect.cs b/src/Tasks/AppConfig/BindingRedirect.cs index d29808b9b59..6691b543a16 100644 --- a/src/Tasks/AppConfig/BindingRedirect.cs +++ b/src/Tasks/AppConfig/BindingRedirect.cs @@ -3,6 +3,7 @@ using System; using System.Xml; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Tasks/AssemblyDependency/AssemblyInformation.cs b/src/Tasks/AssemblyDependency/AssemblyInformation.cs index 2a3c9c6aef3..5ecfd2e4705 100644 --- a/src/Tasks/AssemblyDependency/AssemblyInformation.cs +++ b/src/Tasks/AssemblyDependency/AssemblyInformation.cs @@ -16,8 +16,10 @@ using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; #if FEATURE_ASSEMBLYLOADCONTEXT -using System.Reflection.PortableExecutable; using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +#else +using Microsoft.Build.Framework; #endif using Microsoft.Build.Tasks.AssemblyDependency; diff --git a/src/Tasks/AssemblyDependency/Resolver.cs b/src/Tasks/AssemblyDependency/Resolver.cs index 5cebad377ce..62a455c983d 100644 --- a/src/Tasks/AssemblyDependency/Resolver.cs +++ b/src/Tasks/AssemblyDependency/Resolver.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Reflection; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Tasks/GetReferenceAssemblyPaths.cs b/src/Tasks/GetReferenceAssemblyPaths.cs index 50e3f54d4c4..81b02cdf5a0 100644 --- a/src/Tasks/GetReferenceAssemblyPaths.cs +++ b/src/Tasks/GetReferenceAssemblyPaths.cs @@ -4,10 +4,11 @@ using System; using System.Collections.Generic; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.Utilities; using FrameworkNameVersioning = System.Runtime.Versioning.FrameworkName; + #if FEATURE_GAC +using Microsoft.Build.Shared; using SystemProcessorArchitecture = System.Reflection.ProcessorArchitecture; #endif diff --git a/src/Tasks/ManifestUtil/LauncherBuilder.cs b/src/Tasks/ManifestUtil/LauncherBuilder.cs index c65068ddc1f..e441c548957 100644 --- a/src/Tasks/ManifestUtil/LauncherBuilder.cs +++ b/src/Tasks/ManifestUtil/LauncherBuilder.cs @@ -3,7 +3,7 @@ using System; using System.IO; -using Microsoft.Build.Shared; +using Microsoft.Build.Framework; using Microsoft.Build.Shared.FileSystem; using Microsoft.Build.Tasks.Deployment.Bootstrapper; diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj index 2f01d8ac5e5..9699f49dec9 100644 --- a/src/Tasks/Microsoft.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.Build.Tasks.csproj @@ -117,9 +117,6 @@ StreamMappedString.cs - - ExceptionHandling.cs - StringUtils.cs diff --git a/src/Tasks/Move.cs b/src/Tasks/Move.cs index a957107e942..5e0a13b3742 100644 --- a/src/Tasks/Move.cs +++ b/src/Tasks/Move.cs @@ -6,7 +6,6 @@ using System.IO; using System.Runtime.InteropServices; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; using Microsoft.Build.Utilities; diff --git a/src/Tasks/ResolveKeySource.cs b/src/Tasks/ResolveKeySource.cs index 366f0badba0..23f33794861 100644 --- a/src/Tasks/ResolveKeySource.cs +++ b/src/Tasks/ResolveKeySource.cs @@ -10,7 +10,6 @@ using System.Globalization; using System.Security.Cryptography; using Microsoft.Runtime.Hosting; -using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; #endif diff --git a/src/Tasks/StateFileBase.cs b/src/Tasks/StateFileBase.cs index e44fbf5f2bf..01e2a102f06 100644 --- a/src/Tasks/StateFileBase.cs +++ b/src/Tasks/StateFileBase.cs @@ -4,7 +4,7 @@ using System; using System.IO; using Microsoft.Build.BackEnd; -using Microsoft.Build.Shared; +using Microsoft.Build.Framework; using Microsoft.Build.Shared.FileSystem; using Microsoft.Build.Utilities; diff --git a/src/Tasks/TlbReference.cs b/src/Tasks/TlbReference.cs index 30acc7f8e38..eed290e1bb0 100644 --- a/src/Tasks/TlbReference.cs +++ b/src/Tasks/TlbReference.cs @@ -11,7 +11,6 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.Utilities; // TYPELIBATTR clashes with the one in InteropServices. using TYPELIBATTR = System.Runtime.InteropServices.ComTypes.TYPELIBATTR; diff --git a/src/Tasks/XamlTaskFactory/TaskParser.cs b/src/Tasks/XamlTaskFactory/TaskParser.cs index ff723700f11..1c0f3165ec7 100644 --- a/src/Tasks/XamlTaskFactory/TaskParser.cs +++ b/src/Tasks/XamlTaskFactory/TaskParser.cs @@ -7,6 +7,7 @@ using System.IO; using System.Text; using System.Xaml; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; using XamlTypes = Microsoft.Build.Framework.XamlTypes; diff --git a/src/Utilities/Microsoft.Build.Utilities.csproj b/src/Utilities/Microsoft.Build.Utilities.csproj index ea53f266bf8..91325b42c49 100644 --- a/src/Utilities/Microsoft.Build.Utilities.csproj +++ b/src/Utilities/Microsoft.Build.Utilities.csproj @@ -72,9 +72,6 @@ Shared\EventArgsFormatting.cs - - Shared\ExceptionHandling.cs - Shared\FileMatcher.cs diff --git a/src/Utilities/PlatformManifest.cs b/src/Utilities/PlatformManifest.cs index fec3fedbfca..c5e8f29cb84 100644 --- a/src/Utilities/PlatformManifest.cs +++ b/src/Utilities/PlatformManifest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Xml; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; diff --git a/src/Utilities/SDKManifest.cs b/src/Utilities/SDKManifest.cs index d8c06a2c234..c6aa44563a7 100644 --- a/src/Utilities/SDKManifest.cs +++ b/src/Utilities/SDKManifest.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Xml; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; From 765c83752530178244441db546e902a5a82bb86c Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 10 Mar 2026 13:51:24 -0700 Subject: [PATCH 20/27] Move FileUtilities_Tests to Framework.UnitTests --- .../Microsoft.Build.Engine.UnitTests.csproj | 1 - .../FileUtilities_Tests.cs | 1058 +++++++++++++++++ src/Shared/UnitTests/FileUtilities_Tests.cs | 1058 ----------------- 3 files changed, 1058 insertions(+), 1059 deletions(-) create mode 100644 src/Framework.UnitTests/FileUtilities_Tests.cs delete mode 100644 src/Shared/UnitTests/FileUtilities_Tests.cs diff --git a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj index 5149275192f..43b3921dfed 100644 --- a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj +++ b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj @@ -68,7 +68,6 @@ - diff --git a/src/Framework.UnitTests/FileUtilities_Tests.cs b/src/Framework.UnitTests/FileUtilities_Tests.cs new file mode 100644 index 00000000000..62132cd15f3 --- /dev/null +++ b/src/Framework.UnitTests/FileUtilities_Tests.cs @@ -0,0 +1,1058 @@ +// 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.IO; +using System.Linq; +using System.Threading; +using Microsoft.Build.Framework; +using Microsoft.Build.Shared; +using Microsoft.Build.UnitTests; +using Shouldly; +using Xunit; + +#nullable disable + +namespace Microsoft.Build.Framework.UnitTests; + +public class FileUtilities_Tests +{ + /// + /// Exercises ItemSpecModifiers.GetItemSpecModifier + /// + [Fact] + [Trait("Category", "netcore-osx-failing")] + [Trait("Category", "netcore-linux-failing")] + public void GetItemSpecModifier() + { + TestGetItemSpecModifier(Directory.GetCurrentDirectory()); + TestGetItemSpecModifier(null); + } + + private static void TestGetItemSpecModifier(string currentDirectory) + { + string cache = null; + string modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, "foo", String.Empty, ItemSpecModifiers.RecursiveDir, ref cache); + Assert.Equal(String.Empty, modifier); + + cache = null; + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, "foo", String.Empty, ItemSpecModifiers.ModifiedTime, ref cache); + Assert.Equal(String.Empty, modifier); + + cache = null; + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, @"foo\goo", String.Empty, ItemSpecModifiers.RelativeDir, ref cache); + Assert.Equal(@"foo" + Path.DirectorySeparatorChar, modifier); + + // confirm we get the same thing back the second time + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, @"foo\goo", String.Empty, ItemSpecModifiers.RelativeDir, ref cache); + Assert.Equal(@"foo" + Path.DirectorySeparatorChar, modifier); + + cache = null; + string itemSpec = NativeMethodsShared.IsWindows ? @"c:\foo.txt" : "/foo.txt"; + string itemSpecDir = NativeMethodsShared.IsWindows ? @"c:\" : "/"; + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.FullPath, ref cache); + Assert.Equal(itemSpec, modifier); + Assert.Equal(itemSpec, cache); + + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.RootDir, ref cache); + Assert.Equal(itemSpecDir, modifier); + + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.Filename, ref cache); + Assert.Equal(@"foo", modifier); + + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.Extension, ref cache); + Assert.Equal(@".txt", modifier); + + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.Directory, ref cache); + Assert.Equal(String.Empty, modifier); + + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.Identity, ref cache); + Assert.Equal(itemSpec, modifier); + + string projectPath = NativeMethodsShared.IsWindows ? @"c:\abc\goo.proj" : @"/abc/goo.proj"; + string projectPathDir = NativeMethodsShared.IsWindows ? @"c:\abc\" : @"/abc/"; + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, projectPath, ItemSpecModifiers.DefiningProjectDirectory, ref cache); + Assert.Equal(projectPathDir, modifier); + + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, projectPath, ItemSpecModifiers.DefiningProjectExtension, ref cache); + Assert.Equal(@".proj", modifier); + + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, projectPath, ItemSpecModifiers.DefiningProjectFullPath, ref cache); + Assert.Equal(projectPath, modifier); + + modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, projectPath, ItemSpecModifiers.DefiningProjectName, ref cache); + Assert.Equal(@"goo", modifier); + } + + [Fact] + public void MakeRelativeTests() + { + if (NativeMethodsShared.IsWindows) + { + Assert.Equal(@"foo.cpp", FileUtilities.MakeRelative(@"c:\abc\def", @"c:\abc\def\foo.cpp")); + Assert.Equal(@"def\foo.cpp", FileUtilities.MakeRelative(@"c:\abc\", @"c:\abc\def\foo.cpp")); + Assert.Equal(@"..\foo.cpp", FileUtilities.MakeRelative(@"c:\abc\def\xyz", @"c:\abc\def\foo.cpp")); + Assert.Equal(@"..\ttt\foo.cpp", FileUtilities.MakeRelative(@"c:\abc\def\xyz\", @"c:\abc\def\ttt\foo.cpp")); + Assert.Equal(@"e:\abc\def\foo.cpp", FileUtilities.MakeRelative(@"c:\abc\def", @"e:\abc\def\foo.cpp")); + Assert.Equal(@"foo.cpp", FileUtilities.MakeRelative(@"\\aaa\abc\def", @"\\aaa\abc\def\foo.cpp")); + Assert.Equal(@"foo.cpp", FileUtilities.MakeRelative(@"c:\abc\def", @"foo.cpp")); + Assert.Equal(@"\\host\path\file", FileUtilities.MakeRelative(@"c:\abc\def", @"\\host\path\file")); + Assert.Equal(@"\\host\d$\file", FileUtilities.MakeRelative(@"c:\abc\def", @"\\host\d$\file")); + Assert.Equal(@"..\fff\ggg.hh", FileUtilities.MakeRelative(@"c:\foo\bar\..\abc\cde", @"c:\foo\bar\..\abc\fff\ggg.hh")); + + /* Directories */ + Assert.Equal(@"def\", FileUtilities.MakeRelative(@"c:\abc\", @"c:\abc\def\")); + Assert.Equal(@"..\", FileUtilities.MakeRelative(@"c:\abc\def\xyz\", @"c:\abc\def\")); + Assert.Equal(@"..\ttt\", FileUtilities.MakeRelative(@"c:\abc\def\xyz\", @"c:\abc\def\ttt\")); + Assert.Equal(@".", FileUtilities.MakeRelative(@"c:\abc\def\", @"c:\abc\def\")); + + /* Directory + File */ + Assert.Equal(@"def", FileUtilities.MakeRelative(@"c:\abc\", @"c:\abc\def")); + Assert.Equal(@"..\..\ghi", FileUtilities.MakeRelative(@"c:\abc\def\xyz\", @"c:\abc\ghi")); + Assert.Equal(@"..\ghi", FileUtilities.MakeRelative(@"c:\abc\def\xyz\", @"c:\abc\def\ghi")); + Assert.Equal(@"..\ghi", FileUtilities.MakeRelative(@"c:\abc\def\", @"c:\abc\ghi")); + + /* File + Directory */ + Assert.Equal(@"def\", FileUtilities.MakeRelative(@"c:\abc", @"c:\abc\def\")); + Assert.Equal(@"..\", FileUtilities.MakeRelative(@"c:\abc\def\xyz", @"c:\abc\def\")); + Assert.Equal(@"..\ghi\", FileUtilities.MakeRelative(@"c:\abc\def\xyz", @"c:\abc\def\ghi\")); + Assert.Equal(@".", FileUtilities.MakeRelative(@"c:\abc\def", @"c:\abc\def\")); + } + else + { + Assert.Equal(@"bar.cpp", FileUtilities.MakeRelative(@"/abc/def", @"/abc/def/bar.cpp")); + Assert.Equal(@"def/foo.cpp", FileUtilities.MakeRelative(@"/abc/", @"/abc/def/foo.cpp")); + Assert.Equal(@"../foo.cpp", FileUtilities.MakeRelative(@"/abc/def/xyz", @"/abc/def/foo.cpp")); + Assert.Equal(@"../ttt/foo.cpp", FileUtilities.MakeRelative(@"/abc/def/xyz/", @"/abc/def/ttt/foo.cpp")); + Assert.Equal(@"foo.cpp", FileUtilities.MakeRelative(@"/abc/def", @"foo.cpp")); + Assert.Equal(@"../fff/ggg.hh", FileUtilities.MakeRelative(@"/foo/bar/../abc/cde", @"/foo/bar/../abc/fff/ggg.hh")); + + /* Directories */ + Assert.Equal(@"def/", FileUtilities.MakeRelative(@"/abc/", @"/abc/def/")); + Assert.Equal(@"../", FileUtilities.MakeRelative(@"/abc/def/xyz/", @"/abc/def/")); + Assert.Equal(@"../ttt/", FileUtilities.MakeRelative(@"/abc/def/xyz/", @"/abc/def/ttt/")); + Assert.Equal(@".", FileUtilities.MakeRelative(@"/abc/def/", @"/abc/def/")); + + /* Directory + File */ + Assert.Equal(@"def", FileUtilities.MakeRelative(@"/abc/", @"/abc/def")); + Assert.Equal(@"../../ghi", FileUtilities.MakeRelative(@"/abc/def/xyz/", @"/abc/ghi")); + Assert.Equal(@"../ghi", FileUtilities.MakeRelative(@"/abc/def/xyz/", @"/abc/def/ghi")); + Assert.Equal(@"../ghi", FileUtilities.MakeRelative(@"/abc/def/", @"/abc/ghi")); + + /* File + Directory */ + Assert.Equal(@"def/", FileUtilities.MakeRelative(@"/abc", @"/abc/def/")); + Assert.Equal(@"../", FileUtilities.MakeRelative(@"/abc/def/xyz", @"/abc/def/")); + Assert.Equal(@"../ghi/", FileUtilities.MakeRelative(@"/abc/def/xyz", @"/abc/def/ghi/")); + Assert.Equal(@".", FileUtilities.MakeRelative(@"/abc/def", @"/abc/def/")); + } + } + + /// + /// Exercises ItemSpecModifiers.GetItemSpecModifier on a bad path. + /// + [WindowsFullFrameworkOnlyFact(additionalMessage: ".NET Core 2.1+ no longer validates paths: https://github.com/dotnet/corefx/issues/27779#issuecomment-371253486.")] + public void GetItemSpecModifierOnBadPath() + { + Assert.Throws(() => + { + TestGetItemSpecModifierOnBadPath(Directory.GetCurrentDirectory()); + }); + } + /// + /// Exercises ItemSpecModifiers.GetItemSpecModifier on a bad path. + /// + [WindowsFullFrameworkOnlyFact(additionalMessage: ".NET Core 2.1+ no longer validates paths: https://github.com/dotnet/corefx/issues/27779#issuecomment-371253486.")] + public void GetItemSpecModifierOnBadPath2() + { + Assert.Throws(() => + { + TestGetItemSpecModifierOnBadPath(null); + }); + } + + private static void TestGetItemSpecModifierOnBadPath(string currentDirectory) + { + try + { + string cache = null; + ItemSpecModifiers.GetItemSpecModifier(currentDirectory, @"http://www.microsoft.com", String.Empty, ItemSpecModifiers.RootDir, ref cache); + } + catch (Exception e) + { + // so I can see the exception message in NUnit's "Standard Out" window + Console.WriteLine(e.Message); + throw; + } + } + + [Fact] + public void GetFileInfoNoThrowBasic() + { + string file = null; + try + { + file = FileUtilities.GetTemporaryFile(); + FileInfo info = FileUtilities.GetFileInfoNoThrow(file); + Assert.Equal(info.LastWriteTime, new FileInfo(file).LastWriteTime); + } + finally + { + if (file != null) + { + File.Delete(file); + } + } + } + + [Fact] + public void GetFileInfoNoThrowNonexistent() + { + FileInfo info = FileUtilities.GetFileInfoNoThrow("this_file_is_nonexistent"); + Assert.Null(info); + } + + /// + /// Exercises FrameworkFileUtilities.EndsWithSlash + /// + [Fact] + [Trait("Category", "netcore-osx-failing")] + [Trait("Category", "netcore-linux-failing")] + public void EndsWithSlash() + { + Assert.True(FileUtilities.EndsWithSlash(@"C:\foo\")); + Assert.True(FileUtilities.EndsWithSlash(@"C:\")); + Assert.True(FileUtilities.EndsWithSlash(@"\")); + + Assert.True(FileUtilities.EndsWithSlash(@"http://www.microsoft.com/")); + Assert.True(FileUtilities.EndsWithSlash(@"//server/share/")); + Assert.True(FileUtilities.EndsWithSlash(@"/")); + + Assert.False(FileUtilities.EndsWithSlash(@"C:\foo")); + Assert.False(FileUtilities.EndsWithSlash(@"C:")); + Assert.False(FileUtilities.EndsWithSlash(@"foo")); + + // confirm that empty string doesn't barf + Assert.False(FileUtilities.EndsWithSlash(String.Empty)); + } + + /// + /// Exercises FileUtilities.GetDirectory + /// + [Fact] + [Trait("Category", "netcore-osx-failing")] + [Trait("Category", "netcore-linux-failing")] + public void GetDirectoryWithTrailingSlash() + { + Assert.Equal(NativeMethodsShared.IsWindows ? @"c:\" : "/", FileUtilities.GetDirectory(NativeMethodsShared.IsWindows ? @"c:\" : "/")); + Assert.Equal(NativeMethodsShared.IsWindows ? @"c:\" : "/", FileUtilities.GetDirectory(NativeMethodsShared.IsWindows ? @"c:\foo" : "/foo")); + Assert.Equal(NativeMethodsShared.IsWindows ? @"c:" : "/", FileUtilities.GetDirectory(NativeMethodsShared.IsWindows ? @"c:" : "/")); + Assert.Equal(FileUtilities.FixFilePath(@"\"), FileUtilities.GetDirectory(@"\")); + Assert.Equal(FileUtilities.FixFilePath(@"\"), FileUtilities.GetDirectory(@"\foo")); + Assert.Equal(FileUtilities.FixFilePath(@"..\"), FileUtilities.GetDirectory(@"..\foo")); + Assert.Equal(FileUtilities.FixFilePath(@"\foo\"), FileUtilities.GetDirectory(@"\foo\")); + Assert.Equal(FileUtilities.FixFilePath(@"\\server\share"), FileUtilities.GetDirectory(@"\\server\share")); + Assert.Equal(FileUtilities.FixFilePath(@"\\server\share\"), FileUtilities.GetDirectory(@"\\server\share\")); + Assert.Equal(FileUtilities.FixFilePath(@"\\server\share\"), FileUtilities.GetDirectory(@"\\server\share\file")); + Assert.Equal(FileUtilities.FixFilePath(@"\\server\share\directory\"), FileUtilities.GetDirectory(@"\\server\share\directory\")); + Assert.Equal(FileUtilities.FixFilePath(@"foo\"), FileUtilities.GetDirectory(@"foo\bar")); + Assert.Equal(FileUtilities.FixFilePath(@"\foo\bar\"), FileUtilities.GetDirectory(@"\foo\bar\")); + Assert.Equal(String.Empty, FileUtilities.GetDirectory("foo")); + } + + [Theory] + [InlineData("foo.txt", new[] { ".txt" })] + [InlineData("foo.txt", new[] { ".TXT" })] + [InlineData("foo.txt", new[] { ".EXE", ".TXT" })] + public void HasExtension_WhenFileNameHasExtension_ReturnsTrue(string fileName, string[] allowedExtensions) + { + var result = FileUtilities.HasExtension(fileName, allowedExtensions); + + if (!FileUtilities.GetIsFileSystemCaseSensitive() || allowedExtensions.Any(extension => fileName.Contains(extension))) + { + result.ShouldBeTrue(); + } + } + + [Theory] + [InlineData("foo.txt", new[] { ".DLL" })] + [InlineData("foo.txt", new[] { ".EXE", ".DLL" })] + [InlineData("foo.exec", new[] { ".exe", })] + [InlineData("foo.exe", new[] { ".exec", })] + [InlineData("foo", new[] { ".exe", })] + [InlineData("", new[] { ".exe" })] + [InlineData(null, new[] { ".exe" })] + public void HasExtension_WhenFileNameDoesNotHaveExtension_ReturnsFalse(string fileName, string[] allowedExtensions) + { + var result = FileUtilities.HasExtension(fileName, allowedExtensions); + + Assert.False(result); + } + + [WindowsFullFrameworkOnlyFact] + public void HasExtension_WhenInvalidFileName_ThrowsArgumentException() + { + Assert.Throws(() => + { + FileUtilities.HasExtension("|/", new[] { ".exe" }); + }); + } + + [Fact] + public void HasExtension_UsesOrdinalIgnoreCase() + { + var currentCulture = Thread.CurrentThread.CurrentCulture; + try + { + Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR"); // Turkish + + var result = FileUtilities.HasExtension("foo.ini", new string[] { ".INI" }); + + result.ShouldBe(!FileUtilities.GetIsFileSystemCaseSensitive()); + } + finally + { + Thread.CurrentThread.CurrentCulture = currentCulture; + } + } + + /// + /// Exercises FrameworkFileUtilities.EnsureTrailingSlash + /// + [Fact] + public void EnsureTrailingSlash() + { + // Doesn't have a trailing slash to start with. + Assert.Equal(FileUtilities.FixFilePath(@"foo\bar\"), FileUtilities.EnsureTrailingSlash(@"foo\bar")); // "test 1" + Assert.Equal(FileUtilities.FixFilePath(@"foo/bar\"), FileUtilities.EnsureTrailingSlash(@"foo/bar")); // "test 2" + + // Already has a trailing slash to start with. + Assert.Equal(FileUtilities.FixFilePath(@"foo/bar/"), FileUtilities.EnsureTrailingSlash(@"foo/bar/")); // test 3" + Assert.Equal(FileUtilities.FixFilePath(@"foo\bar\"), FileUtilities.EnsureTrailingSlash(@"foo\bar\")); // test 4" + Assert.Equal(FileUtilities.FixFilePath(@"foo/bar\"), FileUtilities.EnsureTrailingSlash(@"foo/bar\")); // test 5" + Assert.Equal(FileUtilities.FixFilePath(@"foo\bar/"), FileUtilities.EnsureTrailingSlash(@"foo\bar/")); // "test 5" + } + + /// + /// Exercises ItemSpecModifiers.IsItemSpecModifier + /// + [Fact] + public void IsItemSpecModifier() + { + // Positive matches using exact case. + Assert.True(ItemSpecModifiers.IsItemSpecModifier("FullPath")); // "test 1" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("RootDir")); // "test 2" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("Filename")); // "test 3" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("Extension")); // "test 4" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("RelativeDir")); // "test 5" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("Directory")); // "test 6" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("RecursiveDir")); // "test 7" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("Identity")); // "test 8" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("ModifiedTime")); // "test 9" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("CreatedTime")); // "test 10" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("AccessedTime")); // "test 11" + + // Positive matches using different case. + Assert.True(ItemSpecModifiers.IsItemSpecModifier("fullPath")); // "test 21" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("rootDir")); // "test 22" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("filename")); // "test 23" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("extension")); // "test 24" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("relativeDir")); // "test 25" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("directory")); // "test 26" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("recursiveDir")); // "test 27" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("identity")); // "test 28" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("modifiedTime")); // "test 29" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("createdTime")); // "test 30" + Assert.True(ItemSpecModifiers.IsItemSpecModifier("accessedTime")); // "test 31" + + // Negative tests to get maximum code coverage inside the many different branches + // of ItemSpecModifiers.IsItemSpecModifier. + Assert.False(ItemSpecModifiers.IsItemSpecModifier("rootxxx")); // "test 41" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Rootxxx")); // "test 42" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxx")); // "test 43" + + Assert.False(ItemSpecModifiers.IsItemSpecModifier("filexxxx")); // "test 44" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Filexxxx")); // "test 45" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("idenxxxx")); // "test 46" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Idenxxxx")); // "test 47" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxxx")); // "test 48" + + Assert.False(ItemSpecModifiers.IsItemSpecModifier("extenxxxx")); // "test 49" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Extenxxxx")); // "test 50" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("direcxxxx")); // "test 51" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Direcxxxx")); // "test 52" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxxxx")); // "test 53" + + Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxxxxx")); // "test 54" + + Assert.False(ItemSpecModifiers.IsItemSpecModifier("relativexxx")); // "test 55" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Relativexxx")); // "test 56" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("createdxxxx")); // "test 57" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Createdxxxx")); // "test 58" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxxxxxx")); // "test 59" + + Assert.False(ItemSpecModifiers.IsItemSpecModifier("recursivexxx")); // "test 60" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Recursivexxx")); // "test 61" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("accessedxxxx")); // "test 62" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Accessedxxxx")); // "test 63" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("modifiedxxxx")); // "test 64" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("Modifiedxxxx")); // "test 65" + Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxxxxxxx")); // "test 66" + + Assert.False(ItemSpecModifiers.IsItemSpecModifier(null)); // "test 67" + } + + [Fact] + public void CheckDerivableItemSpecModifiers() + { + Assert.True(ItemSpecModifiers.IsDerivableItemSpecModifier("Filename")); + Assert.False(ItemSpecModifiers.IsDerivableItemSpecModifier("RecursiveDir")); + Assert.False(ItemSpecModifiers.IsDerivableItemSpecModifier("recursivedir")); + } + + [WindowsOnlyFact] + public void NormalizePathThatFitsIntoMaxPath() + { + string currentDirectory = @"c:\aardvark\aardvark\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890"; + string filePath = @"..\..\..\..\..\..\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\a.cs"; + string fullPath = @"c:\aardvark\aardvark\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\a.cs"; + + Assert.Equal(fullPath, FileUtilities.NormalizePath(Path.Combine(currentDirectory, filePath))); + } + + [LongPathSupportDisabledFact(fullFrameworkOnly: true, additionalMessage: "https://github.com/dotnet/msbuild/issues/4363")] + public void NormalizePathThatDoesntFitIntoMaxPath() + { + Assert.Throws(() => + { + string currentDirectory = @"c:\aardvark\aardvark\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890"; + string filePath = @"..\..\..\..\..\..\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\a.cs"; + + // This path ends up over 420 characters long + string fullPath = @"c:\aardvark\aardvark\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\a.cs"; + + Assert.Equal(fullPath, FileUtilities.NormalizePath(Path.Combine(currentDirectory, filePath))); + }); + } + + [WindowsOnlyFact] + public void GetItemSpecModifierRootDirThatFitsIntoMaxPath() + { + string currentDirectory = @"c:\aardvark\aardvark\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890"; + string fullPath = @"c:\aardvark\aardvark\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\a.cs"; + string cache = fullPath; + + Assert.Equal(@"c:\", ItemSpecModifiers.GetItemSpecModifier(currentDirectory, fullPath, String.Empty, ItemSpecModifiers.RootDir, ref cache)); + } + + [Fact] + public void NormalizePathNull() + { + Assert.Throws(() => + { + Assert.Null(FileUtilities.NormalizePath(null, null)); + }); + } + + [Fact] + public void NormalizePathEmpty() + { + Assert.Throws(() => + { + Assert.Null(FileUtilities.NormalizePath(String.Empty)); + }); + } + + [WindowsFullFrameworkOnlyFact(additionalMessage: ".NET Core 2.1+ no longer validates paths: https://github.com/dotnet/corefx/issues/27779#issuecomment-371253486.")] + public void NormalizePathBadUNC1() + { + Assert.Throws(() => + { + Assert.Null(FileUtilities.NormalizePath(@"\\")); + }); + } + + [WindowsFullFrameworkOnlyFact(additionalMessage: ".NET Core 2.1+ no longer validates paths: https://github.com/dotnet/corefx/issues/27779#issuecomment-371253486.")] + public void NormalizePathBadUNC2() + { + Assert.Throws(() => + { + Assert.Null(FileUtilities.NormalizePath(@"\\XXX\")); + }); + } + + [WindowsFullFrameworkOnlyFact(additionalMessage: ".NET Core 2.1+ no longer validates paths: https://github.com/dotnet/corefx/issues/27779#issuecomment-371253486.")] + public void NormalizePathBadUNC3() + { + Assert.Throws(() => + { + Assert.Equal(@"\\localhost", FileUtilities.NormalizePath(@"\\localhost")); + }); + } + + [WindowsOnlyFact] + public void NormalizePathGoodUNC() + { + Assert.Equal(@"\\localhost\share", FileUtilities.NormalizePath(@"\\localhost\share")); + } + + [WindowsOnlyFact] + public void NormalizePathTooLongWithDots() + { + string longPart = new string('x', 300); + Assert.Equal(@"c:\abc\def", FileUtilities.NormalizePath(@"c:\abc\" + longPart + @"\..\def")); + } + + [WindowsFullFrameworkOnlyFact(additionalMessage: ".NET Core 2.1+ no longer validates paths: https://github.com/dotnet/corefx/issues/27779#issuecomment-371253486.")] + public void NormalizePathInvalid() + { + string filePath = @"c:\aardvark\|||"; + + Assert.Throws(() => + { + FileUtilities.NormalizePath(filePath); + }); + } + + [WindowsOnlyFact] + public void CannotNormalizePathWithNewLineAndSpace() + { + string filePath = "\r\n C:\\work\\sdk3\\artifacts\\tmp\\Debug\\SimpleNamesWi---6143883E\\NETFrameworkLibrary\\bin\\Debug\\net462\\NETFrameworkLibrary.dll\r\n "; + +#if FEATURE_LEGACY_GETFULLPATH + Assert.Throws(() => FileUtilities.NormalizePath(filePath)); +#else + Assert.NotEqual("C:\\work\\sdk3\\artifacts\\tmp\\Debug\\SimpleNamesWi---6143883E\\NETFrameworkLibrary\\bin\\Debug\\net462\\NETFrameworkLibrary.dll", FileUtilities.NormalizePath(filePath)); +#endif + } + + [Fact] + public void FileOrDirectoryExistsNoThrow() + { + var isWindows = NativeMethodsShared.IsWindows; + + Assert.False(FileUtilities.FileOrDirectoryExistsNoThrow("||")); + Assert.False(FileUtilities.FileOrDirectoryExistsNoThrow(isWindows ? @"c:\doesnot_exist" : "/doesnot_exist")); + Assert.True(FileUtilities.FileOrDirectoryExistsNoThrow(isWindows ? @"c:\" : "/")); + Assert.True(FileUtilities.FileOrDirectoryExistsNoThrow(Path.GetTempPath())); + + string path = null; + + try + { + path = FileUtilities.GetTemporaryFile(); + Assert.True(FileUtilities.FileOrDirectoryExistsNoThrow(path)); + } + finally + { + File.Delete(path); + } + } + +#if FEATURE_ENVIRONMENT_SYSTEMDIRECTORY + // These tests will need to be redesigned for Linux + + [ConditionalFact(nameof(RunTestsThatDependOnWindowsShortPathBehavior_Workaround4241))] + public void FileOrDirectoryExistsNoThrowTooLongWithDots() + { + int length = (Environment.SystemDirectory + @"\" + @"\..\..\..\" + Environment.SystemDirectory.Substring(3)).Length; + + string longPart = new string('x', 260 - length); // We want the shortest that is > max path. + + string inputPath = Environment.SystemDirectory + @"\" + longPart + @"\..\..\..\" + Environment.SystemDirectory.Substring(3); + Console.WriteLine(inputPath.Length); + + // "c:\windows\system32\\..\..\windows\system32" exists + Assert.True(FileUtilities.FileOrDirectoryExistsNoThrow(inputPath)); + Assert.False(FileUtilities.FileOrDirectoryExistsNoThrow(inputPath.Replace('\\', 'X'))); + } + + [ConditionalFact(nameof(RunTestsThatDependOnWindowsShortPathBehavior_Workaround4241))] + public void FileOrDirectoryExistsNoThrowTooLongWithDotsRelative() + { + int length = (Environment.SystemDirectory + @"\" + @"\..\..\..\" + Environment.SystemDirectory.Substring(3)).Length; + + string longPart = new string('x', 260 - length); // We want the shortest that is > max path. + + string inputPath = longPart + @"\..\..\..\" + Environment.SystemDirectory.Substring(3); + Console.WriteLine(inputPath.Length); + + // "c:\windows\system32\\..\..\windows\system32" exists + + string currentDirectory = Directory.GetCurrentDirectory(); + + try + { + Directory.SetCurrentDirectory(Environment.SystemDirectory); + + Assert.True(FileUtilities.FileOrDirectoryExistsNoThrow(inputPath)); + Assert.False(FileUtilities.FileOrDirectoryExistsNoThrow(inputPath.Replace('\\', 'X'))); + } + finally + { + Directory.SetCurrentDirectory(currentDirectory); + } + } + + [Fact] + public void DirectoryExistsNoThrowTooLongWithDots() + { + string path = Path.Combine(Environment.SystemDirectory, "..", "..", "..") + Path.DirectorySeparatorChar; + if (NativeMethodsShared.IsWindows) + { + path += Environment.SystemDirectory.Substring(3); + } + + int length = path.Length; + + string longPart = new string('x', 260 - length); // We want the shortest that is > max path. + + string inputPath = Path.Combine(new[] { Environment.SystemDirectory, longPart, "..", "..", ".." }) + + Path.DirectorySeparatorChar; + if (NativeMethodsShared.IsWindows) + { + path += Environment.SystemDirectory.Substring(3); + } + + Console.WriteLine(inputPath.Length); + + // "c:\windows\system32\\..\..\windows\system32" exists + Assert.True(FileUtilities.DirectoryExistsNoThrow(inputPath)); + } + + [ConditionalFact(nameof(RunTestsThatDependOnWindowsShortPathBehavior_Workaround4241))] + public void DirectoryExistsNoThrowTooLongWithDotsRelative() + { + int length = (Environment.SystemDirectory + @"\" + @"\..\..\..\" + Environment.SystemDirectory.Substring(3)).Length; + + string longPart = new string('x', 260 - length); // We want the shortest that is > max path. + + string inputPath = longPart + @"\..\..\..\" + Environment.SystemDirectory.Substring(3); + Console.WriteLine(inputPath.Length); + + // "c:\windows\system32\\..\..\..\windows\system32" exists + + string currentDirectory = Directory.GetCurrentDirectory(); + + try + { + Directory.SetCurrentDirectory(Environment.SystemDirectory); + + FileUtilities.DirectoryExistsNoThrow(inputPath).ShouldBeTrue(); + FileUtilities.DirectoryExistsNoThrow(inputPath.Replace('\\', 'X')).ShouldBeFalse(); + } + finally + { + Directory.SetCurrentDirectory(currentDirectory); + } + } + + public static bool RunTestsThatDependOnWindowsShortPathBehavior_Workaround4241() + { + // Run these tests only when we're not on Windows + return !NativeMethodsShared.IsWindows || + // OR we're on Windows and long paths aren't enabled + // https://github.com/dotnet/msbuild/issues/4241 + NativeMethodsShared.IsMaxPathLegacyWindows(); + } + + [ConditionalFact(nameof(RunTestsThatDependOnWindowsShortPathBehavior_Workaround4241))] + public void FileExistsNoThrowTooLongWithDots() + { + int length = (Environment.SystemDirectory + @"\" + @"\..\..\..\" + Environment.SystemDirectory.Substring(3) + @"\..\explorer.exe").Length; + + string longPart = new string('x', 260 - length); // We want the shortest that is > max path. + + string inputPath = Environment.SystemDirectory + @"\" + longPart + @"\..\..\..\" + Environment.SystemDirectory.Substring(3) + @"\..\explorer.exe"; + Console.WriteLine(inputPath.Length); + Console.WriteLine(inputPath); + + // "c:\windows\system32\\..\..\windows\system32" exists + Assert.True(FileUtilities.FileExistsNoThrow(inputPath)); + } + + [ConditionalFact(nameof(RunTestsThatDependOnWindowsShortPathBehavior_Workaround4241))] + public void FileExistsNoThrowTooLongWithDotsRelative() + { + int length = (Environment.SystemDirectory + @"\" + @"\..\..\..\" + Environment.SystemDirectory.Substring(3) + @"\..\explorer.exe").Length; + + string longPart = new string('x', 260 - length); // We want the shortest that is > max path. + + string inputPath = longPart + @"\..\..\..\" + Environment.SystemDirectory.Substring(3) + @"\..\explorer.exe"; + Console.WriteLine(inputPath.Length); + + // "c:\windows\system32\\..\..\windows\system32" exists + + string currentDirectory = Directory.GetCurrentDirectory(); + + try + { + Directory.SetCurrentDirectory(Environment.SystemDirectory); + + Assert.True(FileUtilities.FileExistsNoThrow(inputPath)); + Assert.False(FileUtilities.FileExistsNoThrow(inputPath.Replace('\\', 'X'))); + } + finally + { + Directory.SetCurrentDirectory(currentDirectory); + } + } + + [Fact] + public void GetFileInfoNoThrowTooLongWithDots() + { + int length = (Environment.SystemDirectory + @"\" + @"\..\..\..\" + Environment.SystemDirectory.Substring(3) + @"\..\explorer.exe").Length; + + string longPart = new string('x', 260 - length); // We want the shortest that is > max path. + + string inputPath = Environment.SystemDirectory + @"\" + longPart + @"\..\..\..\" + Environment.SystemDirectory.Substring(3) + @"\..\explorer.exe"; + Console.WriteLine(inputPath.Length); + + // "c:\windows\system32\\..\..\windows\system32" exists + Assert.True(FileUtilities.GetFileInfoNoThrow(inputPath) != null); + Assert.False(FileUtilities.GetFileInfoNoThrow(inputPath.Replace('\\', 'X')) != null); + } + + [Fact] + public void GetFileInfoNoThrowTooLongWithDotsRelative() + { + int length = (Environment.SystemDirectory + @"\" + @"\..\..\..\" + Environment.SystemDirectory.Substring(3) + @"\..\explorer.exe").Length; + + string longPart = new string('x', 260 - length); // We want the shortest that is > max path. + + string inputPath = longPart + @"\..\..\..\" + Environment.SystemDirectory.Substring(3) + @"\..\explorer.exe"; + Console.WriteLine(inputPath.Length); + + // "c:\windows\system32\\..\..\windows\system32" exists + + string currentDirectory = Directory.GetCurrentDirectory(); + + try + { + Directory.SetCurrentDirectory(Environment.SystemDirectory); + + Assert.True(FileUtilities.GetFileInfoNoThrow(inputPath) != null); + Assert.False(FileUtilities.GetFileInfoNoThrow(inputPath.Replace('\\', 'X')) != null); + } + finally + { + Directory.SetCurrentDirectory(currentDirectory); + } + } +#endif + + /// + /// Simple test, neither the base file nor retry files exist + /// + [Fact] + public void GenerateTempFileNameSimple() + { + string path = null; + + try + { + path = FileUtilities.GetTemporaryFile(); + + Assert.EndsWith(".tmp", path); + Assert.True(File.Exists(path)); + Assert.StartsWith(Path.GetTempPath(), path); + } + finally + { + File.Delete(path); + } + } + + /// + /// Choose an extension + /// + [Fact] + public void GenerateTempFileNameWithExtension() + { + string path = null; + + try + { + path = FileUtilities.GetTemporaryFile(".bat"); + + Assert.EndsWith(".bat", path); + Assert.True(File.Exists(path)); + Assert.StartsWith(Path.GetTempPath(), path); + } + finally + { + File.Delete(path); + } + } + + /// + /// Choose a (missing) directory and extension + /// + [Fact] + public void GenerateTempFileNameWithDirectoryAndExtension() + { + string path = null; + string directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "subfolder"); + + try + { + path = FileUtilities.GetTemporaryFile(directory, null, ".bat"); + + Assert.EndsWith(".bat", path); + Assert.True(File.Exists(path)); + Assert.StartsWith(directory, path); + } + finally + { + File.Delete(path); + FileUtilities.DeleteWithoutTrailingBackslash(directory); + } + } + + /// + /// Extension without a period + /// + [Fact] + public void GenerateTempFileNameWithExtensionNoPeriod() + { + string path = null; + + try + { + path = FileUtilities.GetTemporaryFile("bat"); + + Assert.EndsWith(".bat", path); + Assert.True(File.Exists(path)); + Assert.StartsWith(Path.GetTempPath(), path); + } + finally + { + File.Delete(path); + } + } + + /// + /// Extension is invalid + /// + [Fact] + [Trait("Category", "netcore-osx-failing")] + [Trait("Category", "netcore-linux-failing")] + public void GenerateTempBatchFileWithBadExtension() + { + Assert.Throws(() => + { + FileUtilities.GetTemporaryFile("|"); + }); + } + + /// + /// Directory is invalid + /// + [Fact] + [Trait("Category", "netcore-osx-failing")] + [Trait("Category", "netcore-linux-failing")] + public void GenerateTempBatchFileWithBadDirectory() + { + Assert.Throws(() => + { + FileUtilities.GetTemporaryFile("|", null, ".tmp"); + }); + } + + [UnixOnlyFact] + public void AbsolutePathLooksLikeUnixPathOnUnix() + { + var secondSlash = SystemSpecificAbsolutePath.Substring(1).IndexOf(Path.DirectorySeparatorChar) + 1; + var rootLevelPath = SystemSpecificAbsolutePath.Substring(0, secondSlash); + + Assert.True(FileUtilities.LooksLikeUnixFilePath(SystemSpecificAbsolutePath)); + Assert.True(FileUtilities.LooksLikeUnixFilePath(rootLevelPath)); + } + + [WindowsOnlyFact] + public void PathDoesNotLookLikeUnixPathOnWindows() + { + Assert.False(FileUtilities.LooksLikeUnixFilePath(SystemSpecificAbsolutePath)); + Assert.False(FileUtilities.LooksLikeUnixFilePath("/path/that/looks/unixy")); + Assert.False(FileUtilities.LooksLikeUnixFilePath("/root")); + } + + [UnixOnlyFact] + public void RelativePathLooksLikeUnixPathOnUnixWithBaseDirectory() + { + string filePath = ObjectModelHelpers.CreateFileInTempProjectDirectory("first/second/file.txt", String.Empty); + string oldCWD = Directory.GetCurrentDirectory(); + + try + { + // /first + string firstDirectory = Path.GetDirectoryName(Path.GetDirectoryName(filePath)); + string tmpDirectory = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(filePath))); + + Directory.SetCurrentDirectory(tmpDirectory); + + // We are in and second is not under that, so this will be false + Assert.False(FileUtilities.LooksLikeUnixFilePath("second/file.txt")); + + // .. but if we have baseDirectory:firstDirectory, then it will be true + Assert.True(FileUtilities.LooksLikeUnixFilePath("second/file.txt", firstDirectory)); + } + finally + { + if (filePath != null) + { + File.Delete(filePath); + } + Directory.SetCurrentDirectory(oldCWD); + } + } + + [UnixOnlyFact] + public void RelativePathMaybeAdjustFilePathWithBaseDirectory() + { + // /first/second/file.txt + string filePath = ObjectModelHelpers.CreateFileInTempProjectDirectory("first/second/file.txt", String.Empty); + string oldCWD = Directory.GetCurrentDirectory(); + + try + { + // /first + string firstDirectory = Path.GetDirectoryName(Path.GetDirectoryName(filePath)); + string tmpDirectory = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(filePath))); + + Directory.SetCurrentDirectory(tmpDirectory); + + // We are in and second is not under that, so this won't convert + Assert.Equal("second\\file.txt", FileUtilities.MaybeAdjustFilePath("second\\file.txt")); + + // .. but if we have baseDirectory:firstDirectory, then it will + Assert.Equal("second/file.txt", FileUtilities.MaybeAdjustFilePath("second\\file.txt", firstDirectory)); + } + finally + { + if (filePath != null) + { + File.Delete(filePath); + } + Directory.SetCurrentDirectory(oldCWD); + } + } + + private static string SystemSpecificAbsolutePath => BuildEnvironmentHelper.ExecutingAssemblyPath; + + + [Fact] + public void GetFolderAboveTest() + { + string root = NativeMethodsShared.IsWindows ? @"c:\" : "/"; + string path = Path.Combine(root, "1", "2", "3", "4", "5"); + + Assert.Equal(Path.Combine(root, "1", "2", "3", "4", "5"), FileUtilities.GetFolderAbove(path, 0)); + Assert.Equal(Path.Combine(root, "1", "2", "3", "4"), FileUtilities.GetFolderAbove(path)); + Assert.Equal(Path.Combine(root, "1", "2", "3"), FileUtilities.GetFolderAbove(path, 2)); + Assert.Equal(Path.Combine(root, "1", "2"), FileUtilities.GetFolderAbove(path, 3)); + Assert.Equal(Path.Combine(root, "1"), FileUtilities.GetFolderAbove(path, 4)); + Assert.Equal(root, FileUtilities.GetFolderAbove(path, 5)); + Assert.Equal(root, FileUtilities.GetFolderAbove(path, 99)); + + Assert.Equal(root, FileUtilities.GetFolderAbove(root, 99)); + } + + [Fact] + public void CombinePathsTest() + { + // These tests run in .NET 4+, so we can cheat + var root = @"c:\"; + + Assert.Equal( + Path.Combine(root, "path1"), + FileUtilities.CombinePaths(root, "path1")); + + Assert.Equal( + Path.Combine(root, "path1", "path2", "file.txt"), + FileUtilities.CombinePaths(root, "path1", "path2", "file.txt")); + } + + [Theory] + [InlineData(@"c:\a\.\b", true)] + [InlineData(@"c:\a\..\b", true)] + [InlineData(@"c:\a\..", true)] + [InlineData(@"c:\a\.", true)] + [InlineData(@".\a", true)] + [InlineData(@"..\b", true)] + [InlineData(@"..", true)] + [InlineData(@".", true)] + [InlineData(@"..\", true)] + [InlineData(@".\", true)] + [InlineData(@"\..", true)] + [InlineData(@"\.", true)] + [InlineData(@"..\..\a", true)] + [InlineData(@"..\..\..\a", true)] + [InlineData(@"b..\", false)] + [InlineData(@"b.\", false)] + [InlineData(@"\b..", false)] + [InlineData(@"\b.", false)] + [InlineData(@"\b..\", false)] + [InlineData(@"\b.\", false)] + [InlineData(@"...", false)] + [InlineData(@"....", false)] + public void ContainsRelativeSegmentsTest(string path, bool expectedResult) + { + FileUtilities.ContainsRelativePathSegments(path).ShouldBe(expectedResult); + } + + [Theory] + [InlineData("a/b/c/d", 0, "")] + [InlineData("a/b/c/d", 1, "d")] + [InlineData("a/b/c/d", 2, "c/d")] + [InlineData("a/b/c/d", 3, "b/c/d")] + [InlineData("a/b/c/d", 4, "a/b/c/d")] + [InlineData("a/b/c/d", 5, "a/b/c/d")] + [InlineData(@"a\/\/\//b/\/\/\//c//\/\/\/d/\//\/\/", 2, "c/d")] + public static void TestTruncatePathToTrailingSegments(string path, int trailingSegments, string expectedTruncatedPath) + { + expectedTruncatedPath = expectedTruncatedPath.Replace('/', Path.DirectorySeparatorChar); + + FileUtilities.TruncatePathToTrailingSegments(path, trailingSegments).ShouldBe(expectedTruncatedPath); + } + + /// + /// Exercises FileUtilities.EnsureSingleQuotes + /// + [Theory] + [InlineData(null, null)] // Null test + [InlineData("", "")] // Empty string test + [InlineData(@" ", @"' '")] // One character which is a space + [InlineData(@"'", @"'''")] // One character which is a single quote + [InlineData(@"""", @"'""'")] // One character which is a double quote + [InlineData(@"example", @"'example'")] // Unquoted string + [InlineData(@"'example'", @"'example'")] // Single quoted string + [InlineData(@"""example""", @"'example'")] // Double quoted string + [InlineData(@"'example""", @"''example""'")] // Mixed Quotes - Leading Single + [InlineData(@"""example'", @"'""example''")] // Mixed Quotes - Leading Double + [InlineData(@"ex""am'ple", @"'ex""am'ple'")] // Interior Quotes + public void EnsureSingleQuotesTest(string path, string expectedResult) + { + FileUtilities.EnsureSingleQuotes(path).ShouldBe(expectedResult); + } + + /// + /// Exercises FileUtilities.EnsureDoubleQuotes + /// + [Theory] + [InlineData(null, null)] // Null test + [InlineData("", "")] // Empty string test + [InlineData(@" ", @""" """)] // One character which is a space + [InlineData(@"'", @"""'""")] // One character which is a single quote + [InlineData(@"""", @"""""""")] // One character which is a double quote + [InlineData(@"example", @"""example""")] // Unquoted string + [InlineData(@"'example'", @"""example""")] // Single quoted string + [InlineData(@"""example""", @"""example""")] // Double quoted string + [InlineData(@"'example""", @"""'example""""")] // Mixed Quotes - Leading Single + [InlineData(@"""example'", @"""""example'""")] // Mixed Quotes - Leading Double + [InlineData(@"ex""am'ple", @"""ex""am'ple""")] // Interior Quotes + public void EnsureDoubleQuotesTest(string path, string expectedResult) + { + FileUtilities.EnsureDoubleQuotes(path).ShouldBe(expectedResult); + } +} diff --git a/src/Shared/UnitTests/FileUtilities_Tests.cs b/src/Shared/UnitTests/FileUtilities_Tests.cs deleted file mode 100644 index ca1d5623348..00000000000 --- a/src/Shared/UnitTests/FileUtilities_Tests.cs +++ /dev/null @@ -1,1058 +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.IO; -using System.Linq; -using System.Threading; -using Microsoft.Build.Framework; -using Microsoft.Build.Shared; -using Shouldly; -using Xunit; - -#nullable disable - -namespace Microsoft.Build.UnitTests -{ - public class FileUtilities_Tests - { - /// - /// Exercises ItemSpecModifiers.GetItemSpecModifier - /// - [Fact] - [Trait("Category", "netcore-osx-failing")] - [Trait("Category", "netcore-linux-failing")] - public void GetItemSpecModifier() - { - TestGetItemSpecModifier(Directory.GetCurrentDirectory()); - TestGetItemSpecModifier(null); - } - - private static void TestGetItemSpecModifier(string currentDirectory) - { - string cache = null; - string modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, "foo", String.Empty, ItemSpecModifiers.RecursiveDir, ref cache); - Assert.Equal(String.Empty, modifier); - - cache = null; - modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, "foo", String.Empty, ItemSpecModifiers.ModifiedTime, ref cache); - Assert.Equal(String.Empty, modifier); - - cache = null; - modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, @"foo\goo", String.Empty, ItemSpecModifiers.RelativeDir, ref cache); - Assert.Equal(@"foo" + Path.DirectorySeparatorChar, modifier); - - // confirm we get the same thing back the second time - modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, @"foo\goo", String.Empty, ItemSpecModifiers.RelativeDir, ref cache); - Assert.Equal(@"foo" + Path.DirectorySeparatorChar, modifier); - - cache = null; - string itemSpec = NativeMethodsShared.IsWindows ? @"c:\foo.txt" : "/foo.txt"; - string itemSpecDir = NativeMethodsShared.IsWindows ? @"c:\" : "/"; - modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.FullPath, ref cache); - Assert.Equal(itemSpec, modifier); - Assert.Equal(itemSpec, cache); - - modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.RootDir, ref cache); - Assert.Equal(itemSpecDir, modifier); - - modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.Filename, ref cache); - Assert.Equal(@"foo", modifier); - - modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.Extension, ref cache); - Assert.Equal(@".txt", modifier); - - modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.Directory, ref cache); - Assert.Equal(String.Empty, modifier); - - modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, String.Empty, ItemSpecModifiers.Identity, ref cache); - Assert.Equal(itemSpec, modifier); - - string projectPath = NativeMethodsShared.IsWindows ? @"c:\abc\goo.proj" : @"/abc/goo.proj"; - string projectPathDir = NativeMethodsShared.IsWindows ? @"c:\abc\" : @"/abc/"; - modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, projectPath, ItemSpecModifiers.DefiningProjectDirectory, ref cache); - Assert.Equal(projectPathDir, modifier); - - modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, projectPath, ItemSpecModifiers.DefiningProjectExtension, ref cache); - Assert.Equal(@".proj", modifier); - - modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, projectPath, ItemSpecModifiers.DefiningProjectFullPath, ref cache); - Assert.Equal(projectPath, modifier); - - modifier = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, itemSpec, projectPath, ItemSpecModifiers.DefiningProjectName, ref cache); - Assert.Equal(@"goo", modifier); - } - - [Fact] - public void MakeRelativeTests() - { - if (NativeMethodsShared.IsWindows) - { - Assert.Equal(@"foo.cpp", FileUtilities.MakeRelative(@"c:\abc\def", @"c:\abc\def\foo.cpp")); - Assert.Equal(@"def\foo.cpp", FileUtilities.MakeRelative(@"c:\abc\", @"c:\abc\def\foo.cpp")); - Assert.Equal(@"..\foo.cpp", FileUtilities.MakeRelative(@"c:\abc\def\xyz", @"c:\abc\def\foo.cpp")); - Assert.Equal(@"..\ttt\foo.cpp", FileUtilities.MakeRelative(@"c:\abc\def\xyz\", @"c:\abc\def\ttt\foo.cpp")); - Assert.Equal(@"e:\abc\def\foo.cpp", FileUtilities.MakeRelative(@"c:\abc\def", @"e:\abc\def\foo.cpp")); - Assert.Equal(@"foo.cpp", FileUtilities.MakeRelative(@"\\aaa\abc\def", @"\\aaa\abc\def\foo.cpp")); - Assert.Equal(@"foo.cpp", FileUtilities.MakeRelative(@"c:\abc\def", @"foo.cpp")); - Assert.Equal(@"\\host\path\file", FileUtilities.MakeRelative(@"c:\abc\def", @"\\host\path\file")); - Assert.Equal(@"\\host\d$\file", FileUtilities.MakeRelative(@"c:\abc\def", @"\\host\d$\file")); - Assert.Equal(@"..\fff\ggg.hh", FileUtilities.MakeRelative(@"c:\foo\bar\..\abc\cde", @"c:\foo\bar\..\abc\fff\ggg.hh")); - - /* Directories */ - Assert.Equal(@"def\", FileUtilities.MakeRelative(@"c:\abc\", @"c:\abc\def\")); - Assert.Equal(@"..\", FileUtilities.MakeRelative(@"c:\abc\def\xyz\", @"c:\abc\def\")); - Assert.Equal(@"..\ttt\", FileUtilities.MakeRelative(@"c:\abc\def\xyz\", @"c:\abc\def\ttt\")); - Assert.Equal(@".", FileUtilities.MakeRelative(@"c:\abc\def\", @"c:\abc\def\")); - - /* Directory + File */ - Assert.Equal(@"def", FileUtilities.MakeRelative(@"c:\abc\", @"c:\abc\def")); - Assert.Equal(@"..\..\ghi", FileUtilities.MakeRelative(@"c:\abc\def\xyz\", @"c:\abc\ghi")); - Assert.Equal(@"..\ghi", FileUtilities.MakeRelative(@"c:\abc\def\xyz\", @"c:\abc\def\ghi")); - Assert.Equal(@"..\ghi", FileUtilities.MakeRelative(@"c:\abc\def\", @"c:\abc\ghi")); - - /* File + Directory */ - Assert.Equal(@"def\", FileUtilities.MakeRelative(@"c:\abc", @"c:\abc\def\")); - Assert.Equal(@"..\", FileUtilities.MakeRelative(@"c:\abc\def\xyz", @"c:\abc\def\")); - Assert.Equal(@"..\ghi\", FileUtilities.MakeRelative(@"c:\abc\def\xyz", @"c:\abc\def\ghi\")); - Assert.Equal(@".", FileUtilities.MakeRelative(@"c:\abc\def", @"c:\abc\def\")); - } - else - { - Assert.Equal(@"bar.cpp", FileUtilities.MakeRelative(@"/abc/def", @"/abc/def/bar.cpp")); - Assert.Equal(@"def/foo.cpp", FileUtilities.MakeRelative(@"/abc/", @"/abc/def/foo.cpp")); - Assert.Equal(@"../foo.cpp", FileUtilities.MakeRelative(@"/abc/def/xyz", @"/abc/def/foo.cpp")); - Assert.Equal(@"../ttt/foo.cpp", FileUtilities.MakeRelative(@"/abc/def/xyz/", @"/abc/def/ttt/foo.cpp")); - Assert.Equal(@"foo.cpp", FileUtilities.MakeRelative(@"/abc/def", @"foo.cpp")); - Assert.Equal(@"../fff/ggg.hh", FileUtilities.MakeRelative(@"/foo/bar/../abc/cde", @"/foo/bar/../abc/fff/ggg.hh")); - - /* Directories */ - Assert.Equal(@"def/", FileUtilities.MakeRelative(@"/abc/", @"/abc/def/")); - Assert.Equal(@"../", FileUtilities.MakeRelative(@"/abc/def/xyz/", @"/abc/def/")); - Assert.Equal(@"../ttt/", FileUtilities.MakeRelative(@"/abc/def/xyz/", @"/abc/def/ttt/")); - Assert.Equal(@".", FileUtilities.MakeRelative(@"/abc/def/", @"/abc/def/")); - - /* Directory + File */ - Assert.Equal(@"def", FileUtilities.MakeRelative(@"/abc/", @"/abc/def")); - Assert.Equal(@"../../ghi", FileUtilities.MakeRelative(@"/abc/def/xyz/", @"/abc/ghi")); - Assert.Equal(@"../ghi", FileUtilities.MakeRelative(@"/abc/def/xyz/", @"/abc/def/ghi")); - Assert.Equal(@"../ghi", FileUtilities.MakeRelative(@"/abc/def/", @"/abc/ghi")); - - /* File + Directory */ - Assert.Equal(@"def/", FileUtilities.MakeRelative(@"/abc", @"/abc/def/")); - Assert.Equal(@"../", FileUtilities.MakeRelative(@"/abc/def/xyz", @"/abc/def/")); - Assert.Equal(@"../ghi/", FileUtilities.MakeRelative(@"/abc/def/xyz", @"/abc/def/ghi/")); - Assert.Equal(@".", FileUtilities.MakeRelative(@"/abc/def", @"/abc/def/")); - } - } - - /// - /// Exercises ItemSpecModifiers.GetItemSpecModifier on a bad path. - /// - [WindowsFullFrameworkOnlyFact(additionalMessage: ".NET Core 2.1+ no longer validates paths: https://github.com/dotnet/corefx/issues/27779#issuecomment-371253486.")] - public void GetItemSpecModifierOnBadPath() - { - Assert.Throws(() => - { - TestGetItemSpecModifierOnBadPath(Directory.GetCurrentDirectory()); - }); - } - /// - /// Exercises ItemSpecModifiers.GetItemSpecModifier on a bad path. - /// - [WindowsFullFrameworkOnlyFact(additionalMessage: ".NET Core 2.1+ no longer validates paths: https://github.com/dotnet/corefx/issues/27779#issuecomment-371253486.")] - public void GetItemSpecModifierOnBadPath2() - { - Assert.Throws(() => - { - TestGetItemSpecModifierOnBadPath(null); - }); - } - - private static void TestGetItemSpecModifierOnBadPath(string currentDirectory) - { - try - { - string cache = null; - ItemSpecModifiers.GetItemSpecModifier(currentDirectory, @"http://www.microsoft.com", String.Empty, ItemSpecModifiers.RootDir, ref cache); - } - catch (Exception e) - { - // so I can see the exception message in NUnit's "Standard Out" window - Console.WriteLine(e.Message); - throw; - } - } - - [Fact] - public void GetFileInfoNoThrowBasic() - { - string file = null; - try - { - file = FileUtilities.GetTemporaryFile(); - FileInfo info = FileUtilities.GetFileInfoNoThrow(file); - Assert.Equal(info.LastWriteTime, new FileInfo(file).LastWriteTime); - } - finally - { - if (file != null) - { - File.Delete(file); - } - } - } - - [Fact] - public void GetFileInfoNoThrowNonexistent() - { - FileInfo info = FileUtilities.GetFileInfoNoThrow("this_file_is_nonexistent"); - Assert.Null(info); - } - - /// - /// Exercises FrameworkFileUtilities.EndsWithSlash - /// - [Fact] - [Trait("Category", "netcore-osx-failing")] - [Trait("Category", "netcore-linux-failing")] - public void EndsWithSlash() - { - Assert.True(FileUtilities.EndsWithSlash(@"C:\foo\")); - Assert.True(FileUtilities.EndsWithSlash(@"C:\")); - Assert.True(FileUtilities.EndsWithSlash(@"\")); - - Assert.True(FileUtilities.EndsWithSlash(@"http://www.microsoft.com/")); - Assert.True(FileUtilities.EndsWithSlash(@"//server/share/")); - Assert.True(FileUtilities.EndsWithSlash(@"/")); - - Assert.False(FileUtilities.EndsWithSlash(@"C:\foo")); - Assert.False(FileUtilities.EndsWithSlash(@"C:")); - Assert.False(FileUtilities.EndsWithSlash(@"foo")); - - // confirm that empty string doesn't barf - Assert.False(FileUtilities.EndsWithSlash(String.Empty)); - } - - /// - /// Exercises FileUtilities.GetDirectory - /// - [Fact] - [Trait("Category", "netcore-osx-failing")] - [Trait("Category", "netcore-linux-failing")] - public void GetDirectoryWithTrailingSlash() - { - Assert.Equal(NativeMethodsShared.IsWindows ? @"c:\" : "/", FileUtilities.GetDirectory(NativeMethodsShared.IsWindows ? @"c:\" : "/")); - Assert.Equal(NativeMethodsShared.IsWindows ? @"c:\" : "/", FileUtilities.GetDirectory(NativeMethodsShared.IsWindows ? @"c:\foo" : "/foo")); - Assert.Equal(NativeMethodsShared.IsWindows ? @"c:" : "/", FileUtilities.GetDirectory(NativeMethodsShared.IsWindows ? @"c:" : "/")); - Assert.Equal(FileUtilities.FixFilePath(@"\"), FileUtilities.GetDirectory(@"\")); - Assert.Equal(FileUtilities.FixFilePath(@"\"), FileUtilities.GetDirectory(@"\foo")); - Assert.Equal(FileUtilities.FixFilePath(@"..\"), FileUtilities.GetDirectory(@"..\foo")); - Assert.Equal(FileUtilities.FixFilePath(@"\foo\"), FileUtilities.GetDirectory(@"\foo\")); - Assert.Equal(FileUtilities.FixFilePath(@"\\server\share"), FileUtilities.GetDirectory(@"\\server\share")); - Assert.Equal(FileUtilities.FixFilePath(@"\\server\share\"), FileUtilities.GetDirectory(@"\\server\share\")); - Assert.Equal(FileUtilities.FixFilePath(@"\\server\share\"), FileUtilities.GetDirectory(@"\\server\share\file")); - Assert.Equal(FileUtilities.FixFilePath(@"\\server\share\directory\"), FileUtilities.GetDirectory(@"\\server\share\directory\")); - Assert.Equal(FileUtilities.FixFilePath(@"foo\"), FileUtilities.GetDirectory(@"foo\bar")); - Assert.Equal(FileUtilities.FixFilePath(@"\foo\bar\"), FileUtilities.GetDirectory(@"\foo\bar\")); - Assert.Equal(String.Empty, FileUtilities.GetDirectory("foo")); - } - - [Theory] - [InlineData("foo.txt", new[] { ".txt" })] - [InlineData("foo.txt", new[] { ".TXT" })] - [InlineData("foo.txt", new[] { ".EXE", ".TXT" })] - public void HasExtension_WhenFileNameHasExtension_ReturnsTrue(string fileName, string[] allowedExtensions) - { - var result = FileUtilities.HasExtension(fileName, allowedExtensions); - - if (!FileUtilities.GetIsFileSystemCaseSensitive() || allowedExtensions.Any(extension => fileName.Contains(extension))) - { - result.ShouldBeTrue(); - } - } - - [Theory] - [InlineData("foo.txt", new[] { ".DLL" })] - [InlineData("foo.txt", new[] { ".EXE", ".DLL" })] - [InlineData("foo.exec", new[] { ".exe", })] - [InlineData("foo.exe", new[] { ".exec", })] - [InlineData("foo", new[] { ".exe", })] - [InlineData("", new[] { ".exe" })] - [InlineData(null, new[] { ".exe" })] - public void HasExtension_WhenFileNameDoesNotHaveExtension_ReturnsFalse(string fileName, string[] allowedExtensions) - { - var result = FileUtilities.HasExtension(fileName, allowedExtensions); - - Assert.False(result); - } - - [WindowsFullFrameworkOnlyFact] - public void HasExtension_WhenInvalidFileName_ThrowsArgumentException() - { - Assert.Throws(() => - { - FileUtilities.HasExtension("|/", new[] { ".exe" }); - }); - } - - [Fact] - public void HasExtension_UsesOrdinalIgnoreCase() - { - var currentCulture = Thread.CurrentThread.CurrentCulture; - try - { - Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR"); // Turkish - - var result = FileUtilities.HasExtension("foo.ini", new string[] { ".INI" }); - - result.ShouldBe(!FileUtilities.GetIsFileSystemCaseSensitive()); - } - finally - { - Thread.CurrentThread.CurrentCulture = currentCulture; - } - } - - /// - /// Exercises FrameworkFileUtilities.EnsureTrailingSlash - /// - [Fact] - public void EnsureTrailingSlash() - { - // Doesn't have a trailing slash to start with. - Assert.Equal(FileUtilities.FixFilePath(@"foo\bar\"), FileUtilities.EnsureTrailingSlash(@"foo\bar")); // "test 1" - Assert.Equal(FileUtilities.FixFilePath(@"foo/bar\"), FileUtilities.EnsureTrailingSlash(@"foo/bar")); // "test 2" - - // Already has a trailing slash to start with. - Assert.Equal(FileUtilities.FixFilePath(@"foo/bar/"), FileUtilities.EnsureTrailingSlash(@"foo/bar/")); // test 3" - Assert.Equal(FileUtilities.FixFilePath(@"foo\bar\"), FileUtilities.EnsureTrailingSlash(@"foo\bar\")); // test 4" - Assert.Equal(FileUtilities.FixFilePath(@"foo/bar\"), FileUtilities.EnsureTrailingSlash(@"foo/bar\")); // test 5" - Assert.Equal(FileUtilities.FixFilePath(@"foo\bar/"), FileUtilities.EnsureTrailingSlash(@"foo\bar/")); // "test 5" - } - - /// - /// Exercises ItemSpecModifiers.IsItemSpecModifier - /// - [Fact] - public void IsItemSpecModifier() - { - // Positive matches using exact case. - Assert.True(ItemSpecModifiers.IsItemSpecModifier("FullPath")); // "test 1" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("RootDir")); // "test 2" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("Filename")); // "test 3" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("Extension")); // "test 4" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("RelativeDir")); // "test 5" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("Directory")); // "test 6" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("RecursiveDir")); // "test 7" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("Identity")); // "test 8" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("ModifiedTime")); // "test 9" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("CreatedTime")); // "test 10" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("AccessedTime")); // "test 11" - - // Positive matches using different case. - Assert.True(ItemSpecModifiers.IsItemSpecModifier("fullPath")); // "test 21" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("rootDir")); // "test 22" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("filename")); // "test 23" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("extension")); // "test 24" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("relativeDir")); // "test 25" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("directory")); // "test 26" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("recursiveDir")); // "test 27" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("identity")); // "test 28" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("modifiedTime")); // "test 29" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("createdTime")); // "test 30" - Assert.True(ItemSpecModifiers.IsItemSpecModifier("accessedTime")); // "test 31" - - // Negative tests to get maximum code coverage inside the many different branches - // of ItemSpecModifiers.IsItemSpecModifier. - Assert.False(ItemSpecModifiers.IsItemSpecModifier("rootxxx")); // "test 41" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("Rootxxx")); // "test 42" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxx")); // "test 43" - - Assert.False(ItemSpecModifiers.IsItemSpecModifier("filexxxx")); // "test 44" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("Filexxxx")); // "test 45" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("idenxxxx")); // "test 46" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("Idenxxxx")); // "test 47" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxxx")); // "test 48" - - Assert.False(ItemSpecModifiers.IsItemSpecModifier("extenxxxx")); // "test 49" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("Extenxxxx")); // "test 50" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("direcxxxx")); // "test 51" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("Direcxxxx")); // "test 52" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxxxx")); // "test 53" - - Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxxxxx")); // "test 54" - - Assert.False(ItemSpecModifiers.IsItemSpecModifier("relativexxx")); // "test 55" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("Relativexxx")); // "test 56" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("createdxxxx")); // "test 57" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("Createdxxxx")); // "test 58" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxxxxxx")); // "test 59" - - Assert.False(ItemSpecModifiers.IsItemSpecModifier("recursivexxx")); // "test 60" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("Recursivexxx")); // "test 61" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("accessedxxxx")); // "test 62" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("Accessedxxxx")); // "test 63" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("modifiedxxxx")); // "test 64" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("Modifiedxxxx")); // "test 65" - Assert.False(ItemSpecModifiers.IsItemSpecModifier("xxxxxxxxxxxx")); // "test 66" - - Assert.False(ItemSpecModifiers.IsItemSpecModifier(null)); // "test 67" - } - - [Fact] - public void CheckDerivableItemSpecModifiers() - { - Assert.True(ItemSpecModifiers.IsDerivableItemSpecModifier("Filename")); - Assert.False(ItemSpecModifiers.IsDerivableItemSpecModifier("RecursiveDir")); - Assert.False(ItemSpecModifiers.IsDerivableItemSpecModifier("recursivedir")); - } - - [WindowsOnlyFact] - public void NormalizePathThatFitsIntoMaxPath() - { - string currentDirectory = @"c:\aardvark\aardvark\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890"; - string filePath = @"..\..\..\..\..\..\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\a.cs"; - string fullPath = @"c:\aardvark\aardvark\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\a.cs"; - - Assert.Equal(fullPath, FileUtilities.NormalizePath(Path.Combine(currentDirectory, filePath))); - } - - [LongPathSupportDisabledFact(fullFrameworkOnly: true, additionalMessage: "https://github.com/dotnet/msbuild/issues/4363")] - public void NormalizePathThatDoesntFitIntoMaxPath() - { - Assert.Throws(() => - { - string currentDirectory = @"c:\aardvark\aardvark\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890"; - string filePath = @"..\..\..\..\..\..\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\a.cs"; - - // This path ends up over 420 characters long - string fullPath = @"c:\aardvark\aardvark\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\a.cs"; - - Assert.Equal(fullPath, FileUtilities.NormalizePath(Path.Combine(currentDirectory, filePath))); - }); - } - - [WindowsOnlyFact] - public void GetItemSpecModifierRootDirThatFitsIntoMaxPath() - { - string currentDirectory = @"c:\aardvark\aardvark\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890"; - string fullPath = @"c:\aardvark\aardvark\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\1234567890\a.cs"; - string cache = fullPath; - - Assert.Equal(@"c:\", ItemSpecModifiers.GetItemSpecModifier(currentDirectory, fullPath, String.Empty, ItemSpecModifiers.RootDir, ref cache)); - } - - [Fact] - public void NormalizePathNull() - { - Assert.Throws(() => - { - Assert.Null(FileUtilities.NormalizePath(null, null)); - }); - } - - [Fact] - public void NormalizePathEmpty() - { - Assert.Throws(() => - { - Assert.Null(FileUtilities.NormalizePath(String.Empty)); - }); - } - - [WindowsFullFrameworkOnlyFact(additionalMessage: ".NET Core 2.1+ no longer validates paths: https://github.com/dotnet/corefx/issues/27779#issuecomment-371253486.")] - public void NormalizePathBadUNC1() - { - Assert.Throws(() => - { - Assert.Null(FileUtilities.NormalizePath(@"\\")); - }); - } - - [WindowsFullFrameworkOnlyFact(additionalMessage: ".NET Core 2.1+ no longer validates paths: https://github.com/dotnet/corefx/issues/27779#issuecomment-371253486.")] - public void NormalizePathBadUNC2() - { - Assert.Throws(() => - { - Assert.Null(FileUtilities.NormalizePath(@"\\XXX\")); - }); - } - - [WindowsFullFrameworkOnlyFact(additionalMessage: ".NET Core 2.1+ no longer validates paths: https://github.com/dotnet/corefx/issues/27779#issuecomment-371253486.")] - public void NormalizePathBadUNC3() - { - Assert.Throws(() => - { - Assert.Equal(@"\\localhost", FileUtilities.NormalizePath(@"\\localhost")); - }); - } - - [WindowsOnlyFact] - public void NormalizePathGoodUNC() - { - Assert.Equal(@"\\localhost\share", FileUtilities.NormalizePath(@"\\localhost\share")); - } - - [WindowsOnlyFact] - public void NormalizePathTooLongWithDots() - { - string longPart = new string('x', 300); - Assert.Equal(@"c:\abc\def", FileUtilities.NormalizePath(@"c:\abc\" + longPart + @"\..\def")); - } - - [WindowsFullFrameworkOnlyFact(additionalMessage: ".NET Core 2.1+ no longer validates paths: https://github.com/dotnet/corefx/issues/27779#issuecomment-371253486.")] - public void NormalizePathInvalid() - { - string filePath = @"c:\aardvark\|||"; - - Assert.Throws(() => - { - FileUtilities.NormalizePath(filePath); - }); - } - - [WindowsOnlyFact] - public void CannotNormalizePathWithNewLineAndSpace() - { - string filePath = "\r\n C:\\work\\sdk3\\artifacts\\tmp\\Debug\\SimpleNamesWi---6143883E\\NETFrameworkLibrary\\bin\\Debug\\net462\\NETFrameworkLibrary.dll\r\n "; - -#if FEATURE_LEGACY_GETFULLPATH - Assert.Throws(() => FileUtilities.NormalizePath(filePath)); -#else - Assert.NotEqual("C:\\work\\sdk3\\artifacts\\tmp\\Debug\\SimpleNamesWi---6143883E\\NETFrameworkLibrary\\bin\\Debug\\net462\\NETFrameworkLibrary.dll", FileUtilities.NormalizePath(filePath)); -#endif - } - - [Fact] - public void FileOrDirectoryExistsNoThrow() - { - var isWindows = NativeMethodsShared.IsWindows; - - Assert.False(FileUtilities.FileOrDirectoryExistsNoThrow("||")); - Assert.False(FileUtilities.FileOrDirectoryExistsNoThrow(isWindows ? @"c:\doesnot_exist" : "/doesnot_exist")); - Assert.True(FileUtilities.FileOrDirectoryExistsNoThrow(isWindows ? @"c:\" : "/")); - Assert.True(FileUtilities.FileOrDirectoryExistsNoThrow(Path.GetTempPath())); - - string path = null; - - try - { - path = FileUtilities.GetTemporaryFile(); - Assert.True(FileUtilities.FileOrDirectoryExistsNoThrow(path)); - } - finally - { - File.Delete(path); - } - } - -#if FEATURE_ENVIRONMENT_SYSTEMDIRECTORY - // These tests will need to be redesigned for Linux - - [ConditionalFact(nameof(RunTestsThatDependOnWindowsShortPathBehavior_Workaround4241))] - public void FileOrDirectoryExistsNoThrowTooLongWithDots() - { - int length = (Environment.SystemDirectory + @"\" + @"\..\..\..\" + Environment.SystemDirectory.Substring(3)).Length; - - string longPart = new string('x', 260 - length); // We want the shortest that is > max path. - - string inputPath = Environment.SystemDirectory + @"\" + longPart + @"\..\..\..\" + Environment.SystemDirectory.Substring(3); - Console.WriteLine(inputPath.Length); - - // "c:\windows\system32\\..\..\windows\system32" exists - Assert.True(FileUtilities.FileOrDirectoryExistsNoThrow(inputPath)); - Assert.False(FileUtilities.FileOrDirectoryExistsNoThrow(inputPath.Replace('\\', 'X'))); - } - - [ConditionalFact(nameof(RunTestsThatDependOnWindowsShortPathBehavior_Workaround4241))] - public void FileOrDirectoryExistsNoThrowTooLongWithDotsRelative() - { - int length = (Environment.SystemDirectory + @"\" + @"\..\..\..\" + Environment.SystemDirectory.Substring(3)).Length; - - string longPart = new string('x', 260 - length); // We want the shortest that is > max path. - - string inputPath = longPart + @"\..\..\..\" + Environment.SystemDirectory.Substring(3); - Console.WriteLine(inputPath.Length); - - // "c:\windows\system32\\..\..\windows\system32" exists - - string currentDirectory = Directory.GetCurrentDirectory(); - - try - { - Directory.SetCurrentDirectory(Environment.SystemDirectory); - - Assert.True(FileUtilities.FileOrDirectoryExistsNoThrow(inputPath)); - Assert.False(FileUtilities.FileOrDirectoryExistsNoThrow(inputPath.Replace('\\', 'X'))); - } - finally - { - Directory.SetCurrentDirectory(currentDirectory); - } - } - - [Fact] - public void DirectoryExistsNoThrowTooLongWithDots() - { - string path = Path.Combine(Environment.SystemDirectory, "..", "..", "..") + Path.DirectorySeparatorChar; - if (NativeMethodsShared.IsWindows) - { - path += Environment.SystemDirectory.Substring(3); - } - - int length = path.Length; - - string longPart = new string('x', 260 - length); // We want the shortest that is > max path. - - string inputPath = Path.Combine(new[] { Environment.SystemDirectory, longPart, "..", "..", ".." }) - + Path.DirectorySeparatorChar; - if (NativeMethodsShared.IsWindows) - { - path += Environment.SystemDirectory.Substring(3); - } - - Console.WriteLine(inputPath.Length); - - // "c:\windows\system32\\..\..\windows\system32" exists - Assert.True(FileUtilities.DirectoryExistsNoThrow(inputPath)); - } - - [ConditionalFact(nameof(RunTestsThatDependOnWindowsShortPathBehavior_Workaround4241))] - public void DirectoryExistsNoThrowTooLongWithDotsRelative() - { - int length = (Environment.SystemDirectory + @"\" + @"\..\..\..\" + Environment.SystemDirectory.Substring(3)).Length; - - string longPart = new string('x', 260 - length); // We want the shortest that is > max path. - - string inputPath = longPart + @"\..\..\..\" + Environment.SystemDirectory.Substring(3); - Console.WriteLine(inputPath.Length); - - // "c:\windows\system32\\..\..\..\windows\system32" exists - - string currentDirectory = Directory.GetCurrentDirectory(); - - try - { - Directory.SetCurrentDirectory(Environment.SystemDirectory); - - FileUtilities.DirectoryExistsNoThrow(inputPath).ShouldBeTrue(); - FileUtilities.DirectoryExistsNoThrow(inputPath.Replace('\\', 'X')).ShouldBeFalse(); - } - finally - { - Directory.SetCurrentDirectory(currentDirectory); - } - } - - public static bool RunTestsThatDependOnWindowsShortPathBehavior_Workaround4241() - { - // Run these tests only when we're not on Windows - return !NativeMethodsShared.IsWindows || - // OR we're on Windows and long paths aren't enabled - // https://github.com/dotnet/msbuild/issues/4241 - NativeMethodsShared.IsMaxPathLegacyWindows(); - } - - [ConditionalFact(nameof(RunTestsThatDependOnWindowsShortPathBehavior_Workaround4241))] - public void FileExistsNoThrowTooLongWithDots() - { - int length = (Environment.SystemDirectory + @"\" + @"\..\..\..\" + Environment.SystemDirectory.Substring(3) + @"\..\explorer.exe").Length; - - string longPart = new string('x', 260 - length); // We want the shortest that is > max path. - - string inputPath = Environment.SystemDirectory + @"\" + longPart + @"\..\..\..\" + Environment.SystemDirectory.Substring(3) + @"\..\explorer.exe"; - Console.WriteLine(inputPath.Length); - Console.WriteLine(inputPath); - - // "c:\windows\system32\\..\..\windows\system32" exists - Assert.True(FileUtilities.FileExistsNoThrow(inputPath)); - } - - [ConditionalFact(nameof(RunTestsThatDependOnWindowsShortPathBehavior_Workaround4241))] - public void FileExistsNoThrowTooLongWithDotsRelative() - { - int length = (Environment.SystemDirectory + @"\" + @"\..\..\..\" + Environment.SystemDirectory.Substring(3) + @"\..\explorer.exe").Length; - - string longPart = new string('x', 260 - length); // We want the shortest that is > max path. - - string inputPath = longPart + @"\..\..\..\" + Environment.SystemDirectory.Substring(3) + @"\..\explorer.exe"; - Console.WriteLine(inputPath.Length); - - // "c:\windows\system32\\..\..\windows\system32" exists - - string currentDirectory = Directory.GetCurrentDirectory(); - - try - { - Directory.SetCurrentDirectory(Environment.SystemDirectory); - - Assert.True(FileUtilities.FileExistsNoThrow(inputPath)); - Assert.False(FileUtilities.FileExistsNoThrow(inputPath.Replace('\\', 'X'))); - } - finally - { - Directory.SetCurrentDirectory(currentDirectory); - } - } - - [Fact] - public void GetFileInfoNoThrowTooLongWithDots() - { - int length = (Environment.SystemDirectory + @"\" + @"\..\..\..\" + Environment.SystemDirectory.Substring(3) + @"\..\explorer.exe").Length; - - string longPart = new string('x', 260 - length); // We want the shortest that is > max path. - - string inputPath = Environment.SystemDirectory + @"\" + longPart + @"\..\..\..\" + Environment.SystemDirectory.Substring(3) + @"\..\explorer.exe"; - Console.WriteLine(inputPath.Length); - - // "c:\windows\system32\\..\..\windows\system32" exists - Assert.True(FileUtilities.GetFileInfoNoThrow(inputPath) != null); - Assert.False(FileUtilities.GetFileInfoNoThrow(inputPath.Replace('\\', 'X')) != null); - } - - [Fact] - public void GetFileInfoNoThrowTooLongWithDotsRelative() - { - int length = (Environment.SystemDirectory + @"\" + @"\..\..\..\" + Environment.SystemDirectory.Substring(3) + @"\..\explorer.exe").Length; - - string longPart = new string('x', 260 - length); // We want the shortest that is > max path. - - string inputPath = longPart + @"\..\..\..\" + Environment.SystemDirectory.Substring(3) + @"\..\explorer.exe"; - Console.WriteLine(inputPath.Length); - - // "c:\windows\system32\\..\..\windows\system32" exists - - string currentDirectory = Directory.GetCurrentDirectory(); - - try - { - Directory.SetCurrentDirectory(Environment.SystemDirectory); - - Assert.True(FileUtilities.GetFileInfoNoThrow(inputPath) != null); - Assert.False(FileUtilities.GetFileInfoNoThrow(inputPath.Replace('\\', 'X')) != null); - } - finally - { - Directory.SetCurrentDirectory(currentDirectory); - } - } -#endif - - /// - /// Simple test, neither the base file nor retry files exist - /// - [Fact] - public void GenerateTempFileNameSimple() - { - string path = null; - - try - { - path = FileUtilities.GetTemporaryFile(); - - Assert.EndsWith(".tmp", path); - Assert.True(File.Exists(path)); - Assert.StartsWith(Path.GetTempPath(), path); - } - finally - { - File.Delete(path); - } - } - - /// - /// Choose an extension - /// - [Fact] - public void GenerateTempFileNameWithExtension() - { - string path = null; - - try - { - path = FileUtilities.GetTemporaryFile(".bat"); - - Assert.EndsWith(".bat", path); - Assert.True(File.Exists(path)); - Assert.StartsWith(Path.GetTempPath(), path); - } - finally - { - File.Delete(path); - } - } - - /// - /// Choose a (missing) directory and extension - /// - [Fact] - public void GenerateTempFileNameWithDirectoryAndExtension() - { - string path = null; - string directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "subfolder"); - - try - { - path = FileUtilities.GetTemporaryFile(directory, null, ".bat"); - - Assert.EndsWith(".bat", path); - Assert.True(File.Exists(path)); - Assert.StartsWith(directory, path); - } - finally - { - File.Delete(path); - FileUtilities.DeleteWithoutTrailingBackslash(directory); - } - } - - /// - /// Extension without a period - /// - [Fact] - public void GenerateTempFileNameWithExtensionNoPeriod() - { - string path = null; - - try - { - path = FileUtilities.GetTemporaryFile("bat"); - - Assert.EndsWith(".bat", path); - Assert.True(File.Exists(path)); - Assert.StartsWith(Path.GetTempPath(), path); - } - finally - { - File.Delete(path); - } - } - - /// - /// Extension is invalid - /// - [Fact] - [Trait("Category", "netcore-osx-failing")] - [Trait("Category", "netcore-linux-failing")] - public void GenerateTempBatchFileWithBadExtension() - { - Assert.Throws(() => - { - FileUtilities.GetTemporaryFile("|"); - }); - } - - /// - /// Directory is invalid - /// - [Fact] - [Trait("Category", "netcore-osx-failing")] - [Trait("Category", "netcore-linux-failing")] - public void GenerateTempBatchFileWithBadDirectory() - { - Assert.Throws(() => - { - FileUtilities.GetTemporaryFile("|", null, ".tmp"); - }); - } - - [UnixOnlyFact] - public void AbsolutePathLooksLikeUnixPathOnUnix() - { - var secondSlash = SystemSpecificAbsolutePath.Substring(1).IndexOf(Path.DirectorySeparatorChar) + 1; - var rootLevelPath = SystemSpecificAbsolutePath.Substring(0, secondSlash); - - Assert.True(FileUtilities.LooksLikeUnixFilePath(SystemSpecificAbsolutePath)); - Assert.True(FileUtilities.LooksLikeUnixFilePath(rootLevelPath)); - } - - [WindowsOnlyFact] - public void PathDoesNotLookLikeUnixPathOnWindows() - { - Assert.False(FileUtilities.LooksLikeUnixFilePath(SystemSpecificAbsolutePath)); - Assert.False(FileUtilities.LooksLikeUnixFilePath("/path/that/looks/unixy")); - Assert.False(FileUtilities.LooksLikeUnixFilePath("/root")); - } - - [UnixOnlyFact] - public void RelativePathLooksLikeUnixPathOnUnixWithBaseDirectory() - { - string filePath = ObjectModelHelpers.CreateFileInTempProjectDirectory("first/second/file.txt", String.Empty); - string oldCWD = Directory.GetCurrentDirectory(); - - try - { - // /first - string firstDirectory = Path.GetDirectoryName(Path.GetDirectoryName(filePath)); - string tmpDirectory = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(filePath))); - - Directory.SetCurrentDirectory(tmpDirectory); - - // We are in and second is not under that, so this will be false - Assert.False(FileUtilities.LooksLikeUnixFilePath("second/file.txt")); - - // .. but if we have baseDirectory:firstDirectory, then it will be true - Assert.True(FileUtilities.LooksLikeUnixFilePath("second/file.txt", firstDirectory)); - } - finally - { - if (filePath != null) - { - File.Delete(filePath); - } - Directory.SetCurrentDirectory(oldCWD); - } - } - - [UnixOnlyFact] - public void RelativePathMaybeAdjustFilePathWithBaseDirectory() - { - // /first/second/file.txt - string filePath = ObjectModelHelpers.CreateFileInTempProjectDirectory("first/second/file.txt", String.Empty); - string oldCWD = Directory.GetCurrentDirectory(); - - try - { - // /first - string firstDirectory = Path.GetDirectoryName(Path.GetDirectoryName(filePath)); - string tmpDirectory = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(filePath))); - - Directory.SetCurrentDirectory(tmpDirectory); - - // We are in and second is not under that, so this won't convert - Assert.Equal("second\\file.txt", FileUtilities.MaybeAdjustFilePath("second\\file.txt")); - - // .. but if we have baseDirectory:firstDirectory, then it will - Assert.Equal("second/file.txt", FileUtilities.MaybeAdjustFilePath("second\\file.txt", firstDirectory)); - } - finally - { - if (filePath != null) - { - File.Delete(filePath); - } - Directory.SetCurrentDirectory(oldCWD); - } - } - - private static string SystemSpecificAbsolutePath => BuildEnvironmentHelper.ExecutingAssemblyPath; - - - [Fact] - public void GetFolderAboveTest() - { - string root = NativeMethodsShared.IsWindows ? @"c:\" : "/"; - string path = Path.Combine(root, "1", "2", "3", "4", "5"); - - Assert.Equal(Path.Combine(root, "1", "2", "3", "4", "5"), FileUtilities.GetFolderAbove(path, 0)); - Assert.Equal(Path.Combine(root, "1", "2", "3", "4"), FileUtilities.GetFolderAbove(path)); - Assert.Equal(Path.Combine(root, "1", "2", "3"), FileUtilities.GetFolderAbove(path, 2)); - Assert.Equal(Path.Combine(root, "1", "2"), FileUtilities.GetFolderAbove(path, 3)); - Assert.Equal(Path.Combine(root, "1"), FileUtilities.GetFolderAbove(path, 4)); - Assert.Equal(root, FileUtilities.GetFolderAbove(path, 5)); - Assert.Equal(root, FileUtilities.GetFolderAbove(path, 99)); - - Assert.Equal(root, FileUtilities.GetFolderAbove(root, 99)); - } - - [Fact] - public void CombinePathsTest() - { - // These tests run in .NET 4+, so we can cheat - var root = @"c:\"; - - Assert.Equal( - Path.Combine(root, "path1"), - FileUtilities.CombinePaths(root, "path1")); - - Assert.Equal( - Path.Combine(root, "path1", "path2", "file.txt"), - FileUtilities.CombinePaths(root, "path1", "path2", "file.txt")); - } - - [Theory] - [InlineData(@"c:\a\.\b", true)] - [InlineData(@"c:\a\..\b", true)] - [InlineData(@"c:\a\..", true)] - [InlineData(@"c:\a\.", true)] - [InlineData(@".\a", true)] - [InlineData(@"..\b", true)] - [InlineData(@"..", true)] - [InlineData(@".", true)] - [InlineData(@"..\", true)] - [InlineData(@".\", true)] - [InlineData(@"\..", true)] - [InlineData(@"\.", true)] - [InlineData(@"..\..\a", true)] - [InlineData(@"..\..\..\a", true)] - [InlineData(@"b..\", false)] - [InlineData(@"b.\", false)] - [InlineData(@"\b..", false)] - [InlineData(@"\b.", false)] - [InlineData(@"\b..\", false)] - [InlineData(@"\b.\", false)] - [InlineData(@"...", false)] - [InlineData(@"....", false)] - public void ContainsRelativeSegmentsTest(string path, bool expectedResult) - { - FileUtilities.ContainsRelativePathSegments(path).ShouldBe(expectedResult); - } - - [Theory] - [InlineData("a/b/c/d", 0, "")] - [InlineData("a/b/c/d", 1, "d")] - [InlineData("a/b/c/d", 2, "c/d")] - [InlineData("a/b/c/d", 3, "b/c/d")] - [InlineData("a/b/c/d", 4, "a/b/c/d")] - [InlineData("a/b/c/d", 5, "a/b/c/d")] - [InlineData(@"a\/\/\//b/\/\/\//c//\/\/\/d/\//\/\/", 2, "c/d")] - public static void TestTruncatePathToTrailingSegments(string path, int trailingSegments, string expectedTruncatedPath) - { - expectedTruncatedPath = expectedTruncatedPath.Replace('/', Path.DirectorySeparatorChar); - - FileUtilities.TruncatePathToTrailingSegments(path, trailingSegments).ShouldBe(expectedTruncatedPath); - } - - /// - /// Exercises FileUtilities.EnsureSingleQuotes - /// - [Theory] - [InlineData(null, null)] // Null test - [InlineData("", "")] // Empty string test - [InlineData(@" ", @"' '")] // One character which is a space - [InlineData(@"'", @"'''")] // One character which is a single quote - [InlineData(@"""", @"'""'")] // One character which is a double quote - [InlineData(@"example", @"'example'")] // Unquoted string - [InlineData(@"'example'", @"'example'")] // Single quoted string - [InlineData(@"""example""", @"'example'")] // Double quoted string - [InlineData(@"'example""", @"''example""'")] // Mixed Quotes - Leading Single - [InlineData(@"""example'", @"'""example''")] // Mixed Quotes - Leading Double - [InlineData(@"ex""am'ple", @"'ex""am'ple'")] // Interior Quotes - public void EnsureSingleQuotesTest(string path, string expectedResult) - { - FileUtilities.EnsureSingleQuotes(path).ShouldBe(expectedResult); - } - - /// - /// Exercises FileUtilities.EnsureDoubleQuotes - /// - [Theory] - [InlineData(null, null)] // Null test - [InlineData("", "")] // Empty string test - [InlineData(@" ", @""" """)] // One character which is a space - [InlineData(@"'", @"""'""")] // One character which is a single quote - [InlineData(@"""", @"""""""")] // One character which is a double quote - [InlineData(@"example", @"""example""")] // Unquoted string - [InlineData(@"'example'", @"""example""")] // Single quoted string - [InlineData(@"""example""", @"""example""")] // Double quoted string - [InlineData(@"'example""", @"""'example""""")] // Mixed Quotes - Leading Single - [InlineData(@"""example'", @"""""example'""")] // Mixed Quotes - Leading Double - [InlineData(@"ex""am'ple", @"""ex""am'ple""")] // Interior Quotes - public void EnsureDoubleQuotesTest(string path, string expectedResult) - { - FileUtilities.EnsureDoubleQuotes(path).ShouldBe(expectedResult); - } - } -} From 79fb6dfc44c59b5125914f448fab0b3f0dc88921 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 10 Mar 2026 13:54:53 -0700 Subject: [PATCH 21/27] Move EscapingUtilities_Tests to Framework.UnitTests --- .../Microsoft.Build.Engine.UnitTests.csproj | 1 - .../EscapingUtilities_Tests.cs | 93 ++++++++++++++++++ .../UnitTests/EscapingUtilities_Tests.cs | 94 ------------------- .../Microsoft.Build.Tasks.UnitTests.csproj | 1 - ...Microsoft.Build.Utilities.UnitTests.csproj | 1 - 5 files changed, 93 insertions(+), 97 deletions(-) create mode 100644 src/Framework.UnitTests/EscapingUtilities_Tests.cs delete mode 100644 src/Shared/UnitTests/EscapingUtilities_Tests.cs diff --git a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj index 43b3921dfed..3ff0799db7e 100644 --- a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj +++ b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj @@ -65,7 +65,6 @@ - diff --git a/src/Framework.UnitTests/EscapingUtilities_Tests.cs b/src/Framework.UnitTests/EscapingUtilities_Tests.cs new file mode 100644 index 00000000000..3a6ddc09c8a --- /dev/null +++ b/src/Framework.UnitTests/EscapingUtilities_Tests.cs @@ -0,0 +1,93 @@ +// 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.Shared; +using Xunit; + +#nullable disable + +namespace Microsoft.Build.Framework.UnitTests; + +public sealed class EscapingUtilities_Tests +{ + /// + /// + [Fact] + public void Unescape() + { + Assert.Equal("", EscapingUtilities.UnescapeAll("")); + Assert.Equal("foo", EscapingUtilities.UnescapeAll("foo")); + Assert.Equal("foo space", EscapingUtilities.UnescapeAll("foo%20space")); + Assert.Equal("foo2;", EscapingUtilities.UnescapeAll("foo2%3B")); + Assert.Equal(";foo3", EscapingUtilities.UnescapeAll("%3bfoo3")); + Assert.Equal(";", EscapingUtilities.UnescapeAll("%3b")); + Assert.Equal(";;;;;", EscapingUtilities.UnescapeAll("%3b%3B;%3b%3B")); + Assert.Equal("%3B", EscapingUtilities.UnescapeAll("%253B")); + Assert.Equal("===%ZZ %%%===", EscapingUtilities.UnescapeAll("===%ZZ%20%%%===")); + Assert.Equal("hello; escaping% how( are) you?", EscapingUtilities.UnescapeAll("hello%3B escaping%25 how%28 are%29 you%3f")); + + Assert.Equal("%*?*%*", EscapingUtilities.UnescapeAll("%25*?*%25*")); + Assert.Equal("%*?*%*", EscapingUtilities.UnescapeAll("%25%2a%3f%2a%25%2a")); + + Assert.Equal("*Star*craft or *War*cr@ft??", EscapingUtilities.UnescapeAll("%2aStar%2Acraft%20or %2aWar%2Acr%40ft%3f%3F")); + } + + /// + /// + [Fact] + public void Escape() + { + Assert.Equal("%2a", EscapingUtilities.Escape("*")); + Assert.Equal("%3f", EscapingUtilities.Escape("?")); + Assert.Equal("#%2a%3f%2a#%2a", EscapingUtilities.Escape("#*?*#*")); + Assert.Equal("%25%2a%3f%2a%25%2a", EscapingUtilities.Escape("%*?*%*")); + } + + /// + /// + [Fact] + public void UnescapeEscape() + { + string text = "*"; + Assert.Equal(text, EscapingUtilities.UnescapeAll(EscapingUtilities.Escape(text))); + + text = "?"; + Assert.Equal(text, EscapingUtilities.UnescapeAll(EscapingUtilities.Escape(text))); + + text = "#*?*#*"; + Assert.Equal(text, EscapingUtilities.UnescapeAll(EscapingUtilities.Escape(text))); + } + + /// + /// + [Fact] + public void EscapeUnescape() + { + string text = "%2a"; + Assert.Equal(text, EscapingUtilities.Escape(EscapingUtilities.UnescapeAll(text))); + + text = "%3f"; + Assert.Equal(text, EscapingUtilities.Escape(EscapingUtilities.UnescapeAll(text))); + + text = "#%2a%3f%2a#%2a"; + Assert.Equal(text, EscapingUtilities.Escape(EscapingUtilities.UnescapeAll(text))); + } + + [Fact] + public void ContainsEscapedWildcards() + { + Assert.False(EscapingUtilities.ContainsEscapedWildcards("NoStarOrQMark")); + Assert.False(EscapingUtilities.ContainsEscapedWildcards("%")); + Assert.False(EscapingUtilities.ContainsEscapedWildcards("%%")); + Assert.False(EscapingUtilities.ContainsEscapedWildcards("%2")); + Assert.False(EscapingUtilities.ContainsEscapedWildcards("%4")); + Assert.False(EscapingUtilities.ContainsEscapedWildcards("%3A")); + Assert.False(EscapingUtilities.ContainsEscapedWildcards("%2B")); + Assert.True(EscapingUtilities.ContainsEscapedWildcards("%2a")); + Assert.True(EscapingUtilities.ContainsEscapedWildcards("%2A")); + Assert.True(EscapingUtilities.ContainsEscapedWildcards("%3F")); + Assert.True(EscapingUtilities.ContainsEscapedWildcards("%3f")); + Assert.True(EscapingUtilities.ContainsEscapedWildcards("%%3f")); + Assert.True(EscapingUtilities.ContainsEscapedWildcards("%3%3f")); + } +} diff --git a/src/Shared/UnitTests/EscapingUtilities_Tests.cs b/src/Shared/UnitTests/EscapingUtilities_Tests.cs deleted file mode 100644 index 9b756d3a3da..00000000000 --- a/src/Shared/UnitTests/EscapingUtilities_Tests.cs +++ /dev/null @@ -1,94 +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.Shared; -using Xunit; - -#nullable disable - -namespace Microsoft.Build.UnitTests -{ - public sealed class EscapingUtilities_Tests - { - /// - /// - [Fact] - public void Unescape() - { - Assert.Equal("", EscapingUtilities.UnescapeAll("")); - Assert.Equal("foo", EscapingUtilities.UnescapeAll("foo")); - Assert.Equal("foo space", EscapingUtilities.UnescapeAll("foo%20space")); - Assert.Equal("foo2;", EscapingUtilities.UnescapeAll("foo2%3B")); - Assert.Equal(";foo3", EscapingUtilities.UnescapeAll("%3bfoo3")); - Assert.Equal(";", EscapingUtilities.UnescapeAll("%3b")); - Assert.Equal(";;;;;", EscapingUtilities.UnescapeAll("%3b%3B;%3b%3B")); - Assert.Equal("%3B", EscapingUtilities.UnescapeAll("%253B")); - Assert.Equal("===%ZZ %%%===", EscapingUtilities.UnescapeAll("===%ZZ%20%%%===")); - Assert.Equal("hello; escaping% how( are) you?", EscapingUtilities.UnescapeAll("hello%3B escaping%25 how%28 are%29 you%3f")); - - Assert.Equal("%*?*%*", EscapingUtilities.UnescapeAll("%25*?*%25*")); - Assert.Equal("%*?*%*", EscapingUtilities.UnescapeAll("%25%2a%3f%2a%25%2a")); - - Assert.Equal("*Star*craft or *War*cr@ft??", EscapingUtilities.UnescapeAll("%2aStar%2Acraft%20or %2aWar%2Acr%40ft%3f%3F")); - } - - /// - /// - [Fact] - public void Escape() - { - Assert.Equal("%2a", EscapingUtilities.Escape("*")); - Assert.Equal("%3f", EscapingUtilities.Escape("?")); - Assert.Equal("#%2a%3f%2a#%2a", EscapingUtilities.Escape("#*?*#*")); - Assert.Equal("%25%2a%3f%2a%25%2a", EscapingUtilities.Escape("%*?*%*")); - } - - /// - /// - [Fact] - public void UnescapeEscape() - { - string text = "*"; - Assert.Equal(text, EscapingUtilities.UnescapeAll(EscapingUtilities.Escape(text))); - - text = "?"; - Assert.Equal(text, EscapingUtilities.UnescapeAll(EscapingUtilities.Escape(text))); - - text = "#*?*#*"; - Assert.Equal(text, EscapingUtilities.UnescapeAll(EscapingUtilities.Escape(text))); - } - - /// - /// - [Fact] - public void EscapeUnescape() - { - string text = "%2a"; - Assert.Equal(text, EscapingUtilities.Escape(EscapingUtilities.UnescapeAll(text))); - - text = "%3f"; - Assert.Equal(text, EscapingUtilities.Escape(EscapingUtilities.UnescapeAll(text))); - - text = "#%2a%3f%2a#%2a"; - Assert.Equal(text, EscapingUtilities.Escape(EscapingUtilities.UnescapeAll(text))); - } - - [Fact] - public void ContainsEscapedWildcards() - { - Assert.False(EscapingUtilities.ContainsEscapedWildcards("NoStarOrQMark")); - Assert.False(EscapingUtilities.ContainsEscapedWildcards("%")); - Assert.False(EscapingUtilities.ContainsEscapedWildcards("%%")); - Assert.False(EscapingUtilities.ContainsEscapedWildcards("%2")); - Assert.False(EscapingUtilities.ContainsEscapedWildcards("%4")); - Assert.False(EscapingUtilities.ContainsEscapedWildcards("%3A")); - Assert.False(EscapingUtilities.ContainsEscapedWildcards("%2B")); - Assert.True(EscapingUtilities.ContainsEscapedWildcards("%2a")); - Assert.True(EscapingUtilities.ContainsEscapedWildcards("%2A")); - Assert.True(EscapingUtilities.ContainsEscapedWildcards("%3F")); - Assert.True(EscapingUtilities.ContainsEscapedWildcards("%3f")); - Assert.True(EscapingUtilities.ContainsEscapedWildcards("%%3f")); - Assert.True(EscapingUtilities.ContainsEscapedWildcards("%3%3f")); - } - } -} diff --git a/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj b/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj index 0ae65d5be14..b5d9c668d35 100644 --- a/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj +++ b/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj @@ -44,7 +44,6 @@ - diff --git a/src/Utilities.UnitTests/Microsoft.Build.Utilities.UnitTests.csproj b/src/Utilities.UnitTests/Microsoft.Build.Utilities.UnitTests.csproj index 971c9183beb..9f5dc1cf08d 100644 --- a/src/Utilities.UnitTests/Microsoft.Build.Utilities.UnitTests.csproj +++ b/src/Utilities.UnitTests/Microsoft.Build.Utilities.UnitTests.csproj @@ -23,7 +23,6 @@ - From b28024441be7d58694f55232d506ac6e8f9d73a6 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 10 Mar 2026 14:01:07 -0700 Subject: [PATCH 22/27] Move MSBuildConstants to Framework This type was compiled from src/Shared/Constants.cs but it wasn't included in any other projects. This change removes it completely from src/Shared and includes it directly in src/Framework. --- src/{Shared/Constants.cs => Framework/MSBuildConstants.cs} | 1 - src/Framework/Microsoft.Build.Framework.csproj | 3 --- 2 files changed, 4 deletions(-) rename src/{Shared/Constants.cs => Framework/MSBuildConstants.cs} (99%) diff --git a/src/Shared/Constants.cs b/src/Framework/MSBuildConstants.cs similarity index 99% rename from src/Shared/Constants.cs rename to src/Framework/MSBuildConstants.cs index 28a6bfe8183..4e8e8fd29d8 100644 --- a/src/Shared/Constants.cs +++ b/src/Framework/MSBuildConstants.cs @@ -7,7 +7,6 @@ #endif using System.IO; - namespace Microsoft.Build.Shared { /// diff --git a/src/Framework/Microsoft.Build.Framework.csproj b/src/Framework/Microsoft.Build.Framework.csproj index 08f2c7c6968..075be6183ce 100644 --- a/src/Framework/Microsoft.Build.Framework.csproj +++ b/src/Framework/Microsoft.Build.Framework.csproj @@ -50,9 +50,6 @@ - - Shared\Constants.cs - Shared\BinaryReaderExtensions.cs From 9cce71a62ff61b274849ebf19de82797beafeb9b Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 10 Mar 2026 14:07:32 -0700 Subject: [PATCH 23/27] Move EnvironmentUtilities to Framework This type was compiled from src/Shared/EnvironmentUtilities.cs but it wasn't included in any other projects. This change removes it completely from src/Shared and includes it directly in src/Framework. In addition, it has been moved to the Microsoft.Build.Framework namespace. --- src/Framework/EnvironmentUtilities.cs | 95 ++++++++++++++++++ .../Microsoft.Build.Framework.csproj | 1 - .../Profiler/EvaluationIdProvider.cs | 1 - src/MSBuild/PerformanceLogEventListener.cs | 2 +- src/Shared/EnvironmentUtilities.cs | 98 ------------------- src/Shared/NamedPipeUtil.cs | 1 + .../RoslynCodeTaskFactoryCompilers.cs | 1 - 7 files changed, 97 insertions(+), 102 deletions(-) create mode 100644 src/Framework/EnvironmentUtilities.cs delete mode 100644 src/Shared/EnvironmentUtilities.cs diff --git a/src/Framework/EnvironmentUtilities.cs b/src/Framework/EnvironmentUtilities.cs new file mode 100644 index 00000000000..9517f781cdf --- /dev/null +++ b/src/Framework/EnvironmentUtilities.cs @@ -0,0 +1,95 @@ +// 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.Threading; + +namespace Microsoft.Build.Framework; + +internal static partial class EnvironmentUtilities +{ +#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.ProcessPath + 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/Framework/Microsoft.Build.Framework.csproj b/src/Framework/Microsoft.Build.Framework.csproj index 075be6183ce..912cf92237b 100644 --- a/src/Framework/Microsoft.Build.Framework.csproj +++ b/src/Framework/Microsoft.Build.Framework.csproj @@ -57,7 +57,6 @@ Shared\BinaryWriterExtensions.cs - Shared\IMSBuildElementLocation.cs diff --git a/src/Framework/Profiler/EvaluationIdProvider.cs b/src/Framework/Profiler/EvaluationIdProvider.cs index 887db13af9a..bc0c1a41636 100644 --- a/src/Framework/Profiler/EvaluationIdProvider.cs +++ b/src/Framework/Profiler/EvaluationIdProvider.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Threading; -using Microsoft.Build.Shared; namespace Microsoft.Build.Framework.Profiler { diff --git a/src/MSBuild/PerformanceLogEventListener.cs b/src/MSBuild/PerformanceLogEventListener.cs index a42f05194bc..bda499f6112 100644 --- a/src/MSBuild/PerformanceLogEventListener.cs +++ b/src/MSBuild/PerformanceLogEventListener.cs @@ -6,7 +6,7 @@ using System.IO; using System.Text; using Microsoft.Build.Eventing; -using Microsoft.Build.Shared; +using Microsoft.Build.Framework; #nullable disable diff --git a/src/Shared/EnvironmentUtilities.cs b/src/Shared/EnvironmentUtilities.cs deleted file mode 100644 index a66cbb26251..00000000000 --- a/src/Shared/EnvironmentUtilities.cs +++ /dev/null @@ -1,98 +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 enable - -using System; -using System.Diagnostics; -using System.Threading; - -namespace Microsoft.Build.Shared -{ - internal static partial class EnvironmentUtilities - { -#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.ProcessPath - 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/Shared/NamedPipeUtil.cs b/src/Shared/NamedPipeUtil.cs index d5ab0e11b80..b62192b4449 100644 --- a/src/Shared/NamedPipeUtil.cs +++ b/src/Shared/NamedPipeUtil.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.IO; +using Microsoft.Build.Framework; using Microsoft.Build.Internal; namespace Microsoft.Build.Shared diff --git a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactoryCompilers.cs b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactoryCompilers.cs index 64f18fab383..84de01be4a8 100644 --- a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactoryCompilers.cs +++ b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactoryCompilers.cs @@ -9,7 +9,6 @@ using Microsoft.Build.Utilities; #if RUNTIME_TYPE_NETCORE using System.Runtime.InteropServices; -using Microsoft.Build.Shared; using Constants = Microsoft.Build.Framework.Constants; #endif From 3220e1b6927056b53081de420bb4f0f3d3ea6b63 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 10 Mar 2026 14:29:57 -0700 Subject: [PATCH 24/27] Move BinaryReader/WriterExtensions to Framework These types were compiled from src/Shared/BinaryReaderExtensions.cs and src/Shared/BinaryWriterExtensions.cs but they weren't included in any other projects. This change removes them completely from src/Shared and includes them directly in src/Framework. In addition, they have been moved to the Microsoft.Build.Framework namespace. --- .../BuildEventArgsSerialization_Tests.cs | 21 ++- src/Framework/AssemblyLoadBuildEventArgs.cs | 1 - src/Framework/BinaryReaderExtensions.cs | 130 +++++++++++++++++ src/Framework/BinaryWriterExtensions.cs | 129 +++++++++++++++++ .../BuildCheck/BuildCheckEventArgs.cs | 1 - src/Framework/BuildErrorEventArgs.cs | 1 - src/Framework/BuildEventArgs.cs | 1 - .../BuildException/BuildExceptionBase.cs | 1 - src/Framework/BuildMessageEventArgs.cs | 1 - .../BuildSubmissionStartedEventArgs.cs | 1 - src/Framework/BuildWarningEventArgs.cs | 1 - .../EnvironmentVariableReadEventArgs.cs | 1 - src/Framework/ExtendedBuildErrorEventArgs.cs | 1 - .../ExtendedBuildMessageEventArgs.cs | 1 - .../ExtendedBuildWarningEventArgs.cs | 1 - .../ExtendedCriticalBuildMessageEventArgs.cs | 1 - src/Framework/ExtendedCustomBuildEventArgs.cs | 1 - .../ExternalProjectFinishedEventArgs.cs | 1 - .../ExternalProjectStartedEventArgs.cs | 1 - .../MetaProjectGeneratedEventArgs.cs | 1 - .../Microsoft.Build.Framework.csproj | 6 - src/Framework/ProjectFinishedEventArgs.cs | 1 - src/Framework/ProjectImportedEventArgs.cs | 1 - src/Framework/ProjectStartedEventArgs.cs | 1 - .../PropertyInitialValueSetEventArgs.cs | 1 - .../PropertyReassignmentEventArgs.cs | 1 - src/Framework/TargetFinishedEventArgs.cs | 1 - src/Framework/TargetSkippedEventArgs.cs | 1 - src/Framework/TargetStartedEventArgs.cs | 1 - src/Framework/TaskFinishedEventArgs.cs | 1 - src/Framework/TaskParameterEventArgs.cs | 1 - src/Framework/TaskStartedEventArgs.cs | 1 - .../Telemetry/WorkerNodeTelemetryEventArgs.cs | 3 - src/Framework/TelemetryEventArgs.cs | 1 - .../UninitializedPropertyReadEventArgs.cs | 1 - src/Shared/BinaryReaderExtensions.cs | 132 ------------------ src/Shared/BinaryWriterExtensions.cs | 131 ----------------- 37 files changed, 269 insertions(+), 313 deletions(-) create mode 100644 src/Framework/BinaryReaderExtensions.cs create mode 100644 src/Framework/BinaryWriterExtensions.cs delete mode 100644 src/Shared/BinaryReaderExtensions.cs delete mode 100644 src/Shared/BinaryWriterExtensions.cs diff --git a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs index 6d0da53ee93..12d33ccc514 100644 --- a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs +++ b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs @@ -1038,17 +1038,16 @@ public void ForwardCompatibleRead_HandleAppendOnlyChanges() // Some future data that are not known in current version binaryWriter.Write(new byte[] { 1, 2, 3, 4 }); - int positionAfterFirstEvent = (int)memoryStream.Position; memoryStream.Position = 0; // event type - Microsoft.Build.Shared.BinaryReaderExtensions.Read7BitEncodedInt(binaryReader); + binaryReader.Read7BitEncodedInt(); int eventSizePos = (int)memoryStream.Position; - int eventSize = Microsoft.Build.Shared.BinaryReaderExtensions.Read7BitEncodedInt(binaryReader); + int eventSize = binaryReader.Read7BitEncodedInt(); int positionAfterFirstEventSize = (int)memoryStream.Position; memoryStream.Position = eventSizePos; // the extra 4 bytes - Microsoft.Build.Shared.BinaryWriterExtensions.Write7BitEncodedInt(binaryWriter, eventSize + 4); + binaryWriter.Write7BitEncodedInt(eventSize + 4); memoryStream.Position.ShouldBe(positionAfterFirstEventSize, "The event size need to be overwritten in place - without overwriting any bytes after the size info"); memoryStream.Position = positionAfterFirstEvent; @@ -1102,13 +1101,13 @@ public void ForwardCompatibleRead_HandleUnknownEvent() int positionAfterFirstEvent = (int)memoryStream.Position; memoryStream.Position = 0; // event type - Microsoft.Build.Shared.BinaryReaderExtensions.Read7BitEncodedInt(binaryReader); + binaryReader.Read7BitEncodedInt(); int eventSizePos = (int)memoryStream.Position; memoryStream.Position = 0; // some future type that is not known in current version BinaryLogRecordKind unknownType = (BinaryLogRecordKind)Enum.GetValues(typeof(BinaryLogRecordKind)).Cast().Select(e => (int)e).Max() + 2; - Microsoft.Build.Shared.BinaryWriterExtensions.Write7BitEncodedInt(binaryWriter, (int)unknownType); + binaryWriter.Write7BitEncodedInt((int)unknownType); memoryStream.Position.ShouldBe(eventSizePos, "The event type need to be overwritten in place - without overwriting any bytes after the type info"); memoryStream.Position = positionAfterFirstEvent; @@ -1156,8 +1155,8 @@ public void ForwardCompatibleRead_HandleMismatchedFormatOfEvent() int positionAfterFirstEvent = (int)memoryStream.Position; memoryStream.Position = 0; // event type - Microsoft.Build.Shared.BinaryReaderExtensions.Read7BitEncodedInt(binaryReader); - int eventSize = Microsoft.Build.Shared.BinaryReaderExtensions.Read7BitEncodedInt(binaryReader); + binaryReader.Read7BitEncodedInt(); + int eventSize = binaryReader.Read7BitEncodedInt(); // overwrite the entire event with garbage binaryWriter.Write(Enumerable.Repeat(byte.MaxValue, eventSize).ToArray()); @@ -1208,13 +1207,13 @@ public void ForwardCompatibleRead_HandleRemovalOfDataFromEventDefinition() int positionAfterFirstEvent = (int)memoryStream.Position; memoryStream.Position = 0; // event type - Microsoft.Build.Shared.BinaryReaderExtensions.Read7BitEncodedInt(binaryReader); + binaryReader.Read7BitEncodedInt(); int eventSizePos = (int)memoryStream.Position; - int eventSize = Microsoft.Build.Shared.BinaryReaderExtensions.Read7BitEncodedInt(binaryReader); + int eventSize = binaryReader.Read7BitEncodedInt(); int positionAfterFirstEventSize = (int)memoryStream.Position; memoryStream.Position = eventSizePos; // simulate there are 4 bytes less in the future version of the event - while our reader expects those - Microsoft.Build.Shared.BinaryWriterExtensions.Write7BitEncodedInt(binaryWriter, eventSize - 4); + binaryWriter.Write7BitEncodedInt(eventSize - 4); memoryStream.Position.ShouldBe(positionAfterFirstEventSize, "The event size need to be overwritten in place - without overwriting any bytes after the size info"); // remove the 4 bytes - so that actual size of event is inline with it's written size. memoryStream.Position = positionAfterFirstEvent - 4; diff --git a/src/Framework/AssemblyLoadBuildEventArgs.cs b/src/Framework/AssemblyLoadBuildEventArgs.cs index 767772f5fc8..7ce71b7d960 100644 --- a/src/Framework/AssemblyLoadBuildEventArgs.cs +++ b/src/Framework/AssemblyLoadBuildEventArgs.cs @@ -5,7 +5,6 @@ using System; using System.IO; -using Microsoft.Build.Shared; namespace Microsoft.Build.Framework { diff --git a/src/Framework/BinaryReaderExtensions.cs b/src/Framework/BinaryReaderExtensions.cs new file mode 100644 index 00000000000..8cc81b03988 --- /dev/null +++ b/src/Framework/BinaryReaderExtensions.cs @@ -0,0 +1,130 @@ +// 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; + +namespace Microsoft.Build.Framework; + +internal static class BinaryReaderExtensions +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string? ReadOptionalString(this BinaryReader reader) + { + return reader.ReadByte() == 0 ? null : reader.ReadString(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int? ReadOptionalInt32(this BinaryReader reader) + { + return reader.ReadByte() == 0 ? null : reader.ReadInt32(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static DateTime ReadTimestamp(this BinaryReader reader) + { + long timestampTicks = reader.ReadInt64(); + DateTimeKind kind = (DateTimeKind)reader.ReadInt32(); + var timestamp = new DateTime(timestampTicks, kind); + return timestamp; + } + + [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; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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/Framework/BinaryWriterExtensions.cs b/src/Framework/BinaryWriterExtensions.cs new file mode 100644 index 00000000000..fe7ac5f7b59 --- /dev/null +++ b/src/Framework/BinaryWriterExtensions.cs @@ -0,0 +1,129 @@ +// 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; + +namespace Microsoft.Build.Framework; + +internal static class BinaryWriterExtensions +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteOptionalString(this BinaryWriter writer, string? value) + { + if (value == null) + { + writer.Write((byte)0); + } + else + { + writer.Write((byte)1); + writer.Write(value); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteTimestamp(this BinaryWriter writer, DateTime timestamp) + { + writer.Write(timestamp.Ticks); + writer.Write((Int32)timestamp.Kind); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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); + } + + [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); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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/Framework/BuildCheck/BuildCheckEventArgs.cs b/src/Framework/BuildCheck/BuildCheckEventArgs.cs index d1d3c682a3f..e8491975a71 100644 --- a/src/Framework/BuildCheck/BuildCheckEventArgs.cs +++ b/src/Framework/BuildCheck/BuildCheckEventArgs.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; namespace Microsoft.Build.Experimental.BuildCheck; diff --git a/src/Framework/BuildErrorEventArgs.cs b/src/Framework/BuildErrorEventArgs.cs index 97cb5b1f1df..6f119b373fb 100644 --- a/src/Framework/BuildErrorEventArgs.cs +++ b/src/Framework/BuildErrorEventArgs.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Framework/BuildEventArgs.cs b/src/Framework/BuildEventArgs.cs index a26e7b26948..3ea2c615208 100644 --- a/src/Framework/BuildEventArgs.cs +++ b/src/Framework/BuildEventArgs.cs @@ -5,7 +5,6 @@ using System.IO; using System.Runtime.Serialization; using System.Text; -using Microsoft.Build.Shared; namespace Microsoft.Build.Framework { diff --git a/src/Framework/BuildException/BuildExceptionBase.cs b/src/Framework/BuildException/BuildExceptionBase.cs index 869b74070b6..d09c0a6fd14 100644 --- a/src/Framework/BuildException/BuildExceptionBase.cs +++ b/src/Framework/BuildException/BuildExceptionBase.cs @@ -7,7 +7,6 @@ using System.IO; using System.Runtime.Serialization; using Microsoft.Build.BackEnd; -using Microsoft.Build.Shared; namespace Microsoft.Build.Framework.BuildException; diff --git a/src/Framework/BuildMessageEventArgs.cs b/src/Framework/BuildMessageEventArgs.cs index 20a1898bea2..785f7b923f8 100644 --- a/src/Framework/BuildMessageEventArgs.cs +++ b/src/Framework/BuildMessageEventArgs.cs @@ -5,7 +5,6 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.Serialization; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Framework/BuildSubmissionStartedEventArgs.cs b/src/Framework/BuildSubmissionStartedEventArgs.cs index 6a1712abf83..b750087b10d 100644 --- a/src/Framework/BuildSubmissionStartedEventArgs.cs +++ b/src/Framework/BuildSubmissionStartedEventArgs.cs @@ -6,7 +6,6 @@ using System.IO; using System.Linq; using Microsoft.Build.Execution; -using Microsoft.Build.Shared; namespace Microsoft.Build.Framework { diff --git a/src/Framework/BuildWarningEventArgs.cs b/src/Framework/BuildWarningEventArgs.cs index 543281e8c26..3d257346d46 100644 --- a/src/Framework/BuildWarningEventArgs.cs +++ b/src/Framework/BuildWarningEventArgs.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Framework/EnvironmentVariableReadEventArgs.cs b/src/Framework/EnvironmentVariableReadEventArgs.cs index 496fe31afe9..de755828413 100644 --- a/src/Framework/EnvironmentVariableReadEventArgs.cs +++ b/src/Framework/EnvironmentVariableReadEventArgs.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.IO; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Framework/ExtendedBuildErrorEventArgs.cs b/src/Framework/ExtendedBuildErrorEventArgs.cs index 54f558432b1..e28be6ebde3 100644 --- a/src/Framework/ExtendedBuildErrorEventArgs.cs +++ b/src/Framework/ExtendedBuildErrorEventArgs.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; -using Microsoft.Build.Shared; namespace Microsoft.Build.Framework; diff --git a/src/Framework/ExtendedBuildMessageEventArgs.cs b/src/Framework/ExtendedBuildMessageEventArgs.cs index 53ec510c8da..bd2dbc8749b 100644 --- a/src/Framework/ExtendedBuildMessageEventArgs.cs +++ b/src/Framework/ExtendedBuildMessageEventArgs.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using Microsoft.Build.Shared; namespace Microsoft.Build.Framework; diff --git a/src/Framework/ExtendedBuildWarningEventArgs.cs b/src/Framework/ExtendedBuildWarningEventArgs.cs index 2d9a163eb15..dcd018b65a3 100644 --- a/src/Framework/ExtendedBuildWarningEventArgs.cs +++ b/src/Framework/ExtendedBuildWarningEventArgs.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; -using Microsoft.Build.Shared; namespace Microsoft.Build.Framework; diff --git a/src/Framework/ExtendedCriticalBuildMessageEventArgs.cs b/src/Framework/ExtendedCriticalBuildMessageEventArgs.cs index 3897a1de6e6..c59dd4ea422 100644 --- a/src/Framework/ExtendedCriticalBuildMessageEventArgs.cs +++ b/src/Framework/ExtendedCriticalBuildMessageEventArgs.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using Microsoft.Build.Shared; namespace Microsoft.Build.Framework; diff --git a/src/Framework/ExtendedCustomBuildEventArgs.cs b/src/Framework/ExtendedCustomBuildEventArgs.cs index c29352897a1..a83de3498ba 100644 --- a/src/Framework/ExtendedCustomBuildEventArgs.cs +++ b/src/Framework/ExtendedCustomBuildEventArgs.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using Microsoft.Build.Shared; namespace Microsoft.Build.Framework; diff --git a/src/Framework/ExternalProjectFinishedEventArgs.cs b/src/Framework/ExternalProjectFinishedEventArgs.cs index 4417569e8fc..51f661fe499 100644 --- a/src/Framework/ExternalProjectFinishedEventArgs.cs +++ b/src/Framework/ExternalProjectFinishedEventArgs.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Framework/ExternalProjectStartedEventArgs.cs b/src/Framework/ExternalProjectStartedEventArgs.cs index 0d25191f08e..09a4d4c1ce9 100644 --- a/src/Framework/ExternalProjectStartedEventArgs.cs +++ b/src/Framework/ExternalProjectStartedEventArgs.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Framework/MetaProjectGeneratedEventArgs.cs b/src/Framework/MetaProjectGeneratedEventArgs.cs index 79e0a68ad09..f7714d7c484 100644 --- a/src/Framework/MetaProjectGeneratedEventArgs.cs +++ b/src/Framework/MetaProjectGeneratedEventArgs.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Framework/Microsoft.Build.Framework.csproj b/src/Framework/Microsoft.Build.Framework.csproj index 912cf92237b..1867fdb4360 100644 --- a/src/Framework/Microsoft.Build.Framework.csproj +++ b/src/Framework/Microsoft.Build.Framework.csproj @@ -50,12 +50,6 @@ - - Shared\BinaryReaderExtensions.cs - - - Shared\BinaryWriterExtensions.cs - Shared\IMSBuildElementLocation.cs diff --git a/src/Framework/ProjectFinishedEventArgs.cs b/src/Framework/ProjectFinishedEventArgs.cs index 9308f830c2f..b425ece314f 100644 --- a/src/Framework/ProjectFinishedEventArgs.cs +++ b/src/Framework/ProjectFinishedEventArgs.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using Microsoft.Build.Shared; namespace Microsoft.Build.Framework { diff --git a/src/Framework/ProjectImportedEventArgs.cs b/src/Framework/ProjectImportedEventArgs.cs index 4884d1bcf23..2ec6c5193ce 100644 --- a/src/Framework/ProjectImportedEventArgs.cs +++ b/src/Framework/ProjectImportedEventArgs.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Framework/ProjectStartedEventArgs.cs b/src/Framework/ProjectStartedEventArgs.cs index 6033d11cb53..57aefbe97c4 100644 --- a/src/Framework/ProjectStartedEventArgs.cs +++ b/src/Framework/ProjectStartedEventArgs.cs @@ -9,7 +9,6 @@ using System.Linq; using System.Runtime.Serialization; using Microsoft.Build.Experimental.BuildCheck; -using Microsoft.Build.Shared; namespace Microsoft.Build.Framework { diff --git a/src/Framework/PropertyInitialValueSetEventArgs.cs b/src/Framework/PropertyInitialValueSetEventArgs.cs index fe1e52023b6..409ba29693b 100644 --- a/src/Framework/PropertyInitialValueSetEventArgs.cs +++ b/src/Framework/PropertyInitialValueSetEventArgs.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Framework/PropertyReassignmentEventArgs.cs b/src/Framework/PropertyReassignmentEventArgs.cs index d7477ee8caa..f3c49e4a18b 100644 --- a/src/Framework/PropertyReassignmentEventArgs.cs +++ b/src/Framework/PropertyReassignmentEventArgs.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Framework/TargetFinishedEventArgs.cs b/src/Framework/TargetFinishedEventArgs.cs index 454d2ce24cc..6762cbd10a4 100644 --- a/src/Framework/TargetFinishedEventArgs.cs +++ b/src/Framework/TargetFinishedEventArgs.cs @@ -4,7 +4,6 @@ using System; using System.Collections; using System.IO; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Framework/TargetSkippedEventArgs.cs b/src/Framework/TargetSkippedEventArgs.cs index 8536d2bec8a..ab2773c12d2 100644 --- a/src/Framework/TargetSkippedEventArgs.cs +++ b/src/Framework/TargetSkippedEventArgs.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Framework/TargetStartedEventArgs.cs b/src/Framework/TargetStartedEventArgs.cs index 22f162a6392..7a2fb476abd 100644 --- a/src/Framework/TargetStartedEventArgs.cs +++ b/src/Framework/TargetStartedEventArgs.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Framework/TaskFinishedEventArgs.cs b/src/Framework/TaskFinishedEventArgs.cs index 4713316748d..47a3e09ddcc 100644 --- a/src/Framework/TaskFinishedEventArgs.cs +++ b/src/Framework/TaskFinishedEventArgs.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Framework/TaskParameterEventArgs.cs b/src/Framework/TaskParameterEventArgs.cs index 8dcf97730c7..52f623017af 100644 --- a/src/Framework/TaskParameterEventArgs.cs +++ b/src/Framework/TaskParameterEventArgs.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.IO; using System.Text; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Framework/TaskStartedEventArgs.cs b/src/Framework/TaskStartedEventArgs.cs index c3b1881616b..ab6da376c33 100644 --- a/src/Framework/TaskStartedEventArgs.cs +++ b/src/Framework/TaskStartedEventArgs.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Framework/Telemetry/WorkerNodeTelemetryEventArgs.cs b/src/Framework/Telemetry/WorkerNodeTelemetryEventArgs.cs index 38c2bb43910..38b51ee6668 100644 --- a/src/Framework/Telemetry/WorkerNodeTelemetryEventArgs.cs +++ b/src/Framework/Telemetry/WorkerNodeTelemetryEventArgs.cs @@ -4,9 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -#if NETFRAMEWORK -using Microsoft.Build.Shared; -#endif namespace Microsoft.Build.Framework.Telemetry; diff --git a/src/Framework/TelemetryEventArgs.cs b/src/Framework/TelemetryEventArgs.cs index 645a72526d3..0601688632f 100644 --- a/src/Framework/TelemetryEventArgs.cs +++ b/src/Framework/TelemetryEventArgs.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using Microsoft.Build.Shared; namespace Microsoft.Build.Framework { diff --git a/src/Framework/UninitializedPropertyReadEventArgs.cs b/src/Framework/UninitializedPropertyReadEventArgs.cs index 7980bdb5485..fc67e238f63 100644 --- a/src/Framework/UninitializedPropertyReadEventArgs.cs +++ b/src/Framework/UninitializedPropertyReadEventArgs.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Shared/BinaryReaderExtensions.cs b/src/Shared/BinaryReaderExtensions.cs deleted file mode 100644 index e2907397e73..00000000000 --- a/src/Shared/BinaryReaderExtensions.cs +++ /dev/null @@ -1,132 +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; -using System.Runtime.CompilerServices; -using Microsoft.Build.Framework; - -namespace Microsoft.Build.Shared -{ - internal static class BinaryReaderExtensions - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string? ReadOptionalString(this BinaryReader reader) - { - return reader.ReadByte() == 0 ? null : reader.ReadString(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int? ReadOptionalInt32(this BinaryReader reader) - { - return reader.ReadByte() == 0 ? null : reader.ReadInt32(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static DateTime ReadTimestamp(this BinaryReader reader) - { - long timestampTicks = reader.ReadInt64(); - DateTimeKind kind = (DateTimeKind)reader.ReadInt32(); - var timestamp = new DateTime(timestampTicks, kind); - return timestamp; - } - - [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; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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/Shared/BinaryWriterExtensions.cs b/src/Shared/BinaryWriterExtensions.cs deleted file mode 100644 index 6df75ed8d67..00000000000 --- a/src/Shared/BinaryWriterExtensions.cs +++ /dev/null @@ -1,131 +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; -using System.Runtime.CompilerServices; -using Microsoft.Build.Framework; - -namespace Microsoft.Build.Shared -{ - internal static class BinaryWriterExtensions - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteOptionalString(this BinaryWriter writer, string? value) - { - if (value == null) - { - writer.Write((byte)0); - } - else - { - writer.Write((byte)1); - writer.Write(value); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteTimestamp(this BinaryWriter writer, DateTime timestamp) - { - writer.Write(timestamp.Ticks); - writer.Write((Int32)timestamp.Kind); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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); - } - - [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); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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); - } - } - } -} From 4ee291acd39ce95f0ae371b875ffb7ebb32396ee Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 10 Mar 2026 14:35:42 -0700 Subject: [PATCH 25/27] Move IMSBuildElementLocation to Framework This type was compiled from src/Shared/IMSBuildElementLocation.cs but it wasn't included in any other projects. This change removes it completely from src/Shared and includes it directly in src/Framework. It has to remain in the Microsoft.Build.Shared namespace, since it's a public API. --- src/Framework/IMSBuildElementLocation.cs | 55 ++++++++++++++++++ .../Microsoft.Build.Framework.csproj | 3 - src/Shared/IMSBuildElementLocation.cs | 56 ------------------- 3 files changed, 55 insertions(+), 59 deletions(-) create mode 100644 src/Framework/IMSBuildElementLocation.cs delete mode 100644 src/Shared/IMSBuildElementLocation.cs diff --git a/src/Framework/IMSBuildElementLocation.cs b/src/Framework/IMSBuildElementLocation.cs new file mode 100644 index 00000000000..aa994b712e6 --- /dev/null +++ b/src/Framework/IMSBuildElementLocation.cs @@ -0,0 +1,55 @@ +// 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; + +/// +/// Represents the location information for error reporting purposes. This is normally used to +/// associate a run-time error with the original XML. +/// This is not used for arbitrary errors from tasks, which store location in a BuildXXXXEventArgs. +/// All implementations should be IMMUTABLE. +/// Any editing of the project XML through the MSBuild API's will invalidate locations in that XML until the XML is reloaded. +/// +public interface IMSBuildElementLocation +{ + /// + /// The file from which this particular element originated. It may + /// differ from the ProjectFile if, for instance, it was part of + /// an import or originated in a targets file. + /// Should always have a value. + /// If not known, returns empty string. + /// + string File + { + get; + } + + /// + /// The line number where this element exists in its file. + /// The first line is numbered 1. + /// Zero indicates "unknown location". + /// + int Line + { + get; + } + + /// + /// The column number where this element exists in its file. + /// The first column is numbered 1. + /// Zero indicates "unknown location". + /// + int Column + { + get; + } + + /// + /// The location in a form suitable for replacement + /// into a message. + /// + string LocationString + { + get; + } +} diff --git a/src/Framework/Microsoft.Build.Framework.csproj b/src/Framework/Microsoft.Build.Framework.csproj index 1867fdb4360..e8606d7302a 100644 --- a/src/Framework/Microsoft.Build.Framework.csproj +++ b/src/Framework/Microsoft.Build.Framework.csproj @@ -51,9 +51,6 @@ - - Shared\IMSBuildElementLocation.cs - diff --git a/src/Shared/IMSBuildElementLocation.cs b/src/Shared/IMSBuildElementLocation.cs deleted file mode 100644 index bd329f0580b..00000000000 --- a/src/Shared/IMSBuildElementLocation.cs +++ /dev/null @@ -1,56 +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 -{ - /// - /// Represents the location information for error reporting purposes. This is normally used to - /// associate a run-time error with the original XML. - /// This is not used for arbitrary errors from tasks, which store location in a BuildXXXXEventArgs. - /// All implementations should be IMMUTABLE. - /// Any editing of the project XML through the MSBuild API's will invalidate locations in that XML until the XML is reloaded. - /// - public interface IMSBuildElementLocation - { - /// - /// The file from which this particular element originated. It may - /// differ from the ProjectFile if, for instance, it was part of - /// an import or originated in a targets file. - /// Should always have a value. - /// If not known, returns empty string. - /// - string File - { - get; - } - - /// - /// The line number where this element exists in its file. - /// The first line is numbered 1. - /// Zero indicates "unknown location". - /// - int Line - { - get; - } - - /// - /// The column number where this element exists in its file. - /// The first column is numbered 1. - /// Zero indicates "unknown location". - /// - int Column - { - get; - } - - /// - /// The location in a form suitable for replacement - /// into a message. - /// - string LocationString - { - get; - } - } -} From 9f64672c8d0dcebf1e47b6a4eba8298d5a91b7b9 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Tue, 10 Mar 2026 15:29:08 -0700 Subject: [PATCH 26/27] Rationalize FileUtilities.GetIsFileSystemCaseSensitive() This method creates a temp file to check whether the file system is case sensitive or not. Since the check relies on writing a file to disk, this change caches the result (like the .NET Runtime does) and converts the GetIsFileSystemCaseSensitive() method to a property called, IsFileSystemCaseSensitive. --- .../Definition/ProjectItem_Tests.cs | 2 +- .../Definition/Project_Tests.cs | 2 +- .../BackEnd/IntrinsicTask_Tests.cs | 2 +- .../FileUtilities_Tests.cs | 4 ++-- src/Framework/FileUtilities.cs | 19 ++++++++++++------- src/Tasks.UnitTests/Copy_Tests.cs | 2 +- 6 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/Build.OM.UnitTests/Definition/ProjectItem_Tests.cs b/src/Build.OM.UnitTests/Definition/ProjectItem_Tests.cs index 98874fae443..079d5592786 100644 --- a/src/Build.OM.UnitTests/Definition/ProjectItem_Tests.cs +++ b/src/Build.OM.UnitTests/Definition/ProjectItem_Tests.cs @@ -2478,7 +2478,7 @@ public void RemoveWithItemReferenceOnFilePathMatchingMetadata() var project = ObjectModelHelpers.CreateInMemoryProject(content); var items = project.ItemsIgnoringCondition.Where(i => i.ItemType.Equals("I2")); - if (FileUtilities.GetIsFileSystemCaseSensitive()) + if (FileUtilities.IsFileSystemCaseSensitive) { items.Select(i => i.EvaluatedInclude).ShouldBe(new[] { "a2", "b2", "c2", "g2" }); diff --git a/src/Build.OM.UnitTests/Definition/Project_Tests.cs b/src/Build.OM.UnitTests/Definition/Project_Tests.cs index 10ce7b415a9..021fdd8780c 100644 --- a/src/Build.OM.UnitTests/Definition/Project_Tests.cs +++ b/src/Build.OM.UnitTests/Definition/Project_Tests.cs @@ -3350,7 +3350,7 @@ public void GetItemProvenancePathMatchingShouldBeCaseInsensitive() ("A", Operation.Include, Provenance.StringLiteral, 1) }; - AssertProvenanceResult(expected, project, FileUtilities.GetIsFileSystemCaseSensitive() ? "a" : "A"); + AssertProvenanceResult(expected, project, FileUtilities.IsFileSystemCaseSensitive ? "a" : "A"); } diff --git a/src/Build.UnitTests/BackEnd/IntrinsicTask_Tests.cs b/src/Build.UnitTests/BackEnd/IntrinsicTask_Tests.cs index 0ae6ed74774..8feb2a31d9c 100644 --- a/src/Build.UnitTests/BackEnd/IntrinsicTask_Tests.cs +++ b/src/Build.UnitTests/BackEnd/IntrinsicTask_Tests.cs @@ -1851,7 +1851,7 @@ public void RemoveWithItemReferenceOnFilePathMatchingMetadata() var items = lookup.GetItems("I2"); - if (FileUtilities.GetIsFileSystemCaseSensitive()) + if (FileUtilities.IsFileSystemCaseSensitive) { items.Select(i => i.EvaluatedInclude).ShouldBe(new[] { "a2", "b2", "c2", "g2" }); diff --git a/src/Framework.UnitTests/FileUtilities_Tests.cs b/src/Framework.UnitTests/FileUtilities_Tests.cs index 62132cd15f3..6e8965ade92 100644 --- a/src/Framework.UnitTests/FileUtilities_Tests.cs +++ b/src/Framework.UnitTests/FileUtilities_Tests.cs @@ -268,7 +268,7 @@ public void HasExtension_WhenFileNameHasExtension_ReturnsTrue(string fileName, s { var result = FileUtilities.HasExtension(fileName, allowedExtensions); - if (!FileUtilities.GetIsFileSystemCaseSensitive() || allowedExtensions.Any(extension => fileName.Contains(extension))) + if (!FileUtilities.IsFileSystemCaseSensitive || allowedExtensions.Any(extension => fileName.Contains(extension))) { result.ShouldBeTrue(); } @@ -308,7 +308,7 @@ public void HasExtension_UsesOrdinalIgnoreCase() var result = FileUtilities.HasExtension("foo.ini", new string[] { ".INI" }); - result.ShouldBe(!FileUtilities.GetIsFileSystemCaseSensitive()); + result.ShouldBe(!FileUtilities.IsFileSystemCaseSensitive); } finally { diff --git a/src/Framework/FileUtilities.cs b/src/Framework/FileUtilities.cs index 814b2430bf4..6877bd85318 100644 --- a/src/Framework/FileUtilities.cs +++ b/src/Framework/FileUtilities.cs @@ -67,15 +67,20 @@ internal static void ClearCacheDirectoryPath() cacheDirectory = null; } - internal static readonly StringComparison PathComparison = GetIsFileSystemCaseSensitive() ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + public static readonly StringComparison PathComparison = IsFileSystemCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; - internal static readonly StringComparer PathComparer = GetIsFileSystemCaseSensitive() ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + public static readonly StringComparer PathComparer = IsFileSystemCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + + private static bool? s_isFileSystemCaseSensitive; + + public static bool IsFileSystemCaseSensitive + => s_isFileSystemCaseSensitive ??= ComputeIsFileSystemCaseSensitive(); /// - /// 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 + /// 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() + private static bool ComputeIsFileSystemCaseSensitive() { try { @@ -86,11 +91,11 @@ public static bool GetIsFileSystemCaseSensitive() return !FileSystems.Default.FileExists(lowerCased); } } - catch (Exception exc) + catch (Exception ex) { // 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); + Debug.Fail($"Casing test failed: {ex}"); return false; } } diff --git a/src/Tasks.UnitTests/Copy_Tests.cs b/src/Tasks.UnitTests/Copy_Tests.cs index 19044e99b41..4cafeba9313 100644 --- a/src/Tasks.UnitTests/Copy_Tests.cs +++ b/src/Tasks.UnitTests/Copy_Tests.cs @@ -3162,7 +3162,7 @@ internal CopyFunctor(int countOfSuccess, bool throwOnFailure) public void CopyToFileWithSameCaseInsensitiveNameAsExistingDirectoryOnUnix() { // Skip this test on case-insensitive file systems (Windows, macOS with default APFS/HFS+) - if (!FileUtilities.GetIsFileSystemCaseSensitive()) + if (!FileUtilities.IsFileSystemCaseSensitive) { return; } From 0d3953afc1cd7ddc2e8498fd04d3dbae0f79d7d7 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 11 Mar 2026 16:09:44 -0700 Subject: [PATCH 27/27] Remove a couple of lingering CLRCOMPATIBILITY code paths --- src/MSBuild/OutOfProcTaskHostNode.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index 83b7be54060..731da1973b4 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -535,11 +535,6 @@ public IReadOnlyDictionary GetGlobalProperties() public int RequestCores(int requestedCores) { -#if CLR2COMPATIBILITY - // CLR2 task host doesn't support resource management. - // If they somehow get here, throw. - throw new NotImplementedException(); -#else ErrorUtilities.VerifyThrowArgumentOutOfRange(requestedCores > 0, nameof(requestedCores)); if (!CallbacksSupported) @@ -552,15 +547,10 @@ public int RequestCores(int requestedCores) var request = new TaskHostCoresRequest(requestedCores, isRelease: false); var response = SendCallbackRequestAndWaitForResponse(request); return response.GrantedCores; -#endif } public void ReleaseCores(int coresToRelease) { -#if CLR2COMPATIBILITY - // CLR2 task host doesn't support resource management. - throw new NotImplementedException(); -#else ErrorUtilities.VerifyThrowArgumentOutOfRange(coresToRelease > 0, nameof(coresToRelease)); if (!CallbacksSupported) @@ -570,7 +560,6 @@ public void ReleaseCores(int coresToRelease) var request = new TaskHostCoresRequest(coresToRelease, isRelease: true); SendCallbackRequestAndWaitForResponse(request); -#endif } #endregion @@ -1080,9 +1069,7 @@ private void RunTask(object state) // Now set the new environment SetTaskHostEnvironment(taskConfiguration.BuildProcessEnvironment); -#if !CLR2COMPATIBILITY DotnetHostEnvironmentHelper.ClearBootstrapDotnetRootEnvironment(taskConfiguration.BuildProcessEnvironment); -#endif // Set culture Thread.CurrentThread.CurrentCulture = taskConfiguration.Culture;