Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
6 changes: 6 additions & 0 deletions src/UnitTests.Shared/TestEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,9 @@ public TransientTempPath(string tempPath, bool deleteTempDirectory)
_deleteTempDirectory = deleteTempDirectory;

_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 +648,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
Loading