2022-08-10 16:03:37 +00:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Threading.Tasks ;
using EpicGames.Core ;
2023-05-30 18:38:07 -04:00
using Microsoft.Extensions.Logging ;
2022-08-10 16:03:37 +00:00
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>
/// <param name="Logger"></param>
2023-04-19 21:44:13 -04:00
public override Task < int > ExecuteAsync ( CommandLineArguments Arguments , ILogger Logger )
2022-08-10 16:03:37 +00:00
{
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
2023-07-21 12:08:09 -04:00
List < TargetDescriptor > TargetDescriptors = TargetDescriptor . ParseCommandLine ( Arguments , BuildConfiguration , Logger ) ;
2022-08-10 16:03:37 +00:00
Clean ( TargetDescriptors , BuildConfiguration , Logger ) ;
2023-04-19 21:44:13 -04:00
return Task . FromResult ( 0 ) ;
2022-08-10 16:03:37 +00:00
}
public void Clean ( List < TargetDescriptor > TargetDescriptors , BuildConfiguration BuildConfiguration , ILogger Logger )
{
using ScopedTimer CleanTimer = new ScopedTimer ( "CleanMode.Clean()" , Logger ) ;
using IScope Scope = GlobalTracer . Instance . BuildSpan ( "CleanMode.Clean()" ) . StartActive ( ) ;
if ( TargetDescriptors . Count = = 0 )
{
throw new BuildException ( "No targets specified to clean" ) ;
}
2022-12-14 14:56:28 -05:00
// Print out warning in Xcode to stop user from trying to make build folder deletable
if ( Environment . GetEnvironmentVariable ( "UE_BUILD_FROM_XCODE" ) = = "1" & & Unreal . IsEngineInstalled ( ) )
{
Logger . LogInformation ( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" ) ;
Logger . LogInformation ( "NOTICE: Please disregard the Xcode prepare clean failed message, we do NOT want Engine/Binaries/Mac to be deletable." ) ;
Logger . LogInformation ( "NOTICE: Cleaning UnrealEditor.app will cause UE fail to launch, you will then need to repair it from Epic Games Launcher." ) ;
Logger . LogInformation ( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" ) ;
}
2022-08-10 16:03:37 +00:00
// Output the list of targets that we're cleaning
Logger . LogInformation ( "Cleaning {TargetNames} 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" , Logger ) )
{
for ( int Idx = 0 ; Idx < TargetDescriptors . Count ; + + Idx )
{
TargetDescriptor TargetDescriptor = TargetDescriptors [ Idx ] ;
// Create the rules assembly
2023-03-14 15:24:13 -04:00
RulesAssembly RulesAssembly = RulesCompiler . CreateTargetRulesAssembly ( TargetDescriptor . ProjectFile , TargetDescriptor . Name , bSkipRulesCompile , BuildConfiguration . bForceRulesCompile , BuildConfiguration . bUsePrecompiled , TargetDescriptor . ForeignPlugin , TargetDescriptor . bBuildPluginAsLocal , Logger ) ;
2022-08-10 16:03:37 +00:00
// Create the rules object
2023-09-28 06:52:15 -04:00
ReadOnlyTargetRules Target = new ReadOnlyTargetRules ( RulesAssembly . CreateTargetRules ( TargetDescriptor . Name , TargetDescriptor . Platform , TargetDescriptor . Configuration , TargetDescriptor . Architectures , TargetDescriptor . ProjectFile , TargetDescriptor . AdditionalArguments , Logger , TargetDescriptor . IsTestsTarget ) ) ;
2022-08-10 16:03:37 +00:00
2023-08-02 14:02:59 -04:00
// Get the intermediate environment for this target
UnrealIntermediateEnvironment IntermediateEnvironment = TargetDescriptor . IntermediateEnvironment ! = UnrealIntermediateEnvironment . Default ? TargetDescriptor . IntermediateEnvironment : Target . IntermediateEnvironment ;
2022-08-10 16:03:37 +00:00
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 ( ) ) ) ;
2023-03-06 19:44:29 -05:00
if ( UnrealArchitectureConfig . ForPlatform ( Target . Platform ) . RequiresArchitectureFilenames ( Target . Architectures ) )
2022-08-10 16:03:37 +00:00
{
UnrealArch/UnrealArchitectures changes
- Creates the UnrealArchitectures class, which wraps a list of UnrealArch objects
- UnrealArch is a single architecture, expandable enum-like struct
- There is no more concept of "no/default architecture", there is always a valid active architecture when building
- Most uses of "string Architecture" are replaced with one of the two above, depending if multiple architectures are supported or not
- UnrealArch has some platform-extensions for platform-specific naming (like Linux adds in LinuxName that turns, for instance, Arm64 -> aarch64-unknown-linux-gnueabi, which is used in folder names, etc)
- UnrealArch has bIsX64 which can be used determine intel instruction set (as opposed to arm)
- TargetRules class has an "Architecture" accessor that will return a single architecture if the active architectures is a single architecture, or throw an exception if multiple. This is useful in a majority of the cases where a paltform can only have a single architecture active in TargetRules (microsoft platforms, for instance, will create separate targets when compiling multiple architectures at once)
- Added UnrealArchitectureConfig class, which contains all the architecture information for a platform (what architectures are supported, what ones are currently active for given project, etc)
#preflight 63c81fb5b065224750a1759e
#rb mike.fricker,roman.dzieciol,joe.kirchoff,dmytro.vovk,brandon.schaefer [various parts]
#p4v-preflight-copy 23562471
[CL 23829977 by josh adams in ue5-main branch]
2023-01-24 09:30:28 -05:00
NameSuffixes . AddRange ( NameSuffixes . ToArray ( ) . Select ( x = > x + Target . Architecture . ToString ( ) ) ) ;
2022-08-10 16:03:37 +00:00
}
// Add all the makefiles and caches to be deleted
2023-08-02 14:02:59 -04:00
FilesToDelete . Add ( TargetMakefile . GetLocation ( Target . ProjectFile , Target . Name , Target . Platform , Target . Architectures , Target . Configuration , IntermediateEnvironment ) ) ;
2022-08-10 16:03:37 +00:00
FilesToDelete . UnionWith ( SourceFileMetadataCache . GetFilesToClean ( Target . ProjectFile ) ) ;
// Add all the intermediate folders to be deleted
foreach ( DirectoryReference BaseDir in BaseDirs )
{
foreach ( string NamePrefix in NamePrefixes )
{
2023-08-02 14:02:59 -04:00
string NamePrefixWithEnv = UEBuildTarget . GetTargetIntermediateFolderName ( NamePrefix , IntermediateEnvironment ) ;
2023-07-29 01:11:21 -04:00
// This is actually wrong.. the generated code is not in this dir.. if changing "Target.Architectures" parameter to null it will delete the right files.
// However, this also means that it will cause a rebuild for both unity, nonunity and iwyu targets since they share this folder.
DirectoryReference GeneratedCodeDir = DirectoryReference . Combine ( BaseDir , UEBuildTarget . GetPlatformIntermediateFolder ( Target . Platform , Target . Architectures , false ) , NamePrefixWithEnv , "Inc" ) ;
2022-08-10 16:03:37 +00:00
if ( DirectoryReference . Exists ( GeneratedCodeDir ) )
{
DirectoriesToDelete . Add ( GeneratedCodeDir ) ;
}
2023-07-29 01:11:21 -04:00
DirectoryReference IntermediateDir = DirectoryReference . Combine ( BaseDir , UEBuildTarget . GetPlatformIntermediateFolder ( Target . Platform , Target . Architectures , false ) , NamePrefixWithEnv , Target . Configuration . ToString ( ) ) ;
2022-08-10 16:03:37 +00:00
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" , Logger ) )
{
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" , Logger ) )
{
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 ;
} ) ;
}
2023-05-31 13:37:21 -04:00
Task DeleteDirectories = Task . Run ( ( ) = >
2022-08-10 16:03:37 +00:00
{
using ScopedTimer Timer = new ScopedTimer ( $"Delete {DirectoriesToDelete.Count} directories" , Logger ) ;
Parallel . ForEach ( DirectoriesToDelete , DirectoryToDelete = >
{
using ScopedTimer Timer = new ScopedTimer ( $"Delete directory '{DirectoryToDelete}'" , Logger , bIncreaseIndent : false ) ;
if ( DirectoryReference . Exists ( DirectoryToDelete ) )
{
Logger . LogDebug ( " Deleting {DirectoryToDelete}..." , $"{DirectoryToDelete}{Path.DirectorySeparatorChar}" ) ;
try
{
FileUtils . ForceDeleteDirectory ( DirectoryToDelete ) ;
}
catch ( Exception Ex )
{
throw new BuildException ( Ex , "Unable to delete {0} ({1})" , DirectoryToDelete , Ex . Message . TrimEnd ( ) ) ;
}
}
} ) ;
} ) ;
2023-05-31 13:37:21 -04:00
Task DeleteFiles = Task . Run ( ( ) = >
2022-08-10 16:03:37 +00:00
{
using ScopedTimer Timer = new ScopedTimer ( $"Delete {FilesToDelete.Count} files" , Logger ) ;
Parallel . ForEach ( FilesToDelete , FileToDelete = >
{
if ( FileReference . Exists ( FileToDelete ) )
{
Logger . LogDebug ( " Deleting {FileToDelete}..." , 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 , Logger ) ;
RemoteMac . Clean ( TargetDescriptor , Logger ) ;
}
}
}
}
}