// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Xml.Serialization;
using EpicGames.Core;
using UnrealBuildBase;
namespace UnrealBuildTool
{
///
/// Type of a build product
///
public enum BuildProductType
{
///
/// An executable file
///
Executable,
///
/// A dynamically loaded module.
///
DynamicLibrary,
///
/// A symbol file. Not required for the executable to run.
///
SymbolFile,
///
/// A map file. Not required for the executable to run.
///
MapFile,
///
/// A resource file which was generated by the build and is required for the executable to run.
///
RequiredResource,
///
/// A build resource which was generated by the build, but is not required for the executable to run.
///
BuildResource,
///
/// A package which can be deployed on device (eg. *.apk for Android, *.stub for iOS)
///
Package
}
///
/// A file that was created as part of the build process
///
[Serializable]
public class BuildProduct
{
///
/// Path to the file.
///
public FileReference Path;
///
/// Type of the build product.
///
public BuildProductType Type;
///
/// Private constructor, for serialization.
///
private BuildProduct()
{
Path = null!;
}
///
/// Constructor.
///
/// Path to the build product
/// Type of the build product
public BuildProduct(FileReference InPath, BuildProductType InType)
{
Path = InPath;
Type = InType;
}
///
/// Copy constructor.
///
/// Build product to copy settings from
public BuildProduct(BuildProduct Other)
{
Path = Other.Path;
Type = Other.Type;
}
///
/// Convert this object to a string, for debugging.
///
/// Path to this build product
public override string ToString()
{
return Path.ToString();
}
}
///
/// How a file may be staged
///
public enum StagedFileType
{
///
/// Only accessed through Unreal filesystem functions; may be included in a PAK file.
///
UFS,
///
/// Must be kept as part of the loose filesystem.
///
NonUFS,
///
/// Debug file which must be kept as part of the loose filesystem.
///
DebugNonUFS,
///
/// System file which must be kept as part of the loose filesystem. System files are not subject to being automatic remapping or renaming by the platform layer.
///
SystemNonUFS,
}
///
/// Information about a file which is required by the target at runtime, and must be moved around with it.
///
[Serializable]
public class RuntimeDependency
{
///
/// The file that should be staged. Should use $(EngineDir) and $(ProjectDir) variables as a root, so that the target can be relocated to different machines.
///
public FileReference Path;
///
/// How to stage this file.
///
public StagedFileType Type;
///
/// Private constructor, for serialization.
///
private RuntimeDependency()
{
Path = null!;
}
///
/// Constructor
///
/// Path to the runtime dependency
/// How to stage the given path
public RuntimeDependency(FileReference InPath, StagedFileType InType = StagedFileType.NonUFS)
{
Path = InPath;
Type = InType;
}
///
/// Copy constructor
///
/// Runtime dependency to copy settings from
public RuntimeDependency(RuntimeDependency InOther)
{
Path = InOther.Path;
Type = InOther.Type;
}
///
/// Convert this object to a string for debugging
///
/// String representation of the object
public override string ToString()
{
return Path.ToString();
}
}
///
/// List of runtime dependencies, with convenience methods for adding new items
///
[Serializable]
public class RuntimeDependencyList : List
{
///
/// Default constructor
///
public RuntimeDependencyList()
{
}
///
/// Copy constructor
///
/// Sequence of runtime dependencies to initialize with
public RuntimeDependencyList(IEnumerable Other) : base(Other)
{
}
///
/// Add a runtime dependency to the list
///
/// Path to the runtime dependency. May include wildcards.
/// How to stage this file
public void Add(FileReference InPath, StagedFileType InType)
{
Add(new RuntimeDependency(InPath, InType));
}
}
///
/// Arbitrary property name/value which metadata from the build scripts can be passed on to downstream tasks
///
[Serializable]
public class ReceiptProperty
{
///
/// Property name
///
[XmlAttribute]
public string Name;
///
/// Value of the property
///
[XmlAttribute]
public string Value;
///
/// Construct a property with the given name and value
///
/// Name of the property
/// Value of the property
public ReceiptProperty(string InName, string InValue)
{
Name = InName;
Value = InValue;
}
}
///
/// Stores a record of a built target, with all metadata that other tools may need to know about the build.
///
[Serializable]
public class TargetReceipt
{
///
/// Path to the project file for this target
///
public FileReference? ProjectFile;
///
/// The project directory
///
public DirectoryReference? ProjectDir;
///
/// The name of this target
///
public string TargetName;
///
/// Which platform the target is compiled for
///
public UnrealTargetPlatform Platform;
///
/// Which platform the target is compiled for
///
public UnrealArchitectures Architectures;
///
/// Which configuration this target is compiled in
///
public UnrealTargetConfiguration Configuration;
///
/// The type of the target
///
public TargetType TargetType;
///
/// Whether it's a low level tests target
///
public bool IsTestTarget;
///
/// Version information for this target.
///
public BuildVersion Version;
///
/// The exectuable to launch for this target
///
public FileReference? Launch;
///
/// The console subsystem/commmandlet exectuable to launch for this target
///
public FileReference? LaunchCmd;
///
/// The build products which are part of this target
///
public List BuildProducts = new List();
///
/// All the runtime dependencies that this target relies on
///
public RuntimeDependencyList RuntimeDependencies = new RuntimeDependencyList();
///
/// All plugins that were either enabled or disabled via the target rules.
///
public Dictionary PluginNameToEnabledState = new Dictionary();
///
/// All plugins that were built via the target rules.
///
public List BuildPlugins = new List();
///
/// Additional build properties passed through from the module rules
///
public List AdditionalProperties = new List();
///
/// Constructor
///
/// Path to the project file for this target
/// The name of the target being compiled
/// The type of target
/// Platform for the target being compiled
/// Configuration of the target being compiled
/// Version information for the target
/// Architecture information for the target
/// Whether it's a target for low level tests
public TargetReceipt(FileReference? InProjectFile, string InTargetName, TargetType InTargetType, UnrealTargetPlatform InPlatform, UnrealTargetConfiguration InConfiguration, BuildVersion InVersion, UnrealArchitectures InArchitectures, bool InIsTestTarget)
{
ProjectFile = InProjectFile;
ProjectDir = DirectoryReference.FromFile(InProjectFile);
TargetName = InTargetName;
Platform = InPlatform;
Configuration = InConfiguration;
TargetType = InTargetType;
Version = InVersion;
Architectures = InArchitectures;
IsTestTarget = InIsTestTarget;
}
///
/// Adds a build product to the receipt. Does not check whether it already exists.
///
/// Path to the build product.
/// Type of build product.
/// The BuildProduct object that was created
public BuildProduct AddBuildProduct(FileReference Path, BuildProductType Type)
{
BuildProduct NewBuildProduct = new BuildProduct(Path, Type);
BuildProducts.Add(NewBuildProduct);
return NewBuildProduct;
}
///
/// Merges another receipt to this one.
///
/// Receipt which should be merged
public void Merge(TargetReceipt Other)
{
foreach (BuildProduct OtherBuildProduct in Other.BuildProducts)
{
BuildProducts.Add(OtherBuildProduct);
}
foreach (RuntimeDependency OtherRuntimeDependency in Other.RuntimeDependencies)
{
if (!RuntimeDependencies.Any(x => x.Path == OtherRuntimeDependency.Path))
{
RuntimeDependencies.Add(OtherRuntimeDependency);
}
}
foreach (KeyValuePair Pair in Other.PluginNameToEnabledState)
{
if (!PluginNameToEnabledState.ContainsKey(Pair.Key))
{
PluginNameToEnabledState.Add(Pair.Key, Pair.Value);
}
}
foreach (string PluginName in Other.BuildPlugins)
{
if (!BuildPlugins.Contains(PluginName))
{
BuildPlugins.Add(PluginName);
}
}
}
///
/// Inserts variables to make a file relative to $(EngineDir) or $(ProjectDir)
///
/// The file to insert variables into.
/// Value of the $(EngineDir) variable.
/// Value of the $(ProjectDir) variable.
/// Converted path for the file.
static string InsertPathVariables(FileReference File, DirectoryReference EngineDir, DirectoryReference? ProjectDir)
{
if (File.IsUnderDirectory(EngineDir))
{
return "$(EngineDir)/" + File.MakeRelativeTo(EngineDir).Replace(Path.DirectorySeparatorChar, '/');
}
else if (ProjectDir != null && File.IsUnderDirectory(ProjectDir))
{
return "$(ProjectDir)/" + File.MakeRelativeTo(ProjectDir).Replace(Path.DirectorySeparatorChar, '/');
}
else
{
return File.FullName;
}
}
///
/// Inserts variables to make a file relative to $(EngineDir) or $(ProjectDir)
///
/// The path to insert variables into.
/// Value of the $(EngineDir) variable.
/// Value of the $(ProjectDir) variable.
/// Converted path for the file.
static FileReference ExpandPathVariables(string Path, DirectoryReference EngineDir, DirectoryReference? ProjectDir)
{
const string EnginePrefix = "$(EngineDir)";
if (Path.StartsWith(EnginePrefix, StringComparison.InvariantCultureIgnoreCase))
{
return new FileReference(EngineDir.FullName + Path.Substring(EnginePrefix.Length));
}
const string ProjectPrefix = "$(ProjectDir)";
if (ProjectDir != null && Path.StartsWith(ProjectPrefix, StringComparison.InvariantCultureIgnoreCase))
{
return new FileReference(ProjectDir.FullName + Path.Substring(ProjectPrefix.Length));
}
return new FileReference(Path);
}
///
/// Returns the standard path to the build receipt for a given target
///
/// Base directory for the target being built; either the project directory or engine directory.
/// The target being built
/// The target platform
/// The target configuration
/// The architecture being built
/// Path to the receipt for this target
public static FileReference GetDefaultPath(DirectoryReference BaseDir, string TargetName, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, UnrealArchitectures? BuildArchitectures)
{
// Get the architecture suffix. Platforms have the option of overriding whether to include this string in filenames.
string ArchitectureSuffix = "";
// @todo disallow null here?
if (BuildArchitectures != null && UnrealArchitectureConfig.ForPlatform(Platform).RequiresArchitectureFilenames(BuildArchitectures))
{
ArchitectureSuffix = BuildArchitectures.ToString();
}
// Build the output filename
if (String.IsNullOrEmpty(ArchitectureSuffix) && Configuration == UnrealTargetConfiguration.Development)
{
return FileReference.Combine(BaseDir, "Binaries", Platform.ToString(), String.Format("{0}.target", TargetName));
}
else
{
return FileReference.Combine(BaseDir, "Binaries", Platform.ToString(), String.Format("{0}-{1}-{2}{3}.target", TargetName, Platform.ToString(), Configuration.ToString(), ArchitectureSuffix));
}
}
///
/// Determine whether a BuildProduct Type is NonUFS or DebugNonUFS
///
/// The build product to check
public static StagedFileType GetStageTypeFromBuildProductType(BuildProduct BuildProduct)
{
if (BuildProduct.Type == BuildProductType.SymbolFile || BuildProduct.Type == BuildProductType.MapFile)
{
return StagedFileType.DebugNonUFS;
}
return StagedFileType.NonUFS;
}
///
/// Checks the Additional properties for one with the given name that matches the given value
///
/// Property name to search for
/// Value to compare against (with StringComparison.InvariantCultureIgnoreCase)
/// True if any property with PropertyName has a value matching Value
public bool HasValueForAdditionalProperty(string PropertyName, string Value)
{
// get all properties with the given name?
IEnumerable Results = AdditionalProperties.Where(x => x.Name == PropertyName);
foreach (ReceiptProperty Property in Results)
{
// does the property value match?
if (Property.Value.Equals(Value, StringComparison.InvariantCultureIgnoreCase))
{
return true;
}
}
return false;
}
///
/// Read a receipt from disk.
///
/// Filename to read from
public static TargetReceipt Read(FileReference Location)
{
return Read(Location, Unreal.EngineDirectory);
}
///
/// Read a receipt from disk.
///
/// Filename to read from
/// Engine directory for expanded variables
public static TargetReceipt Read(FileReference Location, DirectoryReference EngineDir)
{
JsonObject RawObject = JsonObject.Read(Location);
// Read the initial fields
string TargetName = RawObject.GetStringField("TargetName");
TargetType TargetType = RawObject.GetEnumField("TargetType");
UnrealTargetPlatform Platform = UnrealTargetPlatform.Parse(RawObject.GetStringField("Platform"));
UnrealTargetConfiguration Configuration = RawObject.GetEnumField("Configuration");
// Try to read the build version
BuildVersion? Version;
if (!BuildVersion.TryParse(RawObject.GetObjectField("Version"), out Version))
{
throw new JsonException("Invalid 'Version' field", Location.FullName, null, null);
}
// Read the project path
FileReference? ProjectFile;
string? RelativeProjectFile;
if (RawObject.TryGetStringField("Project", out RelativeProjectFile))
{
ProjectFile = FileReference.Combine(Location.Directory, RelativeProjectFile);
}
else
{
ProjectFile = null;
}
// Read the launch executable
string? Architecture;
UnrealArchitectures Architectures;
if (RawObject.TryGetStringField("Architecture", out Architecture))
{
Architectures = UnrealArchitectures.FromString(Architecture, Platform)!;
}
else
{
// @todo this doesn't necessarily match how it was compiled - should this be an error case?
Architectures = UnrealArchitectureConfig.ForPlatform(Platform).ActiveArchitectures(ProjectFile, TargetName);
}
bool IsTestTarget;
RawObject.TryGetBoolField("IsTestTarget", out IsTestTarget);
// Create the receipt
TargetReceipt Receipt = new TargetReceipt(ProjectFile, TargetName, TargetType, Platform, Configuration, Version, Architectures, IsTestTarget);
// Get the project directory
DirectoryReference? ProjectDir = Receipt.ProjectDir;
// Read the launch executable
string? Launch;
if (RawObject.TryGetStringField("Launch", out Launch))
{
Receipt.Launch = ExpandPathVariables(Launch, EngineDir, ProjectDir);
}
string? LaunchCmd;
if (RawObject.TryGetStringField("LaunchCmd", out LaunchCmd))
{
Receipt.LaunchCmd = ExpandPathVariables(LaunchCmd, EngineDir, ProjectDir);
}
// Read the build products
JsonObject[]? BuildProductObjects;
if (RawObject.TryGetObjectArrayField("BuildProducts", out BuildProductObjects))
{
foreach (JsonObject BuildProductObject in BuildProductObjects)
{
string? Path;
BuildProductType Type;
if (BuildProductObject.TryGetStringField("Path", out Path) && BuildProductObject.TryGetEnumField("Type", out Type))
{
FileReference File = ExpandPathVariables(Path, EngineDir, ProjectDir);
string? Module;
BuildProductObject.TryGetStringField("Module", out Module);
Receipt.AddBuildProduct(File, Type);
}
}
}
// Read the runtime dependencies
JsonObject[]? RuntimeDependencyObjects;
if (RawObject.TryGetObjectArrayField("RuntimeDependencies", out RuntimeDependencyObjects))
{
foreach (JsonObject RuntimeDependencyObject in RuntimeDependencyObjects)
{
string? Path;
if (RuntimeDependencyObject.TryGetStringField("Path", out Path))
{
FileReference File = ExpandPathVariables(Path, EngineDir, ProjectDir);
StagedFileType Type;
if (!RuntimeDependencyObject.TryGetEnumField("Type", out Type))
{
// Previous format included an optional IgnoreIfMissing flag, which was only used for debug files. We can explicitly reference them as DebugNonUFS files now.
bool bIgnoreIfMissing;
if (RuntimeDependencyObject.TryGetBoolField("IgnoreIfMissing", out bIgnoreIfMissing))
{
bIgnoreIfMissing = false;
}
Type = bIgnoreIfMissing ? StagedFileType.DebugNonUFS : StagedFileType.NonUFS;
}
Receipt.RuntimeDependencies.Add(File, Type);
}
}
}
// Read the enabled/disabled plugins
JsonObject[]? PluginObjects;
if (RawObject.TryGetObjectArrayField("Plugins", out PluginObjects))
{
foreach (JsonObject PluginObject in PluginObjects)
{
string? PluginName;
if (PluginObject.TryGetStringField("Name", out PluginName))
{
bool PluginEnabled;
if (PluginObject.TryGetBoolField("Enabled", out PluginEnabled))
{
Receipt.PluginNameToEnabledState.Add(PluginName, PluginEnabled);
}
}
}
}
// Read the build plugins
string[]? BuildPlugins;
if (RawObject.TryGetStringArrayField("BuildPlugins", out BuildPlugins))
{
Receipt.BuildPlugins.AddAll(BuildPlugins);
}
// Read the additional properties
JsonObject[]? AdditionalPropertyObjects;
if (RawObject.TryGetObjectArrayField("AdditionalProperties", out AdditionalPropertyObjects))
{
foreach (JsonObject AdditionalPropertyObject in AdditionalPropertyObjects)
{
string? Name;
if (AdditionalPropertyObject.TryGetStringField("Name", out Name))
{
string? Value;
if (AdditionalPropertyObject.TryGetStringField("Value", out Value))
{
Receipt.AdditionalProperties.Add(new ReceiptProperty(Name, Value));
}
}
}
}
return Receipt;
}
///
/// Try to read a receipt from disk, failing gracefully if it can't be read.
///
/// Filename to read from
/// If successful, the receipt that was read
/// True if successful
public static bool TryRead(FileReference Location, [NotNullWhen(true)] out TargetReceipt? Receipt)
{
return TryRead(Location, Unreal.EngineDirectory, out Receipt);
}
///
/// Try to read a receipt from disk, failing gracefully if it can't be read.
///
/// Filename to read from
/// Engine directory for expanded paths
/// If successful, the receipt that was read
/// True if successful
public static bool TryRead(FileReference Location, DirectoryReference EngineDir, [NotNullWhen(true)] out TargetReceipt? Receipt)
{
if (!FileReference.Exists(Location))
{
Receipt = null;
return false;
}
try
{
Receipt = Read(Location, EngineDir);
return true;
}
catch (Exception)
{
Receipt = null;
return false;
}
}
///
/// Write the receipt to disk.
///
/// Output filename
public void Write(FileReference Location)
{
Write(Location, Unreal.EngineDirectory);
}
///
/// Write the receipt to disk.
///
/// Output filename
/// Engine directory for expanded paths
public void Write(FileReference Location, DirectoryReference EngineDir)
{
using (JsonWriter Writer = new JsonWriter(Location.FullName))
{
Writer.WriteObjectStart();
Writer.WriteValue("TargetName", TargetName);
Writer.WriteValue("Platform", Platform.ToString());
Writer.WriteValue("Configuration", Configuration.ToString());
Writer.WriteValue("TargetType", TargetType.ToString());
Writer.WriteValue("IsTestTarget", IsTestTarget);
Writer.WriteValue("Architecture", Architectures.ToString());
if (ProjectFile != null)
{
Writer.WriteValue("Project", ProjectFile.MakeRelativeTo(Location.Directory).Replace(Path.DirectorySeparatorChar, '/'));
}
if (Launch != null)
{
Writer.WriteValue("Launch", InsertPathVariables(Launch, EngineDir, ProjectDir));
}
if (LaunchCmd != null)
{
Writer.WriteValue("LaunchCmd", InsertPathVariables(LaunchCmd, EngineDir, ProjectDir));
}
Writer.WriteObjectStart("Version");
Version.WriteProperties(Writer);
Writer.WriteObjectEnd();
Writer.WriteArrayStart("BuildProducts");
foreach (BuildProduct BuildProduct in BuildProducts.OrderBy(x => x.Path.FullName))
{
Writer.WriteObjectStart();
Writer.WriteValue("Path", InsertPathVariables(BuildProduct.Path, EngineDir, ProjectDir));
Writer.WriteValue("Type", BuildProduct.Type.ToString());
Writer.WriteObjectEnd();
}
Writer.WriteArrayEnd();
Writer.WriteArrayStart("RuntimeDependencies");
foreach (RuntimeDependency RuntimeDependency in RuntimeDependencies.OrderBy(x => x.Path.FullName))
{
Writer.WriteObjectStart();
Writer.WriteValue("Path", InsertPathVariables(RuntimeDependency.Path, EngineDir, ProjectDir));
Writer.WriteValue("Type", RuntimeDependency.Type.ToString());
Writer.WriteObjectEnd();
}
Writer.WriteArrayEnd();
if (PluginNameToEnabledState.Count > 0)
{
Writer.WriteArrayStart("Plugins");
foreach (KeyValuePair PluginNameToEnabledStatePair in PluginNameToEnabledState.OrderBy(x => x.Key))
{
Writer.WriteObjectStart();
Writer.WriteValue("Name", PluginNameToEnabledStatePair.Key);
Writer.WriteValue("Enabled", PluginNameToEnabledStatePair.Value);
Writer.WriteObjectEnd();
}
Writer.WriteArrayEnd();
}
if (BuildPlugins.Count > 0)
{
Writer.WriteStringArrayField("BuildPlugins", BuildPlugins.OrderBy(x => x));
}
if (AdditionalProperties.Count > 0)
{
Writer.WriteArrayStart("AdditionalProperties");
foreach (ReceiptProperty AdditionalProperty in AdditionalProperties)
{
Writer.WriteObjectStart();
Writer.WriteValue("Name", AdditionalProperty.Name);
Writer.WriteValue("Value", AdditionalProperty.Value);
Writer.WriteObjectEnd();
}
Writer.WriteArrayEnd();
}
Writer.WriteObjectEnd();
}
}
}
}