Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions src/Build/BackEnd/BuildManager/BuildManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1151,11 +1151,6 @@ public void EndBuild()
Reset();
_buildManagerState = BuildManagerState.Idle;

if (Traits.Instance.ForceTaskFactoryOutOfProc || _buildParameters.MultiThreaded)
{
TaskFactoryUtilities.CleanCurrentProcessInlineTaskDirectory();
}

MSBuildEventSource.Log.BuildStop();

_threadException?.Throw();
Expand Down
45 changes: 1 addition & 44 deletions src/Shared/TaskFactoryUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ namespace Microsoft.Build.Shared
/// </summary>
internal static class TaskFactoryUtilities
{
/// <summary>
/// The sub-path within the temporary directory where compiled inline tasks are located.
/// </summary>
public const string InlineTaskTempDllSubPath = nameof(InlineTaskTempDllSubPath);
public const string InlineTaskSuffix = "inline_task.dll";
public const string InlineTaskLoadManifestSuffix = ".loadmanifest";

Expand All @@ -57,30 +53,13 @@ public CachedAssemblyEntry(Assembly assembly, string assemblyPath)
public bool IsValid => string.IsNullOrEmpty(AssemblyPath) || FileUtilities.FileExistsNoThrow(AssemblyPath);
}


/// <summary>
/// Creates a process-specific temporary directory for inline task assemblies.
/// </summary>
/// <returns>The path to the created temporary directory.</returns>
public static string CreateProcessSpecificTemporaryTaskDirectory()
{
string processSpecificInlineTaskDir = Path.Combine(
FileUtilities.TempFileDirectory,
InlineTaskTempDllSubPath,
$"pid_{EnvironmentUtilities.CurrentProcessId}");

Directory.CreateDirectory(processSpecificInlineTaskDir);
return processSpecificInlineTaskDir;
}

/// <summary>
/// Gets a temporary file path for an inline task assembly in the process-specific directory.
/// </summary>
/// <returns>The full path to the temporary file.</returns>
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);
}

/// <summary>
Expand Down Expand Up @@ -256,28 +235,6 @@ public static bool ShouldCompileForOutOfProcess(IBuildEngine taskFactoryEngineCo
return false;
}

/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public static void CleanCurrentProcessInlineTaskDirectory()
{
string processSpecificInlineTaskDir = Path.Combine(
FileUtilities.TempFileDirectory,
InlineTaskTempDllSubPath,
$"pid_{EnvironmentUtilities.CurrentProcessId}");

if (FileSystems.Default.DirectoryExists(processSpecificInlineTaskDir))
{
FileUtilities.DeleteDirectoryNoThrow(processSpecificInlineTaskDir, recursive: true);
}
}

/// <summary>
/// Attempts to load an assembly by searching in the specified directories.
/// </summary>
Expand Down
61 changes: 40 additions & 21 deletions src/Shared/TempFileUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.Build.Shared.FileSystem;

#nullable disable
Expand All @@ -16,40 +17,58 @@ namespace Microsoft.Build.Shared
/// </summary>
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<string> tempFileDirectory = CreateTempFileDirectoryLazy();

private const string msbuildTempFolderPrefix = "MSBuildTemp";

internal static string TempFileDirectory
internal static string TempFileDirectory => tempFileDirectory.Value;

private static Lazy<string> CreateTempFileDirectoryLazy()
{
return new Lazy<string>(
() =>
{
string path = CreateFolderUnderTemp();
RegisterCleanupOnExit(path);
return path;
},
LazyThreadSafetyMode.ExecutionAndPublication);
}

private static void RegisterCleanupOnExit(string pathToCleanup)
{
get
AppDomain.CurrentDomain.ProcessExit += (_, _) =>
Comment thread
JanProvaznik marked this conversation as resolved.
{
return tempFileDirectory ??= CreateFolderUnderTemp();
}
try
{
if (Directory.Exists(pathToCleanup))
{
Directory.Delete(pathToCleanup, recursive: true);
}
}
catch
{
// Best effort - ignore failures during cleanup
}
};
}

internal static void ClearTempFileDirectory()
{
Comment thread
JanProvaznik marked this conversation as resolved.
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}");
Comment thread
JanProvaznik marked this conversation as resolved.
Directory.CreateDirectory(path);
#endif
}
else
{
path = Path.Combine(Path.GetTempPath(), msbuildTempFolderPrefix);
Directory.CreateDirectory(path);
}

return FileUtilities.EnsureTrailingSlash(path);
}
Expand Down
3 changes: 0 additions & 3 deletions src/Tasks.UnitTests/CodeTaskFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -758,8 +758,6 @@ public void BuildTaskSimpleCodeFactoryTestExtraReference(bool forceOutOfProc)
[Fact]
public void OutOfProcCodeTaskFactoryCachesAssemblyPath()
{
TaskFactoryUtilities.CleanCurrentProcessInlineTaskDirectory();

try
{
const string taskElementContents = @"<Code Type=""Fragment"" Language=""cs"">
Expand Down Expand Up @@ -805,7 +803,6 @@ public void OutOfProcCodeTaskFactoryCachesAssemblyPath()
}
finally
{
TaskFactoryUtilities.CleanCurrentProcessInlineTaskDirectory();
}
}

Expand Down
3 changes: 0 additions & 3 deletions src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,6 @@ public void RoslynCodeTaskFactory_ReuseCompilation(bool forceOutOfProc)
[Fact]
public void OutOfProcRoslynTaskFactoryCachesAssemblyPath()
{
TaskFactoryUtilities.CleanCurrentProcessInlineTaskDirectory();

try
{
const string taskBody = @"
Expand Down Expand Up @@ -285,7 +283,6 @@ public void OutOfProcRoslynTaskFactoryCachesAssemblyPath()
}
finally
{
TaskFactoryUtilities.CleanCurrentProcessInlineTaskDirectory();
}
}

Expand Down
1 change: 0 additions & 1 deletion src/Tasks.UnitTests/TaskFactoryUtilities_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ public void GetTemporaryTaskAssemblyPath_ShouldReturnValidPath()
// Assert
assemblyPath.ShouldNotBeNull();
assemblyPath.ShouldEndWith(".dll");
Path.GetDirectoryName(assemblyPath).ShouldContain(TaskFactoryUtilities.InlineTaskTempDllSubPath);
}

[Fact]
Expand Down
3 changes: 0 additions & 3 deletions src/Tasks.UnitTests/XamlTaskFactory_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -450,8 +450,6 @@ public class CompilationTests
[Fact]
public void OutOfProcXamlTaskFactoryProvidesAssemblyPath()
{
TaskFactoryUtilities.CleanCurrentProcessInlineTaskDirectory();

try
{
const string taskElementContents = @"<ProjectSchemaDefinitions xmlns=""clr-namespace:Microsoft.Build.Framework.XamlTypes;assembly=Microsoft.Build.Framework"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
Expand Down Expand Up @@ -480,7 +478,6 @@ public void OutOfProcXamlTaskFactoryProvidesAssemblyPath()
}
finally
{
TaskFactoryUtilities.CleanCurrentProcessInlineTaskDirectory();
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/UnitTests.Shared/TestEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
{
Expand Down