diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 3032b80f93a..7a70791f17b 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -1151,11 +1151,6 @@ public void EndBuild() Reset(); _buildManagerState = BuildManagerState.Idle; - if (Traits.Instance.ForceTaskFactoryOutOfProc || _buildParameters.MultiThreaded) - { - TaskFactoryUtilities.CleanCurrentProcessInlineTaskDirectory(); - } - MSBuildEventSource.Log.BuildStop(); _threadException?.Throw(); diff --git a/src/Shared/TaskFactoryUtilities.cs b/src/Shared/TaskFactoryUtilities.cs index 0f671430f87..1dc8353799e 100644 --- a/src/Shared/TaskFactoryUtilities.cs +++ b/src/Shared/TaskFactoryUtilities.cs @@ -27,10 +27,6 @@ namespace Microsoft.Build.Shared /// internal static class TaskFactoryUtilities { - /// - /// The sub-path within the temporary directory where compiled inline tasks are located. - /// - public const string InlineTaskTempDllSubPath = nameof(InlineTaskTempDllSubPath); public const string InlineTaskSuffix = "inline_task.dll"; public const string InlineTaskLoadManifestSuffix = ".loadmanifest"; @@ -57,30 +53,13 @@ public CachedAssemblyEntry(Assembly assembly, string assemblyPath) public bool IsValid => string.IsNullOrEmpty(AssemblyPath) || FileUtilities.FileExistsNoThrow(AssemblyPath); } - - /// - /// Creates a process-specific temporary directory for inline task assemblies. - /// - /// The path to the created temporary directory. - public static string CreateProcessSpecificTemporaryTaskDirectory() - { - string processSpecificInlineTaskDir = Path.Combine( - FileUtilities.TempFileDirectory, - InlineTaskTempDllSubPath, - $"pid_{EnvironmentUtilities.CurrentProcessId}"); - - Directory.CreateDirectory(processSpecificInlineTaskDir); - return processSpecificInlineTaskDir; - } - /// /// Gets a temporary file path for an inline task assembly in the process-specific directory. /// /// The full path to the temporary file. public static string GetTemporaryTaskAssemblyPath() { - string taskDir = CreateProcessSpecificTemporaryTaskDirectory(); - return FileUtilities.GetTemporaryFile(taskDir, fileName: null, extension: "inline_task.dll", createFile: false); + return FileUtilities.GetTemporaryFile(directory: null, fileName: null, extension: "inline_task.dll", createFile: false); } /// @@ -256,28 +235,6 @@ public static bool ShouldCompileForOutOfProcess(IBuildEngine taskFactoryEngineCo return false; } - /// - /// Cleans up the current process's inline task directory by deleting the temporary directory - /// and its contents used for inline task assemblies for this specific process. - /// This should be called at the end of a build to prevent dangling DLL files. - /// - /// - /// On Windows platforms, this may fail to delete files that are still locked by the current process. - /// However, it will clean up any files that are no longer in use. - /// - public static void CleanCurrentProcessInlineTaskDirectory() - { - string processSpecificInlineTaskDir = Path.Combine( - FileUtilities.TempFileDirectory, - InlineTaskTempDllSubPath, - $"pid_{EnvironmentUtilities.CurrentProcessId}"); - - if (FileSystems.Default.DirectoryExists(processSpecificInlineTaskDir)) - { - FileUtilities.DeleteDirectoryNoThrow(processSpecificInlineTaskDir, recursive: true); - } - } - /// /// Attempts to load an assembly by searching in the specified directories. /// diff --git a/src/Shared/TempFileUtilities.cs b/src/Shared/TempFileUtilities.cs index 2b77811c660..909b9863dc1 100644 --- a/src/Shared/TempFileUtilities.cs +++ b/src/Shared/TempFileUtilities.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Runtime.CompilerServices; +using System.Threading; using Microsoft.Build.Shared.FileSystem; #nullable disable @@ -16,40 +17,58 @@ namespace Microsoft.Build.Shared /// internal static partial class FileUtilities { - // For the current user, these correspond to read, write, and execute permissions. - // Lower order bits correspond to the same for "group" or "other" users. - private static string tempFileDirectory = null; + private static Lazy tempFileDirectory = CreateTempFileDirectoryLazy(); + private const string msbuildTempFolderPrefix = "MSBuildTemp"; - internal static string TempFileDirectory + 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) { - get + AppDomain.CurrentDomain.ProcessExit += (_, _) => { - return tempFileDirectory ??= CreateFolderUnderTemp(); - } + try + { + if (Directory.Exists(pathToCleanup)) + { + Directory.Delete(pathToCleanup, recursive: true); + } + } + catch + { + // Best effort - ignore failures during cleanup + } + }; } internal static void ClearTempFileDirectory() { - tempFileDirectory = null; + tempFileDirectory = CreateTempFileDirectoryLazy(); } - // For all native calls, directly check their return values to prevent bad actors from getting in between checking if a directory exists and returning it. private static string CreateFolderUnderTemp() { - string path = null; - - if (NativeMethodsShared.IsLinux) - { -#if NET // always true, Linux implies NET - path = Directory.CreateTempSubdirectory(msbuildTempFolderPrefix).FullName; + 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 - } - else - { - path = Path.Combine(Path.GetTempPath(), msbuildTempFolderPrefix); - Directory.CreateDirectory(path); - } return FileUtilities.EnsureTrailingSlash(path); } diff --git a/src/Tasks.UnitTests/CodeTaskFactoryTests.cs b/src/Tasks.UnitTests/CodeTaskFactoryTests.cs index b61dd893659..2ab31ff10f8 100644 --- a/src/Tasks.UnitTests/CodeTaskFactoryTests.cs +++ b/src/Tasks.UnitTests/CodeTaskFactoryTests.cs @@ -758,8 +758,6 @@ public void BuildTaskSimpleCodeFactoryTestExtraReference(bool forceOutOfProc) [Fact] public void OutOfProcCodeTaskFactoryCachesAssemblyPath() { - TaskFactoryUtilities.CleanCurrentProcessInlineTaskDirectory(); - try { const string taskElementContents = @" @@ -805,7 +803,6 @@ public void OutOfProcCodeTaskFactoryCachesAssemblyPath() } finally { - TaskFactoryUtilities.CleanCurrentProcessInlineTaskDirectory(); } } diff --git a/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs b/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs index 4992ba064f7..814ea1e440a 100644 --- a/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs +++ b/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs @@ -238,8 +238,6 @@ public void RoslynCodeTaskFactory_ReuseCompilation(bool forceOutOfProc) [Fact] public void OutOfProcRoslynTaskFactoryCachesAssemblyPath() { - TaskFactoryUtilities.CleanCurrentProcessInlineTaskDirectory(); - try { const string taskBody = @" @@ -285,7 +283,6 @@ public void OutOfProcRoslynTaskFactoryCachesAssemblyPath() } finally { - TaskFactoryUtilities.CleanCurrentProcessInlineTaskDirectory(); } } diff --git a/src/Tasks.UnitTests/TaskFactoryUtilities_Tests.cs b/src/Tasks.UnitTests/TaskFactoryUtilities_Tests.cs index 1bc4386371e..4e10d18e565 100644 --- a/src/Tasks.UnitTests/TaskFactoryUtilities_Tests.cs +++ b/src/Tasks.UnitTests/TaskFactoryUtilities_Tests.cs @@ -26,7 +26,6 @@ public void GetTemporaryTaskAssemblyPath_ShouldReturnValidPath() // Assert assemblyPath.ShouldNotBeNull(); assemblyPath.ShouldEndWith(".dll"); - Path.GetDirectoryName(assemblyPath).ShouldContain(TaskFactoryUtilities.InlineTaskTempDllSubPath); } [Fact] diff --git a/src/Tasks.UnitTests/XamlTaskFactory_Tests.cs b/src/Tasks.UnitTests/XamlTaskFactory_Tests.cs index 370d6dd587b..8eb7e642931 100644 --- a/src/Tasks.UnitTests/XamlTaskFactory_Tests.cs +++ b/src/Tasks.UnitTests/XamlTaskFactory_Tests.cs @@ -450,8 +450,6 @@ public class CompilationTests [Fact] public void OutOfProcXamlTaskFactoryProvidesAssemblyPath() { - TaskFactoryUtilities.CleanCurrentProcessInlineTaskDirectory(); - try { const string taskElementContents = @" @@ -480,7 +478,6 @@ public void OutOfProcXamlTaskFactoryProvidesAssemblyPath() } finally { - TaskFactoryUtilities.CleanCurrentProcessInlineTaskDirectory(); } } diff --git a/src/UnitTests.Shared/TestEnvironment.cs b/src/UnitTests.Shared/TestEnvironment.cs index 1525ccbfa94..586afed20e0 100644 --- a/src/UnitTests.Shared/TestEnvironment.cs +++ b/src/UnitTests.Shared/TestEnvironment.cs @@ -599,7 +599,14 @@ public TransientTempPath(string tempPath, bool deleteTempDirectory) TempPath = tempPath; _deleteTempDirectory = deleteTempDirectory; + // Ensure the temp directory exists before setting it as TMPDIR + // This is required because Directory.CreateTempSubdirectory() expects TMPDIR to exist + Directory.CreateDirectory(tempPath); + _oldtempPaths = SetTempPath(tempPath); + + // Clear the cached temp directory so FileUtilities picks up the new TMPDIR/TMP/TEMP + FileUtilities.ClearTempFileDirectory(); } private static TempPaths SetTempPath(string tempPath) @@ -645,6 +652,9 @@ private static TempPaths GetTempPaths() public override void Revert() { SetTempPaths(_oldtempPaths); + + // Clear the cached temp directory so FileUtilities picks up the restored TMPDIR/TMP/TEMP + FileUtilities.ClearTempFileDirectory(); if (_deleteTempDirectory) {