Files
UnrealEngineUWP/Engine/Source/Programs/UnrealBuildTool/Modes/CleanMode.cs
jonathan adamczewski b25b8a0d7a UnrealBuildTool: Delete files in parallel when cleaning (adding a few timers)
Cleaning UnrealEngine Development Win64, on a ThreadRipper
Before: 270 seconds
After:   63 seconds

#jira none
#rnx
#preflight 626b29bbb17dd9121b63f219

[CL 19982248 by jonathan adamczewski in ue5-main branch]
2022-04-29 13:53:48 -04:00

315 lines
12 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EpicGames.Core;
using OpenTracing;
using OpenTracing.Util;
using UnrealBuildBase;
namespace UnrealBuildTool
{
/// <summary>
/// Cleans build products and intermediates for the target. This deletes files which are named consistently with the target being built
/// (e.g. UnrealEditor-Foo-Win64-Debug.dll) rather than an actual record of previous build products.
/// </summary>
[ToolMode("Clean", ToolModeOptions.XmlConfig | ToolModeOptions.BuildPlatforms | ToolModeOptions.SingleInstance)]
class CleanMode : ToolMode
{
/// <summary>
/// Whether to avoid cleaning targets
/// </summary>
[CommandLine("-SkipRulesCompile")]
bool bSkipRulesCompile = false;
/// <summary>
/// Skip pre build targets; just do the main target.
/// </summary>
[CommandLine("-SkipPreBuildTargets")]
public bool bSkipPreBuildTargets = false;
/// <summary>
/// Main entry point
/// </summary>
/// <param name="Arguments">Command-line arguments</param>
/// <returns>One of the values of ECompilationResult</returns>
public override int Execute(CommandLineArguments Arguments)
{
Arguments.ApplyTo(this);
// Create the build configuration object, and read the settings
BuildConfiguration BuildConfiguration = new BuildConfiguration();
XmlConfig.ApplyTo(BuildConfiguration);
Arguments.ApplyTo(BuildConfiguration);
// Parse all the targets being built
List<TargetDescriptor> TargetDescriptors = TargetDescriptor.ParseCommandLine(Arguments, BuildConfiguration.bUsePrecompiled, bSkipRulesCompile, BuildConfiguration.bForceRulesCompile);
Clean(TargetDescriptors, BuildConfiguration);
return 0;
}
public void Clean(List<TargetDescriptor> TargetDescriptors, BuildConfiguration BuildConfiguration)
{
using ScopedTimer CleanTimer = new ScopedTimer("CleanMode.Clean()");
using IScope Scope = GlobalTracer.Instance.BuildSpan("CleanMode.Clean()").StartActive();
if (TargetDescriptors.Count == 0)
{
throw new BuildException("No targets specified to clean");
}
// Also add implicit descriptors for cleaning UnrealBuildTool
if (!BuildConfiguration.bDoNotBuildUHT)
{
const string UnrealHeaderToolTarget = "UnrealHeaderTool";
// Get a list of project files to clean UHT for
List<FileReference?> ProjectFiles = new List<FileReference?>();
foreach (TargetDescriptor TargetDesc in TargetDescriptors)
{
if (TargetDesc.Name != UnrealHeaderToolTarget && !RemoteMac.HandlesTargetPlatform(TargetDesc.Platform))
{
if (ProjectFiles.Count == 0)
{
ProjectFiles.Add(null);
}
if (TargetDesc.ProjectFile != null && !ProjectFiles.Contains(TargetDesc.ProjectFile))
{
ProjectFiles.Add(TargetDesc.ProjectFile);
}
}
}
// Add descriptors for cleaning UHT with all these projects
if (ProjectFiles.Count > 0)
{
UnrealTargetConfiguration Configuration = BuildConfiguration.bForceDebugUnrealHeaderTool ? UnrealTargetConfiguration.Debug : UnrealTargetConfiguration.Development;
string Architecture = UEBuildPlatform.GetBuildPlatform(BuildHostPlatform.Current.Platform).GetDefaultArchitecture(null);
foreach (FileReference? ProjectFile in ProjectFiles)
{
TargetDescriptors.Add(new TargetDescriptor(ProjectFile, UnrealHeaderToolTarget, BuildHostPlatform.Current.Platform, Configuration, Architecture, null));
}
}
}
// Output the list of targets that we're cleaning
Log.TraceInformation("Cleaning {0} binaries...", StringUtils.FormatList(TargetDescriptors.Select(x => x.Name).Distinct()));
// Loop through all the targets, and clean them all
HashSet<FileReference> FilesToDelete = new HashSet<FileReference>();
HashSet<DirectoryReference> DirectoriesToDelete = new HashSet<DirectoryReference>();
using (ScopedTimer GatherTimer = new ScopedTimer("Find paths to clean"))
{
for (int Idx = 0; Idx < TargetDescriptors.Count; ++Idx)
{
TargetDescriptor TargetDescriptor = TargetDescriptors[Idx];
// Create the rules assembly
RulesAssembly RulesAssembly = RulesCompiler.CreateTargetRulesAssembly(TargetDescriptor.ProjectFile, TargetDescriptor.Name, bSkipRulesCompile, BuildConfiguration.bForceRulesCompile, BuildConfiguration.bUsePrecompiled, TargetDescriptor.ForeignPlugin);
// Create the rules object
ReadOnlyTargetRules Target = new ReadOnlyTargetRules(RulesAssembly.CreateTargetRules(TargetDescriptor.Name, TargetDescriptor.Platform, TargetDescriptor.Configuration, TargetDescriptor.Architecture, TargetDescriptor.ProjectFile, TargetDescriptor.AdditionalArguments));
if (!bSkipPreBuildTargets && Target.PreBuildTargets.Count > 0)
{
foreach (TargetInfo PreBuildTarget in Target.PreBuildTargets)
{
TargetDescriptor NewTarget = TargetDescriptor.FromTargetInfo(PreBuildTarget);
if (!TargetDescriptors.Contains(NewTarget))
{
TargetDescriptors.Add(NewTarget);
}
}
}
// Find the base folders that can contain binaries
List<DirectoryReference> BaseDirs = new List<DirectoryReference>();
BaseDirs.Add(Unreal.EngineDirectory);
foreach (FileReference Plugin in PluginsBase.EnumeratePlugins(Target.ProjectFile))
{
BaseDirs.Add(Plugin.Directory);
}
if (Target.ProjectFile != null)
{
BaseDirs.Add(Target.ProjectFile.Directory);
}
// If we're running a precompiled build, remove anything under the engine folder
BaseDirs.RemoveAll(x => RulesAssembly.IsReadOnly(x));
// Get all the names which can prefix build products
List<string> NamePrefixes = new List<string>();
if (Target.Type != TargetType.Program)
{
NamePrefixes.Add(UEBuildTarget.GetAppNameForTargetType(Target.Type));
}
NamePrefixes.Add(Target.Name);
// Get the suffixes for this configuration
List<string> NameSuffixes = new List<string>();
if (Target.Configuration == Target.UndecoratedConfiguration)
{
NameSuffixes.Add("");
}
NameSuffixes.Add(String.Format("-{0}-{1}", Target.Platform.ToString(), Target.Configuration.ToString()));
if (!String.IsNullOrEmpty(Target.Architecture))
{
NameSuffixes.AddRange(NameSuffixes.ToArray().Select(x => x + Target.Architecture));
}
// Add all the makefiles and caches to be deleted
FilesToDelete.Add(TargetMakefile.GetLocation(Target.ProjectFile, Target.Name, Target.Platform, Target.Architecture, Target.Configuration));
FilesToDelete.UnionWith(SourceFileMetadataCache.GetFilesToClean(Target.ProjectFile));
// Add all the intermediate folders to be deleted
foreach (DirectoryReference BaseDir in BaseDirs)
{
foreach (string NamePrefix in NamePrefixes)
{
DirectoryReference GeneratedCodeDir = DirectoryReference.Combine(BaseDir, UEBuildTarget.GetPlatformIntermediateFolder(Target.Platform, Target.Architecture, false), NamePrefix, "Inc");
if (DirectoryReference.Exists(GeneratedCodeDir))
{
DirectoriesToDelete.Add(GeneratedCodeDir);
}
DirectoryReference IntermediateDir = DirectoryReference.Combine(BaseDir, UEBuildTarget.GetPlatformIntermediateFolder(Target.Platform, Target.Architecture, false), NamePrefix, Target.Configuration.ToString());
if (DirectoryReference.Exists(IntermediateDir))
{
DirectoriesToDelete.Add(IntermediateDir);
}
}
}
// todo: handle external plugin intermediates, written to the Project's Intermediate/External directory
// List of additional files and directories to clean, specified by the target platform
List<FileReference> AdditionalFilesToDelete = new List<FileReference>();
List<DirectoryReference> AdditionalDirectoriesToDelete = new List<DirectoryReference>();
// Add all the build products from this target
string[] NamePrefixesArray = NamePrefixes.Distinct().ToArray();
string[] NameSuffixesArray = NameSuffixes.Distinct().ToArray();
foreach (DirectoryReference BaseDir in BaseDirs)
{
DirectoryReference BinariesDir = DirectoryReference.Combine(BaseDir, "Binaries", Target.Platform.ToString());
if (DirectoryReference.Exists(BinariesDir))
{
UEBuildPlatform.GetBuildPlatform(Target.Platform).FindBuildProductsToClean(BinariesDir, NamePrefixesArray, NameSuffixesArray, AdditionalFilesToDelete, AdditionalDirectoriesToDelete);
}
}
// Get all the additional intermediate folders created by this platform
UEBuildPlatform.GetBuildPlatform(Target.Platform).FindAdditionalBuildProductsToClean(Target, AdditionalFilesToDelete, AdditionalDirectoriesToDelete);
// Add the platform's files and directories to the main list
FilesToDelete.UnionWith(AdditionalFilesToDelete);
DirectoriesToDelete.UnionWith(AdditionalDirectoriesToDelete);
}
}
// Ensure no overlap between directories
using (ScopedTimer Timer = new ScopedTimer("Ensure no directory overlap"))
{
HashSet<DirectoryReference> SubdirectoriesToDelete = new(DirectoriesToDelete.Count);
foreach (DirectoryReference Directory in DirectoriesToDelete)
{
// Is this directory a subdirectory of some other directory that is being deleted?
for (DirectoryReference? DirectoryWalker = Directory.ParentDirectory; DirectoryWalker != null; DirectoryWalker = DirectoryWalker.ParentDirectory)
{
if (DirectoriesToDelete.Contains(DirectoryWalker))
{
SubdirectoriesToDelete.Add(Directory);
break;
}
}
}
DirectoriesToDelete.ExceptWith(SubdirectoriesToDelete);
}
// Remove any files that are contained within one of the directories
using (ScopedTimer Time = new ScopedTimer("Ensure no file overlap"))
{
FilesToDelete.RemoveWhere(File =>
{
// Is this file in a subdirectory of some directory that is being deleted?
for (DirectoryReference? DirectoryWalker = File.Directory; DirectoryWalker != null; DirectoryWalker = DirectoryWalker.ParentDirectory)
{
if (DirectoriesToDelete.Contains(DirectoryWalker))
{
return true;
}
}
return false;
});
}
var DeleteDirectories = Task.Run(() =>
{
using ScopedTimer Timer = new ScopedTimer($"Delete {DirectoriesToDelete.Count} directories");
Parallel.ForEach(DirectoriesToDelete, DirectoryToDelete =>
{
using ScopedTimer Timer = new ScopedTimer($"Delete directory '{DirectoryToDelete}'", bIncreaseIndent: false);
if (DirectoryReference.Exists(DirectoryToDelete))
{
Log.TraceVerbose($" Deleting {DirectoryToDelete}{Path.DirectorySeparatorChar}...");
try
{
FileUtils.ForceDeleteDirectory(DirectoryToDelete);
}
catch (Exception Ex)
{
throw new BuildException(Ex, "Unable to delete {0} ({1})", DirectoryToDelete, Ex.Message.TrimEnd());
}
}
});
});
var DeleteFiles = Task.Run(() =>
{
using ScopedTimer Timer = new ScopedTimer($"Delete {FilesToDelete.Count} files");
Parallel.ForEach(FilesToDelete, FileToDelete =>
{
if (FileReference.Exists(FileToDelete))
{
Log.TraceVerbose($" Deleting {FileToDelete}...");
try
{
FileUtils.ForceDeleteFile(FileToDelete);
}
catch (Exception Ex)
{
throw new BuildException(Ex, "Unable to delete {0} ({1})", FileToDelete, Ex.Message.TrimEnd());
}
}
});
});
DeleteDirectories.Wait();
DeleteFiles.Wait();
// Also clean all the remote targets
for (int Idx = 0; Idx < TargetDescriptors.Count; Idx++)
{
TargetDescriptor TargetDescriptor = TargetDescriptors[Idx];
if (RemoteMac.HandlesTargetPlatform(TargetDescriptor.Platform))
{
RemoteMac RemoteMac = new RemoteMac(TargetDescriptor.ProjectFile);
RemoteMac.Clean(TargetDescriptor);
}
}
}
}
}