2022-08-10 16:03:37 +00:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Threading.Tasks ;
using System.Xml ;
using EpicGames.Core ;
using OpenTracing ;
2024-05-22 13:17:22 -04:00
using UnrealBuildTool ;
2022-08-10 16:03:37 +00:00
namespace AutomationTool.Tasks
{
/// <summary>
/// Parameters for a compile task
/// </summary>
public class CompileTaskParameters
{
/// <summary>
/// The target to compile.
/// </summary>
2024-05-22 13:17:22 -04:00
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public string Target { get ; set ; }
2022-08-10 16:03:37 +00:00
/// <summary>
/// The configuration to compile.
/// </summary>
[TaskParameter]
2024-05-21 20:54:09 -04:00
public UnrealTargetConfiguration Configuration { get ; set ; }
2022-08-10 16:03:37 +00:00
/// <summary>
/// The platform to compile for.
/// </summary>
[TaskParameter]
2024-05-21 20:54:09 -04:00
public UnrealTargetPlatform Platform { get ; set ; }
2022-08-10 16:03:37 +00:00
/// <summary>
/// The project to compile with.
/// </summary>
[TaskParameter(Optional = true, ValidationType = TaskParameterValidationType.FileSpec)]
2024-05-21 20:54:09 -04:00
public string Project { get ; set ; }
2022-08-10 16:03:37 +00:00
/// <summary>
/// Additional arguments for UnrealBuildTool.
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public string Arguments { get ; set ; }
2022-08-10 16:03:37 +00:00
/// <summary>
/// Whether to allow using XGE for compilation.
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public bool AllowXGE { get ; set ; } = true ;
2022-08-10 16:03:37 +00:00
/// <summary>
2023-12-04 13:43:10 -05:00
/// No longer necessary as UnrealBuildTool is run to compile targets.
2022-08-10 16:03:37 +00:00
/// </summary>
[TaskParameter(Optional = true)]
2024-05-22 11:01:26 -04:00
[Obsolete("This setting is no longer used")]
2024-05-21 20:54:09 -04:00
public bool AllowParallelExecutor { get ; set ; } = true ;
2022-08-10 16:03:37 +00:00
2022-10-03 21:15:14 -04:00
/// <summary>
/// Whether to allow UBT to use all available cores, when AllowXGE is disabled.
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public bool AllowAllCores { get ; set ; } = false ;
2022-10-03 21:15:14 -04:00
2022-08-10 16:03:37 +00:00
/// <summary>
/// Whether to allow cleaning this target. If unspecified, targets are cleaned if the -Clean argument is passed on the command line.
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public bool? Clean { get ; set ; } = null ;
2022-08-10 16:03:37 +00:00
2024-03-25 18:31:19 -04:00
/// <summary>
/// Global flag passed to UBT that can be used to generate target files without fully compiling.
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public bool SkipBuild { get ; set ; } = false ;
2024-03-25 18:31:19 -04:00
2022-08-10 16:03:37 +00:00
/// <summary>
/// Tag to be applied to build products of this task.
/// </summary>
[TaskParameter(Optional = true, ValidationType = TaskParameterValidationType.TagList)]
2024-05-21 20:54:09 -04:00
public string Tag { get ; set ; }
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Executor for compile tasks, which can compile multiple tasks simultaneously
/// </summary>
public class CompileTaskExecutor : ITaskExecutor
{
/// <summary>
/// List of targets to compile. As well as the target specifically added for this task, additional compile tasks may be merged with it.
/// </summary>
2024-05-22 13:17:22 -04:00
readonly List < UnrealBuild . BuildTarget > _targets = new List < UnrealBuild . BuildTarget > ( ) ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// Mapping of receipt filename to its corresponding tag name
/// </summary>
2024-05-22 13:17:22 -04:00
readonly Dictionary < UnrealBuild . BuildTarget , string > _targetToTagName = new Dictionary < UnrealBuild . BuildTarget , string > ( ) ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// Whether to allow using XGE for this job
/// </summary>
2024-05-22 13:17:22 -04:00
bool _allowXge = true ;
2022-08-10 16:03:37 +00:00
2022-10-03 21:15:14 -04:00
/// <summary>
/// Whether to allow using all available cores for this job, when bAllowXGE is false
/// </summary>
2024-05-22 13:17:22 -04:00
bool _allowAllCores = false ;
2022-10-03 21:15:14 -04:00
2024-03-25 18:31:19 -04:00
/// <summary>
/// Should SkipBuild be passed to UBT so that only .target files are generated.
/// </summary>
2024-05-22 13:17:22 -04:00
bool _skipBuild = false ;
2024-03-25 18:31:19 -04:00
2022-08-10 16:03:37 +00:00
/// <summary>
/// Constructor
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="task">Initial task to execute</param>
public CompileTaskExecutor ( CompileTask task )
2022-08-10 16:03:37 +00:00
{
2024-05-22 13:17:22 -04:00
Add ( task ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Adds another task to this executor
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="task">Task to add</param>
2022-08-10 16:03:37 +00:00
/// <returns>True if the task could be added, false otherwise</returns>
2024-05-22 13:17:22 -04:00
public bool Add ( BgTaskImpl task )
2022-08-10 16:03:37 +00:00
{
2024-05-22 13:17:22 -04:00
CompileTask compileTask = task as CompileTask ;
if ( compileTask = = null )
2022-08-10 16:03:37 +00:00
{
return false ;
}
2024-05-22 13:17:22 -04:00
CompileTaskParameters parameters = compileTask . Parameters ;
if ( _targets . Count > 0 )
2022-08-10 16:03:37 +00:00
{
2024-05-22 13:17:22 -04:00
if ( _allowXge ! = parameters . AllowXGE
| | _skipBuild ! = parameters . SkipBuild )
2022-08-10 16:03:37 +00:00
{
return false ;
}
}
2024-03-25 18:31:19 -04:00
else
{
2024-05-22 13:17:22 -04:00
_allowXge = parameters . AllowXGE ;
_allowAllCores = parameters . AllowAllCores ;
_skipBuild = parameters . SkipBuild ;
2024-03-25 18:31:19 -04:00
}
2022-08-10 16:03:37 +00:00
2024-05-22 13:17:22 -04:00
_allowXge & = parameters . AllowXGE ;
_allowAllCores & = parameters . AllowAllCores ;
2022-08-10 16:03:37 +00:00
2024-05-22 13:17:22 -04:00
UnrealBuild . BuildTarget target = new UnrealBuild . BuildTarget { TargetName = parameters . Target , Platform = parameters . Platform , Config = parameters . Configuration , UprojectPath = compileTask . FindProjectFile ( ) , UBTArgs = ( parameters . Arguments ? ? "" ) , Clean = parameters . Clean } ;
if ( ! String . IsNullOrEmpty ( parameters . Tag ) )
2022-08-10 16:03:37 +00:00
{
2024-05-22 13:17:22 -04:00
_targetToTagName . Add ( target , parameters . Tag ) ;
2022-08-10 16:03:37 +00:00
}
2024-05-22 13:17:22 -04:00
_targets . Add ( target ) ;
2022-08-10 16:03:37 +00:00
return true ;
}
/// <summary>
2024-05-22 13:17:22 -04:00
/// ExecuteAsync all the tasks added to this executor.
2022-08-10 16:03:37 +00:00
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="job">Information about the current job</param>
/// <param name="buildProducts">Set of build products produced by this node.</param>
/// <param name="tagNameToFileSet">Mapping from tag names to the set of files they include</param>
2022-08-10 16:03:37 +00:00
/// <returns>Whether the task succeeded or not. Exiting with an exception will be caught and treated as a failure.</returns>
2024-05-22 13:17:22 -04:00
public Task ExecuteAsync ( JobContext job , HashSet < FileReference > buildProducts , Dictionary < string , HashSet < FileReference > > tagNameToFileSet )
2022-08-10 16:03:37 +00:00
{
// Create the agenda
2024-05-22 13:17:22 -04:00
UnrealBuild . BuildAgenda agenda = new UnrealBuild . BuildAgenda ( ) ;
agenda . Targets . AddRange ( _targets ) ;
2022-08-10 16:03:37 +00:00
// Build everything
2024-05-22 13:17:22 -04:00
Dictionary < UnrealBuild . BuildTarget , BuildManifest > targetToManifest = new Dictionary < UnrealBuild . BuildTarget , BuildManifest > ( ) ;
UnrealBuild builder = new UnrealBuild ( job . OwnerCommand ) ;
2022-08-10 16:03:37 +00:00
2024-05-22 13:17:22 -04:00
bool allCores = ( CommandUtils . IsBuildMachine | | _allowAllCores ) ; // Enable using all cores if this is a build agent or the flag was passed in to the task and XGE is disabled.
builder . Build ( agenda , InDeleteBuildProducts : null , InUpdateVersionFiles : false , InForceNoXGE : ! _allowXge , InAllCores : allCores , InTargetToManifest : targetToManifest , InSkipBuild : _skipBuild ) ;
2022-08-10 16:03:37 +00:00
2024-05-22 13:17:22 -04:00
UnrealBuild . CheckBuildProducts ( builder . BuildProductFiles ) ;
2022-08-10 16:03:37 +00:00
// Tag all the outputs
2024-05-22 13:17:22 -04:00
foreach ( KeyValuePair < UnrealBuild . BuildTarget , string > targetTagName in _targetToTagName )
2022-08-10 16:03:37 +00:00
{
2024-05-22 13:17:22 -04:00
BuildManifest manifest ;
if ( ! targetToManifest . TryGetValue ( targetTagName . Key , out manifest ) )
2022-08-10 16:03:37 +00:00
{
2024-05-22 13:17:22 -04:00
throw new AutomationException ( "Missing manifest for target {0} {1} {2}" , targetTagName . Key . TargetName , targetTagName . Key . Platform , targetTagName . Key . Config ) ;
2022-08-10 16:03:37 +00:00
}
2024-05-22 13:17:22 -04:00
HashSet < FileReference > manifestBuildProducts = manifest . BuildProducts . Select ( x = > new FileReference ( x ) ) . ToHashSet ( ) ;
2023-08-11 19:55:17 -04:00
// when we make a Mac/IOS build, Xcode will finalize the .app directory, adding files that UBT has no idea about, so now we recursively add any files in the .app
// as BuildProducts. look for any .apps that we have any files as BuildProducts, and expand to include all files in the .app
if ( BuildHostPlatform . Current . Platform = = UnrealTargetPlatform . Mac )
{
2024-05-22 13:17:22 -04:00
HashSet < string > appBundleLocations = new ( ) ;
foreach ( FileReference file in manifestBuildProducts )
2023-08-11 19:55:17 -04:00
{
// look for a ".app/" portion and chop off anything after it
2024-05-22 13:17:22 -04:00
int appLocation = file . FullName . IndexOf ( ".app/" , StringComparison . InvariantCultureIgnoreCase ) ;
if ( appLocation > 0 )
2023-08-11 19:55:17 -04:00
{
2024-05-22 13:17:22 -04:00
appBundleLocations . Add ( file . FullName . Substring ( 0 , appLocation + 4 ) ) ;
2023-08-11 19:55:17 -04:00
}
}
// now with a unique set of app bundles, add all files in them
2024-05-22 13:17:22 -04:00
foreach ( string appBundleLocation in appBundleLocations )
2023-08-11 19:55:17 -04:00
{
2024-05-22 13:17:22 -04:00
manifestBuildProducts . UnionWith ( DirectoryReference . EnumerateFiles ( new DirectoryReference ( appBundleLocation ) , "*" , System . IO . SearchOption . AllDirectories ) ) ;
2023-08-11 19:55:17 -04:00
}
}
2024-05-22 13:17:22 -04:00
foreach ( string tagName in CustomTask . SplitDelimitedList ( targetTagName . Value ) )
2022-08-10 16:03:37 +00:00
{
2024-05-22 13:17:22 -04:00
HashSet < FileReference > fileSet = CustomTask . FindOrAddTagSet ( tagNameToFileSet , tagName ) ;
fileSet . UnionWith ( manifestBuildProducts ) ;
2022-08-10 16:03:37 +00:00
}
}
// Add everything to the list of build products
2024-05-22 13:17:22 -04:00
buildProducts . UnionWith ( builder . BuildProductFiles . Select ( x = > new FileReference ( x ) ) ) ;
2022-08-10 16:03:37 +00:00
return Task . CompletedTask ;
}
2023-08-11 20:25:22 -04:00
}
2022-08-10 16:03:37 +00:00
/// <summary>
/// Compiles a target with UnrealBuildTool.
/// </summary>
[TaskElement("Compile", typeof(CompileTaskParameters))]
public class CompileTask : BgTaskImpl
{
/// <summary>
/// Parameters for this task
/// </summary>
2024-05-21 20:54:09 -04:00
public CompileTaskParameters Parameters { get ; set ; }
2022-08-10 16:03:37 +00:00
/// <summary>
/// Resolved path to Project file
/// </summary>
2024-05-21 20:54:09 -04:00
public FileReference ProjectFile { get ; set ; } = null ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// Construct a compile task
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="parameters">Parameters for this task</param>
public CompileTask ( CompileTaskParameters parameters )
2022-08-10 16:03:37 +00:00
{
2024-05-22 13:17:22 -04:00
this . Parameters = parameters ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Resolve the path to the project file
/// </summary>
public FileReference FindProjectFile ( )
{
2024-05-22 13:17:22 -04:00
FileReference projectFile = null ;
2022-08-10 16:03:37 +00:00
// Resolve the full path to the project file
2024-05-22 13:17:22 -04:00
if ( ! String . IsNullOrEmpty ( Parameters . Project ) )
2022-08-10 16:03:37 +00:00
{
2024-05-22 13:17:22 -04:00
if ( Parameters . Project . EndsWith ( ".uproject" , StringComparison . OrdinalIgnoreCase ) )
2022-08-10 16:03:37 +00:00
{
2024-05-22 13:17:22 -04:00
projectFile = CustomTask . ResolveFile ( Parameters . Project ) ;
2022-08-10 16:03:37 +00:00
}
else
{
2024-05-22 13:17:22 -04:00
projectFile = NativeProjects . EnumerateProjectFiles ( Log . Logger ) . FirstOrDefault ( x = > x . GetFileNameWithoutExtension ( ) . Equals ( Parameters . Project , StringComparison . OrdinalIgnoreCase ) ) ;
2022-08-10 16:03:37 +00:00
}
2024-05-22 13:17:22 -04:00
if ( projectFile = = null | | ! FileReference . Exists ( projectFile ) )
2022-08-10 16:03:37 +00:00
{
throw new BuildException ( "Unable to resolve project '{0}'" , Parameters . Project ) ;
}
}
2024-05-22 13:17:22 -04:00
return projectFile ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
2024-05-22 13:17:22 -04:00
/// ExecuteAsync the task.
2022-08-10 16:03:37 +00:00
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="job">Information about the current job</param>
/// <param name="buildProducts">Set of build products produced by this node.</param>
/// <param name="tagNameToFileSet">Mapping from tag names to the set of files they include</param>
public override Task ExecuteAsync ( JobContext job , HashSet < FileReference > buildProducts , Dictionary < string , HashSet < FileReference > > tagNameToFileSet )
2022-08-10 16:03:37 +00:00
{
//
// Don't do any logic here. You have to do it in the ctor or a getter
2023-12-04 13:43:10 -05:00
// otherwise you break the ITaskExecutor pathway, which doesn't call this function!
2022-08-10 16:03:37 +00:00
//
2024-05-22 13:17:22 -04:00
return GetExecutor ( ) . ExecuteAsync ( job , buildProducts , tagNameToFileSet ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public override ITaskExecutor GetExecutor ( )
{
return new CompileTaskExecutor ( this ) ;
}
/// <summary>
/// Get properties to include in tracing info
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="span">The span to add metadata to</param>
/// <param name="prefix">Prefix for all metadata keys</param>
public override void GetTraceMetadata ( ITraceSpan span , string prefix )
2022-08-10 16:03:37 +00:00
{
2024-05-22 13:17:22 -04:00
base . GetTraceMetadata ( span , prefix ) ;
2022-08-10 16:03:37 +00:00
2024-05-22 13:17:22 -04:00
span . AddMetadata ( prefix + "target.name" , Parameters . Target ) ;
span . AddMetadata ( prefix + "target.config" , Parameters . Configuration . ToString ( ) ) ;
span . AddMetadata ( prefix + "target.platform" , Parameters . Platform . ToString ( ) ) ;
2022-08-10 16:03:37 +00:00
if ( Parameters . Project ! = null )
{
2024-05-22 13:17:22 -04:00
span . AddMetadata ( prefix + "target.project" , Parameters . Project ) ;
2022-08-10 16:03:37 +00:00
}
}
2024-05-22 13:17:22 -04:00
2022-08-10 16:03:37 +00:00
/// <summary>
/// Get properties to include in tracing info
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="span">The span to add metadata to</param>
/// <param name="prefix">Prefix for all metadata keys</param>
public override void GetTraceMetadata ( ISpan span , string prefix )
2022-08-10 16:03:37 +00:00
{
2024-05-22 13:17:22 -04:00
base . GetTraceMetadata ( span , prefix ) ;
2022-08-10 16:03:37 +00:00
2024-05-22 13:17:22 -04:00
span . SetTag ( prefix + "target.name" , Parameters . Target ) ;
span . SetTag ( prefix + "target.config" , Parameters . Configuration . ToString ( ) ) ;
span . SetTag ( prefix + "target.platform" , Parameters . Platform . ToString ( ) ) ;
2022-08-10 16:03:37 +00:00
if ( Parameters . Project ! = null )
{
2024-05-22 13:17:22 -04:00
span . SetTag ( prefix + "target.project" , Parameters . Project ) ;
2022-08-10 16:03:37 +00:00
}
}
/// <summary>
/// Output this task out to an XML writer.
/// </summary>
2024-05-22 13:17:22 -04:00
public override void Write ( XmlWriter writer )
2022-08-10 16:03:37 +00:00
{
2024-05-22 13:17:22 -04:00
Write ( writer , Parameters ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Find all the tags which are used as inputs to this task
/// </summary>
/// <returns>The tag names which are read by this task</returns>
public override IEnumerable < string > FindConsumedTagNames ( )
{
yield break ;
}
/// <summary>
/// Find all the tags which are modified by this task
/// </summary>
/// <returns>The tag names which are modified by this task</returns>
public override IEnumerable < string > FindProducedTagNames ( )
{
return FindTagNamesFromList ( Parameters . Tag ) ;
}
}
public static partial class StandardTasks
{
/// <summary>
/// Compiles a target
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="target">The target to compile</param>
/// <param name="configuration">The configuration to compile</param>
/// <param name="platform">The platform to compile for</param>
/// <param name="project">The project to compile with</param>
/// <param name="arguments">Additional arguments for UnrealBuildTool</param>
/// <param name="allowXge">Whether to allow using XGE for compilation</param>
/// <param name="clean">Whether to allow cleaning this target. If unspecified, targets are cleaned if the -Clean argument is passed on the command line</param>
2022-08-10 16:03:37 +00:00
/// <returns>Build products from the compile</returns>
2024-05-22 13:17:22 -04:00
public static async Task < FileSet > CompileAsync ( string target , UnrealTargetPlatform platform , UnrealTargetConfiguration configuration , FileReference project = null , string arguments = null , bool allowXge = true , bool? clean = null )
2022-08-10 16:03:37 +00:00
{
2024-05-22 13:17:22 -04:00
CompileTaskParameters parameters = new CompileTaskParameters ( ) ;
parameters . Target = target ;
parameters . Platform = platform ;
parameters . Configuration = configuration ;
parameters . Project = project ? . FullName ;
parameters . Arguments = arguments ;
parameters . AllowXGE = allowXge ;
parameters . Clean = clean ;
return await ExecuteAsync ( new CompileTask ( parameters ) ) ;
2022-08-10 16:03:37 +00:00
}
}
}