// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using EpicGames.Core;
using UnrealBuildBase;
namespace UnrealBuildTool
{
///
/// Enumerates build action types.
///
enum ActionType
{
BuildProject,
GatherModuleDependencies,
CompileModuleInterface,
Compile,
CreateAppBundle,
GenerateDebugInfo,
Link,
WriteMetadata,
PostBuildStep,
ParseTimingInfo,
}
interface IExternalAction
{
///
/// The type of this action (for debugging purposes).
///
ActionType ActionType { get; }
///
/// Every file this action depends on. These files need to exist and be up to date in order for this action to even be considered
///
IEnumerable PrerequisiteItems { get; }
///
/// The files that this action produces after completing
///
IEnumerable ProducedItems { get; }
///
/// Items that should be deleted before running this action
///
IEnumerable DeleteItems { get; }
///
/// For C++ source files, specifies a dependency list file used to check changes to header files
///
FileItem? DependencyListFile { get; }
///
/// Directory from which to execute the program to create produced items
///
DirectoryReference WorkingDirectory { get; }
///
/// The command to run to create produced items
///
FileReference CommandPath { get; }
///
/// Command-line parameters to pass to the program
///
string CommandArguments { get; }
///
/// Version of the command used for this action. This will be considered a dependency.
///
string CommandVersion { get; }
///
/// Optional friendly description of the type of command being performed, for example "Compile" or "Link". Displayed by some executors.
///
string CommandDescription { get; }
///
/// Human-readable description of this action that may be displayed as status while invoking the action. This is often the name of the file being compiled, or an executable file name being linked. Displayed by some executors.
///
string StatusDescription { get; }
///
/// True if this action is allowed to be run on a remote machine when a distributed build system is being used, such as XGE
///
bool bCanExecuteRemotely { get; }
///
/// True if this action is allowed to be run on a remote machine with SNDBS. Files with #import directives must be compiled locally. Also requires bCanExecuteRemotely = true.
///
bool bCanExecuteRemotelyWithSNDBS { get; }
///
/// True if this action is using the GCC compiler. Some build systems may be able to optimize for this case.
///
bool bIsGCCCompiler { get; }
///
/// Whether we should log this action, whether executed locally or remotely. This is useful for actions that take time
/// but invoke tools without any console output.
///
bool bShouldOutputStatusDescription { get; }
///
/// True if any libraries produced by this action should be considered 'import libraries'
///
bool bProducesImportLibrary { get; }
///
/// Whether changes in the command line used to generate these produced items should invalidate the action
///
bool bUseActionHistory { get; }
}
///
/// A build action.
///
class Action : IExternalAction
{
///
/// Preparation and Assembly (serialized)
///
///
/// The type of this action (for debugging purposes).
///
public ActionType ActionType { get; set; }
///
/// Every file this action depends on. These files need to exist and be up to date in order for this action to even be considered
///
public List PrerequisiteItems { get; set; } = new List();
///
/// The files that this action produces after completing
///
public List ProducedItems { get; set; } = new List();
///
/// Items that should be deleted before running this action
///
public List DeleteItems { get; set; } = new List();
///
/// For C++ source files, specifies a dependency list file used to check changes to header files
///
public FileItem? DependencyListFile { get; set; }
///
/// Directory from which to execute the program to create produced items
///
public DirectoryReference WorkingDirectory { get; set; } = null!;
///
/// The command to run to create produced items
///
public FileReference CommandPath { get; set; } = null!;
///
/// Command-line parameters to pass to the program
///
public string CommandArguments { get; set; } = null!;
///
/// Version of the command used for this action. This will be considered a dependency.
///
public string CommandVersion { get; set; } = "0";
///
/// Optional friendly description of the type of command being performed, for example "Compile" or "Link". Displayed by some executors.
///
public string CommandDescription { get; set; } = null!;
///
/// Human-readable description of this action that may be displayed as status while invoking the action. This is often the name of the file being compiled, or an executable file name being linked. Displayed by some executors.
///
public string StatusDescription { get; set; } = "...";
///
/// True if this action is allowed to be run on a remote machine when a distributed build system is being used, such as XGE
///
public bool bCanExecuteRemotely { get; set; } = false;
///
/// True if this action is allowed to be run on a remote machine with SNDBS. Files with #import directives must be compiled locally. Also requires bCanExecuteRemotely = true.
///
public bool bCanExecuteRemotelyWithSNDBS { get; set; } = true;
///
/// True if this action is using the GCC compiler. Some build systems may be able to optimize for this case.
///
public bool bIsGCCCompiler { get; set; } = false;
///
/// Whether we should log this action, whether executed locally or remotely. This is useful for actions that take time
/// but invoke tools without any console output.
///
public bool bShouldOutputStatusDescription { get; set; } = true;
///
/// True if any libraries produced by this action should be considered 'import libraries'
///
public bool bProducesImportLibrary { get; set; } = false;
///
public bool bUseActionHistory { get; set; } = true;
IEnumerable IExternalAction.PrerequisiteItems => PrerequisiteItems;
IEnumerable IExternalAction.ProducedItems => ProducedItems;
IEnumerable IExternalAction.DeleteItems => DeleteItems;
public Action(ActionType InActionType)
{
ActionType = InActionType;
// link actions are going to run locally on SN-DBS so don't try to distribute them as that generates warnings for missing tool templates
if ( ActionType == ActionType.Link )
{
bCanExecuteRemotelyWithSNDBS = false;
}
}
public Action(IExternalAction InOther)
{
ActionType = InOther.ActionType;
PrerequisiteItems = new List(InOther.PrerequisiteItems);
ProducedItems = new List(InOther.ProducedItems);
DeleteItems = new List(InOther.DeleteItems);
DependencyListFile = InOther.DependencyListFile;
WorkingDirectory = InOther.WorkingDirectory;
CommandPath = InOther.CommandPath;
CommandArguments = InOther.CommandArguments;
CommandVersion = InOther.CommandVersion;
CommandDescription = InOther.CommandDescription;
StatusDescription = InOther.StatusDescription;
bCanExecuteRemotely = InOther.bCanExecuteRemotely;
bCanExecuteRemotelyWithSNDBS = InOther.bCanExecuteRemotelyWithSNDBS;
bIsGCCCompiler = InOther.bIsGCCCompiler;
bShouldOutputStatusDescription = InOther.bShouldOutputStatusDescription;
bProducesImportLibrary = InOther.bProducesImportLibrary;
bUseActionHistory = InOther.bUseActionHistory;
}
public Action(BinaryArchiveReader Reader)
{
ActionType = (ActionType)Reader.ReadByte();
WorkingDirectory = Reader.ReadDirectoryReferenceNotNull();
CommandPath = Reader.ReadFileReference();
CommandArguments = Reader.ReadString()!;
CommandVersion = Reader.ReadString()!;
CommandDescription = Reader.ReadString()!;
StatusDescription = Reader.ReadString()!;
bCanExecuteRemotely = Reader.ReadBool();
bCanExecuteRemotelyWithSNDBS = Reader.ReadBool();
bIsGCCCompiler = Reader.ReadBool();
bShouldOutputStatusDescription = Reader.ReadBool();
bProducesImportLibrary = Reader.ReadBool();
PrerequisiteItems = Reader.ReadList(() => Reader.ReadFileItem())!;
ProducedItems = Reader.ReadList(() => Reader.ReadFileItem())!;
DeleteItems = Reader.ReadList(() => Reader.ReadFileItem())!;
DependencyListFile = Reader.ReadFileItem();
bUseActionHistory = Reader.ReadBool();
}
///
/// ISerializable: Called when serialized to report additional properties that should be saved
///
public void Write(BinaryArchiveWriter Writer)
{
Writer.WriteByte((byte)ActionType);
Writer.WriteDirectoryReference(WorkingDirectory);
Writer.WriteFileReference(CommandPath);
Writer.WriteString(CommandArguments);
Writer.WriteString(CommandVersion);
Writer.WriteString(CommandDescription);
Writer.WriteString(StatusDescription);
Writer.WriteBool(bCanExecuteRemotely);
Writer.WriteBool(bCanExecuteRemotelyWithSNDBS);
Writer.WriteBool(bIsGCCCompiler);
Writer.WriteBool(bShouldOutputStatusDescription);
Writer.WriteBool(bProducesImportLibrary);
Writer.WriteList(PrerequisiteItems, Item => Writer.WriteFileItem(Item));
Writer.WriteList(ProducedItems, Item => Writer.WriteFileItem(Item));
Writer.WriteList(DeleteItems, Item => Writer.WriteFileItem(Item));
Writer.WriteFileItem(DependencyListFile);
Writer.WriteBool(bUseActionHistory);
}
///
/// Writes an action to a json file
///
/// The object to parse
public static Action ImportJson(JsonObject Object)
{
Action Action = new Action(Object.GetEnumField("Type"));
string? WorkingDirectory;
if(Object.TryGetStringField("WorkingDirectory", out WorkingDirectory))
{
Action.WorkingDirectory = new DirectoryReference(WorkingDirectory);
}
string? CommandPath;
if(Object.TryGetStringField("CommandPath", out CommandPath))
{
Action.CommandPath = new FileReference(CommandPath);
}
string? CommandArguments;
if(Object.TryGetStringField("CommandArguments", out CommandArguments))
{
Action.CommandArguments = CommandArguments;
}
string? CommandVersion;
if (Object.TryGetStringField("CommandVersion", out CommandVersion))
{
Action.CommandVersion = CommandVersion;
}
string? CommandDescription;
if(Object.TryGetStringField("CommandDescription", out CommandDescription))
{
Action.CommandDescription = CommandDescription;
}
string? StatusDescription;
if(Object.TryGetStringField("StatusDescription", out StatusDescription))
{
Action.StatusDescription = StatusDescription;
}
bool bCanExecuteRemotely;
if(Object.TryGetBoolField("bCanExecuteRemotely", out bCanExecuteRemotely))
{
Action.bCanExecuteRemotely = bCanExecuteRemotely;
}
bool bCanExecuteRemotelyWithSNDBS;
if(Object.TryGetBoolField("bCanExecuteRemotelyWithSNDBS", out bCanExecuteRemotelyWithSNDBS))
{
Action.bCanExecuteRemotelyWithSNDBS = bCanExecuteRemotelyWithSNDBS;
}
bool bIsGCCCompiler;
if(Object.TryGetBoolField("bIsGCCCompiler", out bIsGCCCompiler))
{
Action.bIsGCCCompiler = bIsGCCCompiler;
}
bool bShouldOutputStatusDescription;
if(Object.TryGetBoolField("bShouldOutputStatusDescription", out bShouldOutputStatusDescription))
{
Action.bShouldOutputStatusDescription = bShouldOutputStatusDescription;
}
bool bProducesImportLibrary;
if(Object.TryGetBoolField("bProducesImportLibrary", out bProducesImportLibrary))
{
Action.bProducesImportLibrary = bProducesImportLibrary;
}
string[]? PrerequisiteItems;
if (Object.TryGetStringArrayField("PrerequisiteItems", out PrerequisiteItems))
{
Action.PrerequisiteItems.AddRange(PrerequisiteItems.Select(x => FileItem.GetItemByPath(x)));
}
string[]? ProducedItems;
if (Object.TryGetStringArrayField("ProducedItems", out ProducedItems))
{
Action.ProducedItems.AddRange(ProducedItems.Select(x => FileItem.GetItemByPath(x)));
}
string[]? DeleteItems;
if (Object.TryGetStringArrayField("DeleteItems", out DeleteItems))
{
Action.DeleteItems.AddRange(DeleteItems.Select(x => FileItem.GetItemByPath(x)));
}
string? DependencyListFile;
if (Object.TryGetStringField("DependencyListFile", out DependencyListFile))
{
Action.DependencyListFile = FileItem.GetItemByPath(DependencyListFile);
}
return Action;
}
public override string ToString()
{
string ReturnString = "";
if (CommandPath != null)
{
ReturnString += CommandPath + " - ";
}
if (CommandArguments != null)
{
ReturnString += CommandArguments;
}
return ReturnString;
}
}
///
/// Extension methods for action classes
///
static class ActionExtensions
{
///
/// Writes an action to a json file
///
/// The action to write
/// Map of action to unique id
/// Writer to receive the output
public static void ExportJson(this LinkedAction Action, Dictionary LinkedActionToId, JsonWriter Writer)
{
Writer.WriteValue("Id", LinkedActionToId[Action]);
Writer.WriteEnumValue("Type", Action.ActionType);
Writer.WriteValue("WorkingDirectory", Action.WorkingDirectory.FullName);
Writer.WriteValue("CommandPath", Action.CommandPath.FullName);
Writer.WriteValue("CommandArguments", Action.CommandArguments);
Writer.WriteValue("CommandVersion", Action.CommandVersion);
Writer.WriteValue("CommandDescription", Action.CommandDescription);
Writer.WriteValue("StatusDescription", Action.StatusDescription);
Writer.WriteValue("bCanExecuteRemotely", Action.bCanExecuteRemotely);
Writer.WriteValue("bCanExecuteRemotelyWithSNDBS", Action.bCanExecuteRemotelyWithSNDBS);
Writer.WriteValue("bIsGCCCompiler", Action.bIsGCCCompiler);
Writer.WriteValue("bShouldOutputStatusDescription", Action.bShouldOutputStatusDescription);
Writer.WriteValue("bProducesImportLibrary", Action.bProducesImportLibrary);
Writer.WriteArrayStart("PrerequisiteActions");
foreach (LinkedAction PrerequisiteAction in Action.PrerequisiteActions)
{
Writer.WriteValue(LinkedActionToId[PrerequisiteAction]);
}
Writer.WriteArrayEnd();
Writer.WriteArrayStart("ProducedItems");
foreach (FileItem ProducedItem in Action.ProducedItems)
{
Writer.WriteValue(ProducedItem.AbsolutePath);
}
Writer.WriteArrayEnd();
Writer.WriteArrayStart("DeleteItems");
foreach (FileItem DeleteItem in Action.DeleteItems)
{
Writer.WriteValue(DeleteItem.AbsolutePath);
}
Writer.WriteArrayEnd();
if (Action.DependencyListFile != null)
{
Writer.WriteValue("DependencyListFile", Action.DependencyListFile.AbsolutePath);
}
}
}
///
/// Default serializer for instances
///
class DefaultActionSerializer : ActionSerializerBase
{
///
public override Action Read(BinaryArchiveReader Reader)
{
return new Action(Reader);
}
///
public override void Write(BinaryArchiveWriter Writer, Action Action)
{
Action.Write(Writer);
}
}
///
/// Information about an action queued to be executed
///
[DebuggerDisplay("{StatusDescription}")]
class LinkedAction : IExternalAction
{
///
/// The inner action instance
///
public IExternalAction Inner;
///
/// A target that this action contributes to
///
public TargetDescriptor? Target;
///
/// Set of other actions that this action depends on. This set is built when the action graph is linked.
///
public HashSet PrerequisiteActions = null!;
///
/// Total number of actions depending on this one.
///
public int NumTotalDependentActions = 0;
///
/// If set, will be output whenever the group differs to the last executed action. Set when executing multiple targets at once.
///
public List GroupNames = new List();
#region Wrapper implementation of IAction
public ActionType ActionType => Inner.ActionType;
public IEnumerable PrerequisiteItems => Inner.PrerequisiteItems;
public IEnumerable ProducedItems => Inner.ProducedItems;
public IEnumerable DeleteItems => Inner.DeleteItems;
public FileItem? DependencyListFile => Inner.DependencyListFile;
public DirectoryReference WorkingDirectory => Inner.WorkingDirectory;
public FileReference CommandPath => Inner.CommandPath;
public string CommandArguments => Inner.CommandArguments;
public string CommandVersion => Inner.CommandVersion;
public string CommandDescription => Inner.CommandDescription;
public string StatusDescription => Inner.StatusDescription;
public bool bCanExecuteRemotely => Inner.bCanExecuteRemotely;
public bool bCanExecuteRemotelyWithSNDBS => Inner.bCanExecuteRemotelyWithSNDBS;
public bool bIsGCCCompiler => Inner.bIsGCCCompiler;
public bool bShouldOutputStatusDescription => Inner.bShouldOutputStatusDescription;
public bool bProducesImportLibrary => Inner.bProducesImportLibrary;
public bool bUseActionHistory => Inner.bUseActionHistory;
#endregion
///
/// Constructor
///
/// The inner action instance
///
public LinkedAction(IExternalAction Inner, TargetDescriptor? Target)
{
this.Inner = Inner;
this.Target = Target;
}
///
/// Increment the number of dependents, recursively
///
/// Set of visited actions
public void IncrementDependentCount(HashSet VisitedActions)
{
if (VisitedActions.Add(this))
{
NumTotalDependentActions++;
foreach (LinkedAction PrerequisiteAction in PrerequisiteActions)
{
PrerequisiteAction.IncrementDependentCount(VisitedActions);
}
}
}
///
/// Compares two actions based on total number of dependent items, descending.
///
/// Action to compare
/// Action to compare
public static int Compare(LinkedAction A, LinkedAction B)
{
// Primary sort criteria is total number of dependent files, up to max depth.
if (B.NumTotalDependentActions != A.NumTotalDependentActions)
{
return Math.Sign(B.NumTotalDependentActions - A.NumTotalDependentActions);
}
// Secondary sort criteria is number of pre-requisites.
else
{
return Math.Sign(B.PrerequisiteItems.Count() - A.PrerequisiteItems.Count());
}
}
}
}