2024-01-09 18:26:51 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "UbaControllerModule.h"
# include "Misc/ConfigCacheIni.h"
# include "UbaJobProcessor.h"
# include "Features/IModularFeatures.h"
# include "HAL/FileManager.h"
# include "Misc/CommandLine.h"
# include "Misc/CoreDelegates.h"
# include "Misc/Paths.h"
# include "Modules/ModuleManager.h"
# include "CoreMinimal.h"
DEFINE_LOG_CATEGORY ( LogUbaController ) ;
namespace UbaControllerModule
{
static constexpr int32 SubFolderCount = 32 ;
2024-02-13 13:28:56 -05:00
2024-05-02 16:10:40 -04:00
static bool bDumpTraceFiles = true ;
2024-02-13 13:28:56 -05:00
static FAutoConsoleVariableRef CVarDumpTraceFiles (
TEXT ( " r.UbaController.DumpTraceFiles " ) ,
bDumpTraceFiles ,
2024-05-02 16:10:40 -04:00
TEXT ( " If true, UBA controller dumps trace files for later use with UBA visualizer in the Saved folder under UbaController (Enabled by default) " ) ) ;
2024-02-13 13:28:56 -05:00
static FString MakeAndGetDebugInfoPath ( )
{
// Build machines should dump to the AutomationTool/Saved/Logs directory and they will upload as build artifacts via the AutomationTool.
FString BaseDebugInfoPath = FPaths : : ProjectSavedDir ( ) ;
if ( GIsBuildMachine )
{
BaseDebugInfoPath = FPaths : : Combine ( * FPaths : : EngineDir ( ) , TEXT ( " Programs " ) , TEXT ( " AutomationTool " ) , TEXT ( " Saved " ) , TEXT ( " Logs " ) ) ;
}
FString AbsoluteDebugInfoDirectory = IFileManager : : Get ( ) . ConvertToAbsolutePathForExternalAppForWrite ( * ( BaseDebugInfoPath / TEXT ( " UbaController " ) ) ) ;
FPaths : : NormalizeDirectoryName ( AbsoluteDebugInfoDirectory ) ;
// Create directory if it doesn't exit yet
if ( ! IFileManager : : Get ( ) . DirectoryExists ( * AbsoluteDebugInfoDirectory ) )
{
IFileManager : : Get ( ) . MakeDirectory ( * AbsoluteDebugInfoDirectory , true ) ;
}
return AbsoluteDebugInfoDirectory ;
}
2024-08-20 19:41:17 -04:00
FString GetTempDir ( )
{
const FString HordeSharedDir = FPlatformMisc : : GetEnvironmentVariable ( TEXT ( " UE_HORDE_SHARED_DIR " ) ) ;
if ( ! HordeSharedDir . IsEmpty ( ) )
return HordeSharedDir ;
return FPlatformProcess : : UserTempDir ( ) ;
}
2024-01-09 18:26:51 -05:00
} ;
FUbaControllerModule : : FUbaControllerModule ( )
: bSupported ( false )
, bModuleInitialized ( false )
, bControllerInitialized ( false )
2024-08-20 19:41:17 -04:00
, RootWorkingDirectory ( FPaths : : Combine ( * UbaControllerModule : : GetTempDir ( ) , TEXT ( " UbaControllerWorkingDir " ) ) )
2024-01-09 18:26:51 -05:00
, WorkingDirectory ( FPaths : : Combine ( RootWorkingDirectory , FGuid : : NewGuid ( ) . ToString ( EGuidFormats : : Digits ) ) )
, NextFileID ( 0 )
, NextTaskID ( 0 )
{
}
FUbaControllerModule : : ~ FUbaControllerModule ( )
{
if ( JobDispatcherThread )
{
JobDispatcherThread - > Stop ( ) ;
// Wait until the thread is done
FPlatformProcess : : ConditionalSleep ( [ & ] ( ) { return JobDispatcherThread & & JobDispatcherThread - > IsWorkDone ( ) ; } , 0.1f ) ;
}
CleanWorkingDirectory ( ) ;
}
2024-05-15 11:40:23 -04:00
static bool IsUbaControllerEnabled ( )
{
if ( FParse : : Param ( FCommandLine : : Get ( ) , TEXT ( " NoUbaController " ) ) )
{
return false ;
}
// Check if UbaController is enabled via command line argument
if ( FParse : : Param ( FCommandLine : : Get ( ) , TEXT ( " Uba " ) ) )
{
return true ;
}
// Check if UbaController is enabled via INI configuration in [UbaController] section.
FString EnabledState ;
GConfig - > GetString ( TEXT ( " UbaController " ) , TEXT ( " Enabled " ) , EnabledState , GEngineIni ) ;
// This "Enabled" parameter is a tri-state so we have to parse it as a string.
// Those strings can include the comments (starting with ';') from the INI file, so we have to trim that section from the string.
const int32 EnabledStateCommentPosition = EnabledState . Find ( TEXT ( " ; " ) ) ;
if ( EnabledStateCommentPosition ! = INDEX_NONE )
{
EnabledState . RemoveAt ( EnabledStateCommentPosition , EnabledState . Len ( ) - EnabledStateCommentPosition ) ;
EnabledState . RemoveSpacesInline ( ) ;
}
if ( EnabledState . Equals ( TEXT ( " True " ) , ESearchCase : : IgnoreCase ) | | ( EnabledState . Equals ( TEXT ( " BuildMachineOnly " ) , ESearchCase : : IgnoreCase ) & & GIsBuildMachine ) )
{
return true ;
}
return false ;
}
2024-01-09 18:26:51 -05:00
bool FUbaControllerModule : : IsSupported ( )
{
if ( bControllerInitialized )
{
return bSupported ;
}
2024-05-15 11:40:23 -04:00
const bool bEnabled = IsUbaControllerEnabled ( ) ;
2024-01-09 18:26:51 -05:00
bSupported = FPlatformProcess : : SupportsMultithreading ( ) & & bEnabled ;
return bSupported ;
}
void FUbaControllerModule : : CleanWorkingDirectory ( ) const
{
2024-01-10 20:47:59 -05:00
if ( UE : : GetMultiprocessId ( ) ! = 0 ) // Only director is allowed to clean
{
return ;
}
2024-01-09 18:26:51 -05:00
IFileManager & FileManager = IFileManager : : Get ( ) ;
if ( ! RootWorkingDirectory . IsEmpty ( ) )
{
if ( ! FileManager . DeleteDirectory ( * RootWorkingDirectory , false , true ) )
{
UE_LOG ( LogUbaController , Log , TEXT ( " %s => Failed to delete current working Directory => %s " ) , ANSI_TO_TCHAR ( __FUNCTION__ ) , * RootWorkingDirectory ) ;
}
}
}
bool FUbaControllerModule : : HasTasksDispatchedOrPending ( ) const
{
return ! PendingRequestedCompilationTasks . IsEmpty ( ) | | ( JobDispatcherThread . IsValid ( ) & & JobDispatcherThread - > HasJobsInFlight ( ) ) ;
}
FString GetUbaBinariesPath ( )
{
2024-08-26 17:16:53 -04:00
# if PLATFORM_WINDOWS
2024-01-09 18:26:51 -05:00
# if PLATFORM_CPU_ARM_FAMILY
const TCHAR * BinariesArch = TEXT ( " arm64 " ) ;
# else
const TCHAR * BinariesArch = TEXT ( " x64 " ) ;
# endif
2024-08-26 17:16:53 -04:00
return FPaths : : Combine ( FPaths : : EngineDir ( ) , TEXT ( " Binaries " ) , TEXT ( " Win64 " ) , TEXT ( " UnrealBuildAccelerator " ) , BinariesArch ) ;
# elif PLATFORM_MAC
return FPaths : : Combine ( FPaths : : EngineDir ( ) , TEXT ( " Binaries " ) , TEXT ( " Mac " ) , TEXT ( " UnrealBuildAccelerator " ) ) ;
# elif PLATFORM_LINUX
return FPaths : : Combine ( FPaths : : EngineDir ( ) , TEXT ( " Binaries " ) , TEXT ( " Linux " ) , TEXT ( " UnrealBuildAccelerator " ) ) ;
# else
# error Unsupported platform to compile UbaController plugin. Only Win64, Mac, and Linux are supported!
# endif
2024-01-09 18:26:51 -05:00
}
void FUbaControllerModule : : LoadDependencies ( )
{
const FString UbaBinariesPath = GetUbaBinariesPath ( ) ;
FPlatformProcess : : AddDllDirectory ( * UbaBinariesPath ) ;
FPlatformProcess : : GetDllHandle ( * ( FPaths : : Combine ( UbaBinariesPath , " UbaHost.dll " ) ) ) ;
}
void FUbaControllerModule : : StartupModule ( )
{
check ( ! bModuleInitialized ) ;
LoadDependencies ( ) ;
IModularFeatures : : Get ( ) . RegisterModularFeature ( GetModularFeatureType ( ) , this ) ;
bModuleInitialized = true ;
FCoreDelegates : : OnEnginePreExit . AddLambda ( [ & ] ( )
{
if ( bControllerInitialized & & JobDispatcherThread )
{
JobDispatcherThread - > Stop ( ) ;
FPlatformProcess : : ConditionalSleep ( [ & ] ( ) { return JobDispatcherThread & & JobDispatcherThread - > IsWorkDone ( ) ; } , 0.1f ) ;
JobDispatcherThread = nullptr ;
}
} ) ;
}
void FUbaControllerModule : : ShutdownModule ( )
{
check ( bModuleInitialized ) ;
IModularFeatures : : Get ( ) . UnregisterModularFeature ( GetModularFeatureType ( ) , this ) ;
if ( bControllerInitialized )
{
// Stop the jobs thread
if ( JobDispatcherThread )
{
JobDispatcherThread - > Stop ( ) ;
// Wait until the thread is done
FPlatformProcess : : ConditionalSleep ( [ & ] ( ) { return JobDispatcherThread & & JobDispatcherThread - > IsWorkDone ( ) ; } , 0.1f ) ;
}
FTask * Task ;
while ( PendingRequestedCompilationTasks . Dequeue ( Task ) )
{
FDistributedBuildTaskResult Result ;
Result . ReturnCode = 0 ;
Result . bCompleted = false ;
Task - > Promise . SetValue ( Result ) ;
delete Task ;
}
PendingRequestedCompilationTasks . Empty ( ) ;
}
CleanWorkingDirectory ( ) ;
bModuleInitialized = false ;
bControllerInitialized = false ;
}
void FUbaControllerModule : : InitializeController ( )
{
// We should never Initialize the controller twice
if ( ensureAlwaysMsgf ( ! bControllerInitialized , TEXT ( " Multiple initialization of UBA controller! " ) ) )
{
CleanWorkingDirectory ( ) ;
if ( IsSupported ( ) )
{
IFileManager : : Get ( ) . MakeDirectory ( * WorkingDirectory , true ) ;
// Pre-create the directories so we don't have to explicitly register them to uba later
for ( int32 It = 0 ; It ! = UbaControllerModule : : SubFolderCount ; + + It )
{
IFileManager : : Get ( ) . MakeDirectory ( * FString : : Printf ( TEXT ( " %s/%d " ) , * WorkingDirectory , It ) ) ;
}
2024-02-13 13:28:56 -05:00
if ( UbaControllerModule : : bDumpTraceFiles )
{
DebugInfoPath = UbaControllerModule : : MakeAndGetDebugInfoPath ( ) ;
}
2024-01-09 18:26:51 -05:00
JobDispatcherThread = MakeShared < FUbaJobProcessor > ( * this ) ;
JobDispatcherThread - > StartThread ( ) ;
}
bControllerInitialized = true ;
}
}
FString FUbaControllerModule : : CreateUniqueFilePath ( )
{
check ( bSupported ) ;
int32 ID = NextFileID + + ;
int32 FolderID = ID % UbaControllerModule : : SubFolderCount ; // We use sub folders to be nicer to file system (we can end up with 20000 files in one folder otherwise)
return FString : : Printf ( TEXT ( " %s/%d/%d.uba " ) , * WorkingDirectory , FolderID , ID ) ;
}
TFuture < FDistributedBuildTaskResult > FUbaControllerModule : : EnqueueTask ( const FTaskCommandData & CommandData )
{
check ( bSupported ) ;
TPromise < FDistributedBuildTaskResult > Promise ;
TFuture < FDistributedBuildTaskResult > Future = Promise . GetFuture ( ) ;
// Enqueue the new task
FTask * Task = new FTask ( NextTaskID + + , CommandData , MoveTemp ( Promise ) ) ;
{
PendingRequestedCompilationTasks . Enqueue ( Task ) ;
}
JobDispatcherThread - > HandleTaskQueueUpdated ( CommandData . InputFileName ) ;
return MoveTemp ( Future ) ;
}
2024-02-08 17:29:49 -05:00
bool FUbaControllerModule : : PollStats ( FDistributedBuildStats & OutStats )
{
return JobDispatcherThread ! = nullptr & & JobDispatcherThread - > PollStats ( OutStats ) ;
}
2024-01-09 18:26:51 -05:00
void FUbaControllerModule : : ReportJobProcessed ( const FTaskResponse & InTaskResponse , FTask * CompileTask )
{
if ( CompileTask )
{
FDistributedBuildTaskResult Result ;
Result . ReturnCode = InTaskResponse . ReturnCode ;
Result . bCompleted = true ;
CompileTask - > Promise . SetValue ( Result ) ;
delete CompileTask ;
}
}
UBACONTROLLER_API FUbaControllerModule & FUbaControllerModule : : Get ( )
{
return FModuleManager : : LoadModuleChecked < FUbaControllerModule > ( TEXT ( " UbaController " ) ) ;
}
IMPLEMENT_MODULE ( FUbaControllerModule , UbaController ) ;