// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using EpicGames.Core;
using Microsoft.Extensions.Logging;
using UnrealBuildBase;
namespace UnrealBuildTool
{
///
/// All binary types generated by UBT
///
enum UEBuildBinaryType
{
///
/// An executable
///
Executable,
///
/// A dynamic library (.dll, .dylib, or .so)
///
DynamicLinkLibrary,
///
/// A static library (.lib or .a)
///
StaticLibrary,
///
/// Object files
///
Object,
///
/// Precompiled header
///
PrecompiledHeader,
}
///
/// A binary built by UBT.
///
class UEBuildBinary
{
///
/// The type of binary to build
///
public UEBuildBinaryType Type;
///
/// Output directory for this binary
///
public DirectoryReference OutputDir;
///
/// The output file path. This must be set before a binary can be built using it.
///
public List OutputFilePaths;
///
/// Returns the OutputFilePath if there is only one entry in OutputFilePaths
///
public FileReference OutputFilePath
{
get
{
if (OutputFilePaths.Count != 1)
{
throw new BuildException("Attempted to use UEBuildBinaryConfiguration.OutputFilePath property, but there are multiple (or no) OutputFilePaths. You need to handle multiple in the code that called this (size = {0})", OutputFilePaths.Count);
}
return OutputFilePaths[0];
}
}
///
/// The intermediate directory for this binary. Modules should create separate intermediate directories below this. Must be set before a binary can be built using it.
///
public DirectoryReference IntermediateDirectory;
///
/// If true, build exports lib
///
public bool bAllowExports = false;
///
/// If true, create a separate import library
///
public bool bCreateImportLibrarySeparately = false;
///
/// If true, creates an additional console application. Hack for Windows, where it's not possible to conditionally inherit a parent's console Window depending on how
/// the application is invoked; you have to link the same executable with a different subsystem setting.
///
public bool bBuildAdditionalConsoleApp = false;
///
/// If true, replaces the executable with a console application. Hack for Windows, where it's not possible to conditionally inherit a parent's console Window depending on how
/// the application is invoked
///
public bool bBuildConsoleAppOnly = false;
///
///
///
public bool bUsePrecompiled;
///
/// The primary module that this binary was constructed for. For executables, this is typically the launch module.
///
public readonly UEBuildModuleCPP PrimaryModule;
///
/// List of modules to link together into this executable
///
public readonly List Modules = new List();
///
/// Cached list of dependent link libraries.
///
private List? DependentLinkLibraries;
///
/// Expanded list of runtime dependencies generated by calling PrepareRuntimeDependencies.
///
public readonly List RuntimeDependencies = new List();
///
/// If compiled objects will have unused exports stripped, only affects objects compiled into modular libraries.
///
public bool bStripUnusedExports = false;
///
/// List of all objects that would be linked.
///
public readonly HashSet InputObjects = new();
///
/// Create an instance of the class with the given configuration data
///
///
///
///
///
///
///
///
///
public UEBuildBinary(
UEBuildBinaryType Type,
IEnumerable OutputFilePaths,
DirectoryReference IntermediateDirectory,
bool bAllowExports,
bool bBuildAdditionalConsoleApp,
bool bBuildConsoleAppOnly,
UEBuildModuleCPP PrimaryModule,
bool bUsePrecompiled
)
{
this.Type = Type;
OutputDir = OutputFilePaths.First().Directory;
this.OutputFilePaths = new List(OutputFilePaths);
this.IntermediateDirectory = IntermediateDirectory;
this.bAllowExports = bAllowExports;
this.bBuildAdditionalConsoleApp = bBuildAdditionalConsoleApp;
this.bBuildConsoleAppOnly = bBuildConsoleAppOnly;
this.PrimaryModule = PrimaryModule;
this.bUsePrecompiled = bUsePrecompiled;
PrimaryModule.bCreatePerModuleFile = true;
Modules.Add(PrimaryModule);
}
///
/// Creates all the modules referenced by this target.
///
public void CreateAllDependentModules(UEBuildModule.CreateModuleDelegate CreateModule, ILogger Logger)
{
foreach (UEBuildModule Module in Modules)
{
Module.RecursivelyCreateModules(CreateModule, "Target", Logger);
}
}
///
/// Expands all of the runtime dependencies.
///
/// Output directory for the executable
public void PrepareRuntimeDependencies(DirectoryReference ExeDir)
{
RuntimeDependencies.Clear();
foreach (UEBuildModule Module in Modules)
{
foreach (ModuleRules.RuntimeDependency Dependency in Module.Rules.RuntimeDependencies.Inner)
{
if (Dependency.SourcePath == null)
{
// Expand the target path
string ExpandedPath = Module.ExpandPathVariables(Dependency.Path, OutputDir, ExeDir);
if (FileFilter.FindWildcardIndex(ExpandedPath) == -1)
{
RuntimeDependencies.Add(new ModuleRules.RuntimeDependency(ExpandedPath, Dependency.Type));
}
else
{
RuntimeDependencies.AddRange(FileFilter.ResolveWildcard(ExpandedPath).Select(x => new ModuleRules.RuntimeDependency(x.FullName, Dependency.Type)));
}
}
else
{
// Parse the source and target patterns
FilePattern SourcePattern = new FilePattern(Unreal.EngineSourceDirectory, Module.ExpandPathVariables(Dependency.SourcePath, OutputDir, ExeDir));
FilePattern TargetPattern = new FilePattern(Unreal.EngineSourceDirectory, Module.ExpandPathVariables(Dependency.Path, OutputDir, ExeDir));
// Skip non-essential single files if they do not exist
if (Dependency.Type == StagedFileType.DebugNonUFS && !SourcePattern.ContainsWildcards() && !FileReference.Exists(SourcePattern.GetSingleFile()))
{
continue;
}
// Resolve all the wildcards between the source and target paths
Dictionary Mapping;
try
{
Mapping = FilePattern.CreateMapping(null, ref SourcePattern, ref TargetPattern);
}
catch (FilePatternException Ex)
{
ExceptionUtils.AddContext(Ex, "while creating runtime dependencies for module '{0}'", Module.Name);
throw;
}
foreach (KeyValuePair Pair in Mapping)
{
RuntimeDependencies.Add(new ModuleRules.RuntimeDependency(Pair.Key.FullName, Pair.Value.FullName, Dependency.Type));
}
}
}
}
}
///
/// Builds the binary.
///
/// Rules for the target being built
/// The toolchain which to use for building
/// The environment to compile the binary in
/// The environment to link the binary in
/// The working set of source files
/// Directory containing the output executable
/// The graph being built
/// Logger for output
/// Set of built products
public List Build(ReadOnlyTargetRules Target, UEToolChain ToolChain, CppCompileEnvironment CompileEnvironment, LinkEnvironment LinkEnvironment, ISourceFileWorkingSet WorkingSet, DirectoryReference ExeDir, IActionGraphBuilder Graph, ILogger Logger)
{
// Return nothing if we're using precompiled binaries. If we're not linking, we might want just one module to be compiled (eg. a foreign plugin), so allow any actions to run.
if (bUsePrecompiled && !(Target.LinkType == TargetLinkType.Monolithic && Target.bDisableLinking))
{
return new List();
}
// Setup linking environment.
LinkEnvironment BinaryLinkEnvironment = SetupBinaryLinkEnvironment(Target, ToolChain, LinkEnvironment, CompileEnvironment, WorkingSet, ExeDir, Graph, Logger);
// If we're generating projects, we only need include paths and definitions, there is no need to run the linking logic.
if (ProjectFileGenerator.bGenerateProjectFiles)
{
return BinaryLinkEnvironment.InputFiles;
}
// If linking is disabled, our build products are just the compiled object files
if (Target.bDisableLinking)
{
if (Target.LinkType == TargetLinkType.Modular)
{
// Make sure we record these files as outputs for this particular module.
Graph.SetOutputItemsForModule(PrimaryModule.Name, BinaryLinkEnvironment.InputFiles.ToArray());
}
return BinaryLinkEnvironment.InputFiles;
}
// Generate import libraries as a separate step
List OutputFiles = new List();
if (bCreateImportLibrarySeparately)
{
// Mark the link environment as cross-referenced.
BinaryLinkEnvironment.bIsCrossReferenced = true;
}
// Create the import library if needed
OutputFiles.AddRange(ToolChain.LinkImportLibrary(BinaryLinkEnvironment, Graph));
// Override the build to be a console app (i.e. only build a console app)
if (bBuildConsoleAppOnly)
{
BinaryLinkEnvironment.bIsBuildingConsoleApplication = true;
BinaryLinkEnvironment.bCodeCoverage = CompileEnvironment.bCodeCoverage;
BinaryLinkEnvironment.WindowsEntryPointOverride = "WinMainCRTStartup"; // For WinMain() instead of "main()" for Launch module
BinaryLinkEnvironment.OutputFilePaths = BinaryLinkEnvironment.OutputFilePaths.Select(Path => GetAdditionalConsoleAppPath(Path)).ToList();
}
else if (bBuildAdditionalConsoleApp)
{
// Produce additional binary but link it as a console app
LinkEnvironment ConsoleAppLinkEnvironment = new LinkEnvironment(BinaryLinkEnvironment);
ConsoleAppLinkEnvironment.bIsBuildingConsoleApplication = true;
ConsoleAppLinkEnvironment.bCodeCoverage = CompileEnvironment.bCodeCoverage;
ConsoleAppLinkEnvironment.WindowsEntryPointOverride = "WinMainCRTStartup"; // For WinMain() instead of "main()" for Launch module
ConsoleAppLinkEnvironment.OutputFilePaths = ConsoleAppLinkEnvironment.OutputFilePaths.Select(Path => GetAdditionalConsoleAppPath(Path)).ToList();
// Link the console app executable
FileItem[] ConsoleAppOutputFiles = ToolChain.LinkAllFiles(ConsoleAppLinkEnvironment, false, Graph);
OutputFiles.AddRange(ConsoleAppOutputFiles);
foreach (FileItem Executable in ConsoleAppOutputFiles)
{
OutputFiles.AddRange(ToolChain.PostBuild(Target, Executable, ConsoleAppLinkEnvironment, Graph));
}
}
// Link the binary.
FileItem[] Executables = ToolChain.LinkAllFiles(BinaryLinkEnvironment, false, Graph);
OutputFiles.AddRange(Executables);
// Save all the output items for this binary. This is used for hot-reload, and excludes any items added in PostBuild (such as additional files copied into the app).
if (Target.LinkType == TargetLinkType.Modular)
{
Graph.SetOutputItemsForModule(PrimaryModule.Name, OutputFiles.ToArray());
}
foreach (FileItem Executable in Executables)
{
OutputFiles.AddRange(ToolChain.PostBuild(Target, Executable, BinaryLinkEnvironment, Graph));
}
return OutputFiles;
}
///
/// Gets all the runtime dependencies Copies all the runtime dependencies from any modules in
///
/// The output list of runtime dependencies, mapping target file to type
/// Map of target files to source files that need to be copied
public void CollectRuntimeDependencies(List OutRuntimeDependencies, Dictionary TargetFileToSourceFile)
{
foreach (ModuleRules.RuntimeDependency Dependency in RuntimeDependencies)
{
FileReference DepFile = new FileReference(Dependency.Path);
if (Dependency.SourcePath == null)
{
OutRuntimeDependencies.Add(new RuntimeDependency(DepFile, Dependency.Type));
}
else
{
FileReference? ExistingSourceFile;
if (!TargetFileToSourceFile.TryGetValue(DepFile, out ExistingSourceFile))
{
TargetFileToSourceFile[DepFile] = new FileReference(Dependency.SourcePath);
OutRuntimeDependencies.Add(new RuntimeDependency(DepFile, Dependency.Type));
}
else if (ExistingSourceFile.FullName != Dependency.SourcePath)
{
throw new BuildException("Runtime dependency '{0}' is configured to be staged from '{1}' and '{2}'", Dependency.Path, Dependency.SourcePath, ExistingSourceFile);
}
}
}
}
///
/// Called to allow the binary to modify the link environment of a different binary containing
/// a module that depends on a module in this binary.
///
/// The link environment of the dependency
public void SetupDependentLinkEnvironment(LinkEnvironment DependentLinkEnvironment)
{
// Cache the list of libraries in the dependent link environment between calls. We typically run this code path many times for each module.
if (DependentLinkLibraries == null)
{
DependentLinkLibraries = new List();
foreach (FileReference OutputFilePath in OutputFilePaths)
{
FileReference LibraryFileName;
if (Type == UEBuildBinaryType.StaticLibrary || !DependentLinkEnvironment.Platform.IsInGroup(UnrealPlatformGroup.Microsoft))
{
LibraryFileName = OutputFilePath;
}
else
{
LibraryFileName = FileReference.Combine(IntermediateDirectory, OutputFilePath.GetFileNameWithoutExtension() + ".lib");
}
DependentLinkLibraries.Add(LibraryFileName);
}
}
DependentLinkEnvironment.Libraries.AddRange(DependentLinkLibraries);
}
///
/// Called to allow the binary to find hot reloadable modules.
///
/// Enumerable of modules
public IEnumerable FindHotReloadModules()
{
return Modules.Where(x => x.Rules.Context.bCanHotReload);
}
///
/// Generates a list of all modules referenced by this binary
///
/// True if dynamically loaded modules (and all of their dependent modules) should be included.
/// True if circular dependencies should be process
/// List of all referenced modules
public List GetAllDependencyModules(bool bIncludeDynamicallyLoaded, bool bForceCircular)
{
List ReferencedModules = new List();
HashSet IgnoreReferencedModules = new HashSet();
foreach (UEBuildModule Module in Modules)
{
if (IgnoreReferencedModules.Add(Module))
{
Module.GetAllDependencyModules(ReferencedModules, IgnoreReferencedModules, bIncludeDynamicallyLoaded, bForceCircular, bOnlyDirectDependencies: false);
ReferencedModules.Add(Module);
}
}
return ReferencedModules;
}
///
/// Generates a list of all modules referenced by this binary
///
/// Map of module to the module that referenced it
/// List of all referenced modules
public void FindModuleReferences(Dictionary ReferencedBy)
{
List ReferencedModules = new List();
foreach (UEBuildModule Module in Modules)
{
ReferencedModules.Add(Module);
ReferencedBy.Add(Module, null);
}
List DirectlyReferencedModules = new List();
HashSet VisitedModules = new HashSet();
for (int Idx = 0; Idx < ReferencedModules.Count; Idx++)
{
UEBuildModule SourceModule = ReferencedModules[Idx];
// Find all the direct references from this module
DirectlyReferencedModules.Clear();
SourceModule.GetAllDependencyModules(DirectlyReferencedModules, VisitedModules, false, false, true);
// Set up the references for all the new modules
foreach (UEBuildModule DirectlyReferencedModule in DirectlyReferencedModules)
{
if (!ReferencedBy.ContainsKey(DirectlyReferencedModule))
{
ReferencedBy.Add(DirectlyReferencedModule, SourceModule);
ReferencedModules.Add(DirectlyReferencedModule);
}
}
}
}
///
/// Sets whether to create a separate import library to resolve circular dependencies for this binary
///
/// True to create a separate import library
public void SetCreateImportLibrarySeparately(bool bInCreateImportLibrarySeparately)
{
bCreateImportLibrarySeparately = bInCreateImportLibrarySeparately;
}
///
/// Adds a module to the binary.
///
/// The module to add
public void AddModule(UEBuildModule Module)
{
if (!Modules.Contains(Module))
{
Modules.Add(Module);
}
}
///
/// Gets all build products produced by this binary
///
/// The target being built
/// The platform toolchain
/// Mapping of produced build product to type
/// Whether debug info is enabled for this binary
public void GetBuildProducts(ReadOnlyTargetRules Target, UEToolChain ToolChain, Dictionary BuildProducts, bool bCreateDebugInfo)
{
// Add all the precompiled outputs
foreach (UEBuildModuleCPP Module in Modules.OfType())
{
if (Module.Rules.bPrecompile)
{
if (Module.GeneratedCodeDirectory != null && DirectoryReference.Exists(Module.GeneratedCodeDirectory))
{
foreach (FileReference GeneratedCodeFile in DirectoryReference.EnumerateFiles(Module.GeneratedCodeDirectory, "*", SearchOption.AllDirectories))
{
// Exclude timestamp files, since they're always updated and cause collisions between builds
if (!GeneratedCodeFile.GetFileName().Equals("Timestamp", StringComparison.OrdinalIgnoreCase) && !GeneratedCodeFile.HasExtension(".cpp"))
{
BuildProducts.Add(GeneratedCodeFile, BuildProductType.BuildResource);
}
}
}
foreach (FileItem NatvisSourceFile in Module.NatvisFiles)
{
FileItem? Item = ToolChain.LinkDebuggerVisualizer(NatvisSourceFile, Module.IntermediateDirectory);
if (Item != null)
{
BuildProducts.Add(Item.Location, BuildProductType.BuildResource);
}
}
if (Target.LinkType == TargetLinkType.Monolithic)
{
FileReference PrecompiledManifestLocation = Module.PrecompiledManifestLocation;
BuildProducts.Add(PrecompiledManifestLocation, BuildProductType.BuildResource);
if (FileReference.Exists(PrecompiledManifestLocation)) // May be single file compile; skipped modulePrecompiledManifestLocation{
{
PrecompiledManifest ModuleManifest = PrecompiledManifest.Read(PrecompiledManifestLocation);
foreach (FileReference OutputFile in ModuleManifest.OutputFiles)
{
if (!BuildProducts.ContainsKey(OutputFile))
{
BuildProducts.Add(OutputFile, BuildProductType.BuildResource);
}
}
}
}
}
}
// Add all the binary outputs
if (!Target.bDisableLinking)
{
// Get the type of build products we're creating
BuildProductType OutputType = BuildProductType.RequiredResource;
switch (Type)
{
case UEBuildBinaryType.Executable:
OutputType = BuildProductType.Executable;
break;
case UEBuildBinaryType.DynamicLinkLibrary:
OutputType = BuildProductType.DynamicLibrary;
break;
case UEBuildBinaryType.StaticLibrary:
OutputType = BuildProductType.BuildResource;
break;
}
// Add the primary build products
string[] DebugExtensions = UEBuildPlatform.GetBuildPlatform(Target.Platform).GetDebugInfoExtensions(Target, Type);
if (Type == UEBuildBinaryType.Executable && bBuildConsoleAppOnly)
{
foreach (FileReference OutputFilePath in OutputFilePaths)
{
AddBuildProductAndDebugFiles(GetAdditionalConsoleAppPath(OutputFilePath), OutputType, DebugExtensions, BuildProducts, ToolChain, bCreateDebugInfo);
}
}
else
{
foreach (FileReference OutputFilePath in OutputFilePaths)
{
AddBuildProductAndDebugFiles(OutputFilePath, OutputType, DebugExtensions, BuildProducts, ToolChain, bCreateDebugInfo);
}
}
// Add the console app, if there is one
if (Type == UEBuildBinaryType.Executable && !bBuildConsoleAppOnly && bBuildAdditionalConsoleApp)
{
foreach (FileReference OutputFilePath in OutputFilePaths)
{
AddBuildProductAndDebugFiles(GetAdditionalConsoleAppPath(OutputFilePath), OutputType, DebugExtensions, BuildProducts, ToolChain, bCreateDebugInfo);
}
}
// Add any additional build products from the modules in this binary, including additional bundle resources/dylibs on Mac.
List Libraries = new List();
List BundleResources = new List();
GatherAdditionalResources(Libraries, BundleResources);
// Add any extra files from the toolchain
ToolChain.ModifyBuildProducts(Target, this, Libraries, BundleResources, BuildProducts);
}
}
///
/// Adds a build product and its associated debug file to a receipt.
///
/// Build product to add
/// The type of built product
/// Extensions for the matching debug file (may be null).
/// Map of build products to their type
/// The toolchain used to build these binaries
/// Whether creating debug info is enabled
static void AddBuildProductAndDebugFiles(FileReference OutputFile, BuildProductType OutputType, string[] DebugExtensions, Dictionary BuildProducts, UEToolChain ToolChain, bool bCreateDebugInfo)
{
BuildProducts.Add(OutputFile, OutputType);
foreach (string DebugExtension in DebugExtensions)
{
if (!String.IsNullOrEmpty(DebugExtension) && ToolChain.ShouldAddDebugFileToReceipt(OutputFile, OutputType) && bCreateDebugInfo)
{
// @todo this could be cleaned up if we replaced Platform.GetDebugExtensions() with ToolChain.GetDebugFiles(OutputFile)
// would need care in MacToolchain tho, so too risky for now
BuildProducts.Add(ToolChain.GetDebugFile(OutputFile, DebugExtension), BuildProductType.SymbolFile);
}
}
}
///
/// Enumerates resources which the toolchain may need may produced additional build products from. Some platforms (eg. Mac, Linux) can link directly
/// against .so/.dylibs, but they are also copied to the output folder by the toolchain.
///
/// List to which libraries required by this module are added
/// List of bundle resources required by this module
public void GatherAdditionalResources(List Libraries, List BundleResources)
{
foreach (UEBuildModule Module in Modules)
{
Module.GatherAdditionalResources(Libraries, BundleResources);
}
}
///
/// Helper function to get the console app BinaryName-Cmd.exe filename based on the binary filename.
///
/// Full path to the binary exe.
///
public static FileReference GetAdditionalConsoleAppPath(FileReference BinaryPath)
{
DirectoryReference Directory = BinaryPath.Directory;
if (Directory.FullName.EndsWith(".app/Contents/MacOS"))
{
Directory = Directory.ParentDirectory!.ParentDirectory!.ParentDirectory!;
}
return FileReference.Combine(Directory, BinaryPath.GetFileNameWithoutExtension() + "-Cmd" + BinaryPath.GetExtension());
}
///
/// Checks whether the binary output paths are appropriate for the distribution
/// level of its direct module dependencies
///
public bool CheckRestrictedFolders(List RootDirectories, Dictionary> ModuleRestrictedFolderCache, ILogger Logger)
{
// Find all the modules we depend on
Dictionary ModuleReferencedBy = new Dictionary();
FindModuleReferences(ModuleReferencedBy);
// Loop through each of the output binaries and check them separately
bool bResult = true;
foreach (FileReference OutputFilePath in OutputFilePaths)
{
// Find the base directory for this binary
DirectoryReference? BaseDir = RootDirectories.FirstOrDefault(x => OutputFilePath.IsUnderDirectory(x));
if (BaseDir == null)
{
continue;
}
// Find the permitted restricted folder references under the base directory
List BinaryFolders = RestrictedFolders.FindPermittedRestrictedFolderReferences(BaseDir, OutputFilePath.Directory);
List AliasedBinaryFolders = new List();
foreach (RestrictedFolder BinaryFolder in BinaryFolders)
{
string? Alias;
if (PrimaryModule.AliasRestrictedFolders.TryGetValue(BinaryFolder.ToString(), out Alias))
{
foreach (RestrictedFolder Folder in RestrictedFolder.GetValues())
{
if (Folder.ToString().Equals(Alias))
{
AliasedBinaryFolders.Add(Folder);
}
}
}
}
BinaryFolders.AddRange(AliasedBinaryFolders);
// Check all the dependent modules
foreach (UEBuildModule Module in ModuleReferencedBy.Keys)
{
// Find the restricted folders for this module
Dictionary? ModuleRestrictedFolders;
if (!ModuleRestrictedFolderCache.TryGetValue(Module, out ModuleRestrictedFolders))
{
ModuleRestrictedFolders = Module.FindRestrictedFolderReferences(RootDirectories);
ModuleRestrictedFolderCache.Add(Module, ModuleRestrictedFolders);
}
// Write errors for any missing paths in the output files
foreach (KeyValuePair Pair in ModuleRestrictedFolders)
{
if (!BinaryFolders.Contains(Pair.Key))
{
List ReferenceChain = new List();
for (UEBuildModule? ReferencedModule = Module; ReferencedModule != null; ReferencedModule = ModuleReferencedBy[ReferencedModule])
{
ReferenceChain.Insert(0, ReferencedModule.Name);
}
Logger.LogError("Output binary \"{OutputFilePath}\" is not in a {Folder} folder, but references \"{ReferenceFile}\" via {ReferenceChain}.", OutputFilePath, Pair.Key.ToString(), Pair.Value, String.Join(" -> ", ReferenceChain));
bResult = false;
}
}
}
}
return bResult;
}
///
/// Write information about this binary to a JSON file
///
/// Writer for this binary's data
public virtual void ExportJson(JsonWriter Writer)
{
Writer.WriteValue("File", OutputFilePath.FullName);
Writer.WriteValue("Type", Type.ToString());
Writer.WriteArrayStart("Modules");
foreach (UEBuildModule Module in Modules)
{
Writer.WriteValue(Module.Name);
}
Writer.WriteArrayEnd();
}
// UEBuildBinary interface.
bool IsBuildingDll(UEBuildBinaryType Type)
{
return Type == UEBuildBinaryType.DynamicLinkLibrary;
}
bool IsBuildingLibrary(UEBuildBinaryType Type)
{
return Type == UEBuildBinaryType.StaticLibrary;
}
public CppCompileEnvironment CreateBinaryCompileEnvironment(CppCompileEnvironment GlobalCompileEnvironment)
{
CppCompileEnvironment BinaryCompileEnvironment = new CppCompileEnvironment(GlobalCompileEnvironment);
BinaryCompileEnvironment.bIsBuildingDLL = IsBuildingDll(Type);
BinaryCompileEnvironment.bIsBuildingLibrary = IsBuildingLibrary(Type);
return BinaryCompileEnvironment;
}
private LinkEnvironment SetupBinaryLinkEnvironment(ReadOnlyTargetRules Target, UEToolChain ToolChain, LinkEnvironment LinkEnvironment, CppCompileEnvironment CompileEnvironment, ISourceFileWorkingSet WorkingSet, DirectoryReference ExeDir, IActionGraphBuilder Graph, ILogger Logger)
{
LinkEnvironment BinaryLinkEnvironment = new LinkEnvironment(LinkEnvironment);
HashSet LinkEnvironmentVisitedModules = new HashSet();
List BinaryDependencies = new List();
CppCompileEnvironment BinaryCompileEnvironment = CreateBinaryCompileEnvironment(CompileEnvironment);
if (BinaryCompileEnvironment.bUseSharedBuildEnvironment && Target.ProjectFile != null && IntermediateDirectory.IsUnderDirectory(Target.ProjectFile.Directory))
{
BinaryCompileEnvironment.bUseSharedBuildEnvironment = false;
}
HashSet InputFilesLookup = new();
foreach (UEBuildModule Module in UEBuildModule.StableTopologicalSort(Modules))
{
if (Module.Binary == null || Module.Binary == this)
{
// Compile each module.
Logger.LogDebug("Compile module: {ModuleName}", Module.Name);
List LinkInputFiles = Module.Compile(Target, ToolChain, BinaryCompileEnvironment, WorkingSet, Graph, Logger);
InputObjects.UnionWith(LinkInputFiles.Where(x => x.HasExtension(".obj") || x.HasExtension(".o")));
// Save the module outputs. In monolithic builds, this is just the object files.
if (Target.LinkType == TargetLinkType.Monolithic)
{
Graph.SetOutputItemsForModule(Module.Name, LinkInputFiles.ToArray());
}
// Sometimes the same link input file is added multiple times, so make sure to filter out the dups
foreach (FileItem LinkInputFile in LinkInputFiles)
{
if (InputFilesLookup.Add(LinkInputFile))
{
BinaryLinkEnvironment.InputFiles.Add(LinkInputFile);
}
}
// Project root path should only be added if the binary needs it, to prevent violating shared environment actions
if (Target.ProjectFile != null && BinaryLinkEnvironment.InputFiles.Any(x => x.Location.IsUnderDirectory(Target.ProjectFile.Directory)))
{
BinaryLinkEnvironment.RootPaths[CppRootPathFolder.Project] = Target.ProjectFile.Directory;
}
// Force a reference to initialize module for this binary
if (Module.Rules.bRequiresImplementModule ?? true)
{
BinaryLinkEnvironment.IncludeFunctions.Add(String.Format("IMPLEMENT_MODULE_{0}", Module.Name));
}
}
else
{
BinaryDependencies.Add(Module.Binary);
}
// Allow the module to modify the link environment for the binary.
Module.SetupPrivateLinkEnvironment(this, BinaryLinkEnvironment, BinaryDependencies, LinkEnvironmentVisitedModules, ExeDir, Logger);
}
// Allow the binary dependencies to modify the link environment.
foreach (UEBuildBinary BinaryDependency in BinaryDependencies)
{
BinaryDependency.SetupDependentLinkEnvironment(BinaryLinkEnvironment);
}
// Gather debug visualizers for every relevant module
foreach (UEBuildModule VisitedModule in LinkEnvironmentVisitedModules)
{
VisitedModule.LinkDebuggerVisualizers(BinaryLinkEnvironment.DebuggerVisualizerFiles, ToolChain, Logger);
}
// Set the link output file.
BinaryLinkEnvironment.OutputFilePaths = OutputFilePaths.ToList();
// Remember the link type
BinaryLinkEnvironment.LinkType = Target.LinkType;
// Set whether the link is allowed to have exports.
BinaryLinkEnvironment.bHasExports = bAllowExports;
// Set the output folder for intermediate files
BinaryLinkEnvironment.IntermediateDirectory = IntermediateDirectory;
// Put the non-executable output files (PDB, import library, etc) in the same directory as the production
BinaryLinkEnvironment.OutputDirectory = OutputFilePaths[0].Directory;
// Setup link output type
BinaryLinkEnvironment.bIsBuildingDLL = IsBuildingDll(Type);
BinaryLinkEnvironment.bIsBuildingLibrary = IsBuildingLibrary(Type);
// Setup object export stripping
if (Target.bStripExports)
{
bStripUnusedExports = true;
string Name = OutputFilePaths.First().GetFileNameWithoutExtension();
string Ext = ToolChain.GetExtraLinkFileExtension();
FileReference extraObj = FileReference.Combine(IntermediateDirectory!, $"{Name}.exports.{Ext}");
BinaryLinkEnvironment.InputFiles.Add(FileItem.GetItemByFileReference(extraObj));
}
// Code coverage inherited from compile environment
BinaryLinkEnvironment.bCodeCoverage = CompileEnvironment.bCodeCoverage;
// If we don't have any resource file, use the default or compile a custom one for this module
if (BinaryLinkEnvironment.Platform.IsInGroup(UnrealPlatformGroup.Windows))
{
// Figure out if this binary has any custom resource files. Hacky check to ignore the resource file in the Launch module, since it contains dialogs that the engine needs and always needs to be included.
FileItem[] CustomResourceFiles = BinaryLinkEnvironment.InputFiles.Where(x => x.Location.HasExtension(".res") && !x.Location.FullName.EndsWith("\\Launch\\PCLaunch.rc.res", StringComparison.OrdinalIgnoreCase)).ToArray();
if (CustomResourceFiles.Length == 0)
{
if (BinaryLinkEnvironment.DefaultResourceFiles.Count > 0)
{
// Use the default resource file if possible
BinaryLinkEnvironment.InputFiles.AddRange(BinaryLinkEnvironment.DefaultResourceFiles);
}
else
{
// Get the intermediate directory
DirectoryReference ResourceIntermediateDirectory = BinaryLinkEnvironment.IntermediateDirectory;
// Place resource intermediate files for executables in a subfolder
if (!BinaryLinkEnvironment.bIsBuildingDLL && !BinaryLinkEnvironment.bIsBuildingLibrary)
{
ResourceIntermediateDirectory = DirectoryReference.Combine(ResourceIntermediateDirectory, OutputFilePaths[0].GetFileName());
}
// Create a compile environment for resource files
CppCompileEnvironment ResourceCompileEnvironment = new CppCompileEnvironment(BinaryCompileEnvironment);
// @todo: This should be in some Windows code somewhere...
// Set the original file name macro; used in Default.rc2 to set the binary metadata fields.
ResourceCompileEnvironment.Definitions.Add("ORIGINAL_FILE_NAME=\"" + OutputFilePaths[0].GetFileName() + "\"");
// Set the other version fields if requested
if (Target.WindowsPlatform.bSetResourceVersions)
{
if (Target.Version.Changelist != 0)
{
ResourceCompileEnvironment.Definitions.Add(String.Format("BUILT_FROM_CHANGELIST={0}", Target.Version.Changelist));
}
if (!String.IsNullOrEmpty(Target.Version.BranchName))
{
ResourceCompileEnvironment.Definitions.Add(String.Format("BRANCH_NAME={0}", Target.Version.BranchName));
}
if (!String.IsNullOrEmpty(Target.BuildVersion))
{
ResourceCompileEnvironment.Definitions.Add(String.Format("BUILD_VERSION={0}", Target.BuildVersion));
}
}
// Otherwise compile the default resource file per-binary, so that it gets the correct ORIGINAL_FILE_NAME macro.
FileItem DefaultResourceFile = FileItem.GetItemByFileReference(FileReference.Combine(Unreal.EngineDirectory, "Build", "Windows", "Resources", "Default.rc2"));
CPPOutput DefaultResourceOutput = ToolChain.CompileRCFiles(ResourceCompileEnvironment, new List { DefaultResourceFile }, ResourceIntermediateDirectory, Graph);
BinaryLinkEnvironment.InputFiles.AddRange(DefaultResourceOutput.ObjectFiles);
}
}
}
// Add all the common resource files
BinaryLinkEnvironment.InputFiles.AddRange(BinaryLinkEnvironment.CommonResourceFiles);
// Provide list of runtime dependencies to link environment.
BinaryLinkEnvironment.RuntimeDependencies = RuntimeDependencies;
// Allow the platform to modify the binary link environment for platform-specific resources etc.
UEBuildPlatform.GetBuildPlatform(Target.Platform).ModifyBinaryLinkEnvironment(BinaryLinkEnvironment, BinaryCompileEnvironment, Target, ToolChain, Graph);
return BinaryLinkEnvironment;
}
///
/// ToString implementation
///
/// Returns the OutputFilePath for this binary
public override string ToString()
{
return OutputFilePath.FullName;
}
}
}