// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Diagnostics; using System.Linq; using System.Xml.Serialization; using System.ComponentModel; using System.Runtime.Serialization; namespace UnrealBuildTool { /// /// Type of a build product /// public enum BuildProductType { /// /// An executable file /// Executable, /// /// A dynamically loaded module. /// DynamicLibrary, /// /// A statically linked library. Not required for the executable to run. /// StaticLibrary, /// /// An import library. Not required for the executable to run. /// ImportLibrary, /// /// 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 file that was created as part of the build process /// [Serializable] public class BuildProduct { /// /// Path to the file. /// public string Path; /// /// Type of the build product. /// public BuildProductType Type; /// /// Whether the file is precompiled for use by downstream builds, but not directly used by the current target. /// public bool IsPrecompiled; /// /// Private constructor, for serialization. /// private BuildProduct() { } /// /// Constructor. /// /// Path to the build product /// Type of the build product public BuildProduct(string 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; IsPrecompiled = Other.IsPrecompiled; } /// /// Convert this object to a string, for debugging. /// /// Path to this build product public override string ToString() { return Path; } } /// /// 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 } /// /// 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 string Path; /// /// How to stage this file. /// public StagedFileType Type; /// /// Private constructor, for serialization. /// private RuntimeDependency() { } /// /// Constructor /// /// Path to the runtime dependency /// How to stage the given path public RuntimeDependency(string 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; } } /// /// 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(string 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 { /// /// The name of this target /// public string TargetName; /// /// Which platform the target is compiled for /// public UnrealTargetPlatform Platform; /// /// Which configuration this target is compiled in /// public UnrealTargetConfiguration Configuration; /// /// The unique ID for this build. /// public string BuildId; /// /// The changelist that this target was compiled with. /// public BuildVersion Version; /// /// 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 the files which are required to use precompiled binaries with this target /// public HashSet PrecompiledBuildDependencies = new HashSet(StringComparer.InvariantCultureIgnoreCase); /// /// All the files which are required runtime dependencies for precompiled binaries that are part of this target /// public HashSet PrecompiledRuntimeDependencies = new HashSet(StringComparer.InvariantCultureIgnoreCase); /// /// Additional build properties passed through from the module rules /// public List AdditionalProperties = new List(); /// /// Default constructor /// public TargetReceipt() { } /// /// Constructor /// /// The name of the target being compiled /// Platform for the target being compiled /// Configuration of the target being compiled /// The current unique build id /// Version information for the target public TargetReceipt(string InTargetName, UnrealTargetPlatform InPlatform, UnrealTargetConfiguration InConfiguration, string InBuildId, BuildVersion InVersion) { TargetName = InTargetName; Platform = InPlatform; Configuration = InConfiguration; BuildId = InBuildId; Version = InVersion; } /// /// Copy constructor /// /// Receipt to copy from public TargetReceipt(TargetReceipt Other) { foreach (BuildProduct OtherBuildProduct in Other.BuildProducts) { BuildProducts.Add(new BuildProduct(OtherBuildProduct)); } foreach (RuntimeDependency OtherRuntimeDependency in Other.RuntimeDependencies) { RuntimeDependencies.Add(new RuntimeDependency(OtherRuntimeDependency)); } AdditionalProperties.AddRange(Other.AdditionalProperties); PrecompiledBuildDependencies.UnionWith(Other.PrecompiledBuildDependencies); PrecompiledRuntimeDependencies.UnionWith(Other.PrecompiledRuntimeDependencies); } /// /// 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(string 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); } } PrecompiledBuildDependencies.UnionWith(Other.PrecompiledBuildDependencies); PrecompiledRuntimeDependencies.UnionWith(Other.PrecompiledRuntimeDependencies); } /// /// Expand all the path variables in the manifest /// /// Value for the $(EngineDir) variable /// Value for the $(ProjectDir) variable public void ExpandPathVariables(DirectoryReference EngineDir, DirectoryReference ProjectDir) { ExpandPathVariables(EngineDir, ProjectDir, new Dictionary()); } /// /// Expand all the path variables in the manifest, including a list of supplied variable values. /// /// Value for the $(EngineDir) variable /// Value for the $(ProjectDir) variable /// Other variables to expand from the manifest public void ExpandPathVariables(DirectoryReference EngineDir, DirectoryReference ProjectDir, IDictionary OtherVariables) { // Build a dictionary containing the standard variable expansions Dictionary Variables = new Dictionary(OtherVariables); Variables["EngineDir"] = EngineDir.FullName; Variables["ProjectDir"] = ProjectDir.FullName; // Replace all the variables in the paths foreach (BuildProduct BuildProduct in BuildProducts) { BuildProduct.Path = Utils.ExpandVariables(BuildProduct.Path, Variables); } foreach (RuntimeDependency RuntimeDependency in RuntimeDependencies) { RuntimeDependency.Path = Utils.ExpandVariables(RuntimeDependency.Path, Variables); } // Replace the variables in the precompiled dependencies PrecompiledBuildDependencies = new HashSet(PrecompiledBuildDependencies.Select(x => Utils.ExpandVariables(x, Variables)), StringComparer.InvariantCultureIgnoreCase); PrecompiledRuntimeDependencies = new HashSet(PrecompiledRuntimeDependencies.Select(x => Utils.ExpandVariables(x, Variables)), StringComparer.InvariantCultureIgnoreCase); } /// /// Inserts $(EngineDir) and $(ProjectDir) variables into a path string, so it can be used on different machines. /// /// Input path /// The engine directory. Relative paths are ok. /// The project directory. Relative paths are ok. /// New string with the base directory replaced, or the original string public static string InsertPathVariables(string InputPath, DirectoryReference EngineDir, DirectoryReference ProjectDir) { string Result = InputPath; if (InputPath != null && !InputPath.StartsWith("$(")) { Result = InsertPathVariables(new FileReference(InputPath), EngineDir, ProjectDir); } return Result; } /// /// 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. public static string InsertPathVariables(FileReference File, DirectoryReference EngineDir, DirectoryReference ProjectDir) { if (File.IsUnderDirectory(EngineDir)) { return "$(EngineDir)/" + File.MakeRelativeTo(EngineDir).Replace(Path.DirectorySeparatorChar, '/'); } else if (File.IsUnderDirectory(ProjectDir)) { return "$(ProjectDir)/" + File.MakeRelativeTo(ProjectDir).Replace(Path.DirectorySeparatorChar, '/'); } else { return File.FullName; } } /// /// 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 string GetDefaultPath(string BaseDir, string TargetName, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, string BuildArchitecture) { // Get the architecture suffix. Platforms have the option of overriding whether to include this string in filenames. string ArchitectureSuffix = ""; if(UEBuildPlatform.GetBuildPlatform(Platform).RequiresArchitectureSuffix()) { ArchitectureSuffix = BuildArchitecture; } // Build the output filename if (String.IsNullOrEmpty(ArchitectureSuffix) && Configuration == UnrealTargetConfiguration.Development) { return Path.Combine(BaseDir, "Binaries", Platform.ToString(), String.Format("{0}.target", TargetName)); } else { return Path.Combine(BaseDir, "Binaries", Platform.ToString(), String.Format("{0}-{1}-{2}{3}.target", TargetName, Platform.ToString(), Configuration.ToString(), ArchitectureSuffix)); } } /// /// Read a receipt from disk. /// /// Filename to read from public static TargetReceipt Read(string FileName) { JsonObject RawObject = JsonObject.Read(FileName); // Read the initial fields string TargetName = RawObject.GetStringField("TargetName"); UnrealTargetPlatform Platform = RawObject.GetEnumField("Platform"); UnrealTargetConfiguration Configuration = RawObject.GetEnumField("Configuration"); string BuildId = RawObject.GetStringField("BuildId"); // Try to read the build version BuildVersion Version; if (!BuildVersion.TryParse(RawObject.GetObjectField("Version"), out Version)) { throw new JsonParseException("Invalid 'Version' field"); } // Create the receipt TargetReceipt Receipt = new TargetReceipt(TargetName, Platform, Configuration, BuildId, Version); // 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)) { string Module; BuildProductObject.TryGetStringField("Module", out Module); BuildProduct NewBuildProduct = Receipt.AddBuildProduct(Path, Type); bool IsPrecompiled; if (BuildProductObject.TryGetBoolField("IsPrecompiled", out IsPrecompiled)) { NewBuildProduct.IsPrecompiled = IsPrecompiled; } } } } // 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)) { 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(Path, Type); } } } // 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)); } } } } // Read the precompiled dependencies string[] PrecompiledBuildDependencies; if(RawObject.TryGetStringArrayField("PrecompiledBuildDependencies", out PrecompiledBuildDependencies)) { Receipt.PrecompiledBuildDependencies.UnionWith(PrecompiledBuildDependencies); } // Read the precompiled dependencies string[] PrecompiledRuntimeDependencies; if(RawObject.TryGetStringArrayField("PrecompiledRuntimeDependencies", out PrecompiledRuntimeDependencies)) { Receipt.PrecompiledRuntimeDependencies.UnionWith(PrecompiledRuntimeDependencies); } 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(string FileName, out TargetReceipt Receipt) { if (!File.Exists(FileName)) { Receipt = null; return false; } try { Receipt = Read(FileName); return true; } catch (Exception) { Receipt = null; return false; } } /// /// Write the receipt to disk. /// /// Output filename public void Write(string FileName) { using (JsonWriter Writer = new JsonWriter(FileName)) { Writer.WriteObjectStart(); Writer.WriteValue("TargetName", TargetName); Writer.WriteValue("Platform", Platform.ToString()); Writer.WriteValue("Configuration", Configuration.ToString()); Writer.WriteValue("BuildId", BuildId); Writer.WriteObjectStart("Version"); Version.WriteProperties(Writer); Writer.WriteObjectEnd(); Writer.WriteArrayStart("BuildProducts"); foreach (BuildProduct BuildProduct in BuildProducts) { Writer.WriteObjectStart(); Writer.WriteValue("Path", BuildProduct.Path); Writer.WriteValue("Type", BuildProduct.Type.ToString()); if (BuildProduct.IsPrecompiled) { Writer.WriteValue("IsPrecompiled", BuildProduct.IsPrecompiled); } Writer.WriteObjectEnd(); } Writer.WriteArrayEnd(); Writer.WriteArrayStart("RuntimeDependencies"); foreach (RuntimeDependency RuntimeDependency in RuntimeDependencies) { Writer.WriteObjectStart(); Writer.WriteValue("Path", RuntimeDependency.Path); Writer.WriteValue("Type", RuntimeDependency.Type.ToString()); Writer.WriteObjectEnd(); } Writer.WriteArrayEnd(); 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(); } if(PrecompiledBuildDependencies.Count > 0) { Writer.WriteArrayStart("PrecompiledBuildDependencies"); foreach(string PrecompiledBuildDependency in PrecompiledBuildDependencies.OrderBy(x => x)) { Writer.WriteValue(PrecompiledBuildDependency); } Writer.WriteArrayEnd(); } if(PrecompiledRuntimeDependencies.Count > 0) { Writer.WriteArrayStart("PrecompiledRuntimeDependencies"); foreach(string PrecompiledRuntimeDependency in PrecompiledRuntimeDependencies.OrderBy(x => x)) { Writer.WriteValue(PrecompiledRuntimeDependency); } Writer.WriteArrayEnd(); } Writer.WriteObjectEnd(); } } } }