// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Text; using System.Diagnostics; using System.IO; using System.Linq; using System.Xml; namespace UnrealBuildTool { /// /// All binary types generated by UBT /// public enum UEBuildBinaryType { Executable, DynamicLinkLibrary, StaticLibrary, Object, PrecompiledHeader } /// /// UEBuildBinary configuration /// Configuration class for a UEBuildBinary. /// Exposes the configuration values of the BuildBinary class without exposing the functions. /// public class UEBuildBinaryConfiguration { /// /// The type of binary to build /// public UEBuildBinaryType Type; /// /// The output file path. This must be set before a binary can be built using it. /// public string OutputFilePath; /// /// Original output filepath. This is the original binary name before hot-reload suffix has been appended to it. /// public string OriginalOutputFilePath; /// /// 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 string IntermediateDirectory; /// /// If true, build exports lib /// public bool bAllowExports = false; /// /// If true, create a separate import library /// public bool bCreateImportLibrarySeparately = false; /// /// If true, include dependent libraries in the static library being built /// public bool bIncludeDependentLibrariesInLibrary = false; /// /// If false, this binary will not be compiled and it is only used to set up link environments /// public bool bAllowCompilation = true; /// /// True if this binary has any Build.cs files, if not this is probably a binary-only plugins /// public bool bHasModuleRules = true; /// /// For most binaries, this is false. If this is a cross-platform binary build for a specific platform (for example XB1 DLL for a windows editor) this will be true. /// public bool bIsCrossTarget = false; /// /// If true, the binary is being compiled as a monolithic build /// public bool bCompileMonolithic = false; /// /// The build target configuration being compiled /// public UnrealTargetConfiguration TargetConfiguration = UnrealTargetConfiguration.Development; /// /// The name of the target being compiled /// public string TargetName = ""; /// /// The projectfile path /// public string ProjectFilePath = ""; /// /// List of modules to link together into this executable /// public List ModuleNames = new List(); /// /// The configuration class for a binary build. /// /// /// /// /// /// For most binaries, this is false. If this is a cross-platform binary build for a specific platform (for example XB1 DLL for a windows editor) this will be true. /// /// public UEBuildBinaryConfiguration( UEBuildBinaryType InType, string InOutputFilePath = null, string InIntermediateDirectory = null, bool bInAllowExports = false, bool bInCreateImportLibrarySeparately = false, bool bInIncludeDependentLibrariesInLibrary = false, bool bInAllowCompilation = true, bool bInHasModuleRules = true, bool bInIsCrossTarget = false, bool bInCompileMonolithic = false, UnrealTargetConfiguration InTargetConfiguration = UnrealTargetConfiguration.Development, string InTargetName = "", string InProjectFilePath = "", List InModuleNames = null ) { Type = InType; OutputFilePath = InOutputFilePath; IntermediateDirectory = InIntermediateDirectory; bAllowExports = bInAllowExports; bCreateImportLibrarySeparately = bInCreateImportLibrarySeparately; bIncludeDependentLibrariesInLibrary = bInIncludeDependentLibrariesInLibrary; bAllowCompilation = bInAllowCompilation; bHasModuleRules = bInHasModuleRules; bIsCrossTarget = bInIsCrossTarget; bCompileMonolithic = bInCompileMonolithic; TargetConfiguration = InTargetConfiguration; TargetName = InTargetName; ProjectFilePath = InProjectFilePath; ModuleNames = InModuleNames; } } /// /// A binary built by UBT. /// public abstract class UEBuildBinary { /// /// The target which owns this binary. /// public UEBuildTarget Target; /// /// The build binary configuration data /// public UEBuildBinaryConfiguration Config = null; /// /// Create an instance of the class with the given configuration data /// /// The build binary configuration to initialize the class with public UEBuildBinary( UEBuildTarget InTarget, UEBuildBinaryConfiguration InConfig) { Debug.Assert(InConfig.OutputFilePath != null && InConfig.IntermediateDirectory != null); Target = InTarget; Config = InConfig; } /// /// Called to resolve module names and uniquely bind modules to a binary. /// /// The build target the modules are being bound for /// The target info public virtual void BindModules() {} /// /// Builds the binary. /// /// The toolchain which to use for building /// The environment to compile the binary in /// The environment to link the binary in /// public abstract IEnumerable Build(IUEToolChain ToolChain, CPPEnvironment CompileEnvironment,LinkEnvironment LinkEnvironment); /// /// Writes an XML summary of the build environment for this binary /// /// The environment to compile the binary in /// The environment to link the binary in /// The output XML writer /// public abstract void WriteBuildEnvironment(CPPEnvironment CompileEnvironment, LinkEnvironment LinkEnvironment, XmlWriter Writer); /// /// 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 virtual void SetupDependentLinkEnvironment(ref LinkEnvironment DependentLinkEnvironment) {} /// /// Called to allow the binary to to determine if it matches the Only module "short module name". /// /// /// The OnlyModule if found, null if not public virtual OnlyModule FindOnlyModule(List OnlyModules) { return null; } /// /// 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 virtual List GetAllDependencyModules( bool bIncludeDynamicallyLoaded, bool bForceCircular ) { return new List(); } /// /// Process all modules that aren't yet bound, creating binaries for modules that don't yet have one (if needed), /// and updating modules for circular dependencies. /// /// The target we are currently building /// True to build only specific modules, false for all /// The specific modules to build /// List of newly-created binaries (may be empty) public virtual List ProcessUnboundModules() { return null; } /// /// Sets whether to create a separate import library to resolve circular dependencies for this binary /// /// True to create a separate import library public virtual void SetCreateImportLibrarySeparately( bool bInCreateImportLibrarySeparately ) { } /// /// Sets whether to include dependent libraries when building a static library /// /// True to include dependent libraries public virtual void SetIncludeDependentLibrariesInLibrary(bool bInIncludeDependentLibrariesInLibrary) { } /// /// Adds a module to the binary. /// /// The module to add public virtual void AddModule( string ModuleName ) { } /// /// Helper function to get the console app BinaryName-Cmd.exe filename based on the binary filename. /// /// Full path to the binary exe. /// public static string GetAdditionalConsoleAppPath(string BinaryPath) { return Path.Combine(Path.GetDirectoryName(BinaryPath), Path.GetFileNameWithoutExtension(BinaryPath) + "-Cmd" + Path.GetExtension(BinaryPath)); } }; /// /// A binary built by UBT from a set of C++ modules. /// public class UEBuildBinaryCPP : UEBuildBinary { public HashSet ModuleNames { get; private set; } private bool bCreateImportLibrarySeparately; private bool bIncludeDependentLibrariesInLibrary; /// /// Create an instance initialized to the given configuration /// /// The build binary configuration to initialize the instance to public UEBuildBinaryCPP( UEBuildTarget InTarget, UEBuildBinaryConfiguration InConfig ) : base( InTarget, InConfig ) { ModuleNames = new HashSet(InConfig.ModuleNames); bCreateImportLibrarySeparately = InConfig.bCreateImportLibrarySeparately; bIncludeDependentLibrariesInLibrary = InConfig.bIncludeDependentLibrariesInLibrary; } /// /// Adds a module to the binary. /// /// The module to add public override void AddModule(string ModuleName) { if( !ModuleNames.Contains( ModuleName ) ) { ModuleNames.Add( ModuleName ); } } // UEBuildBinary interface. /// /// Called to resolve module names and uniquely bind modules to a binary. /// /// The build target the modules are being bound for /// The target info public override void BindModules() { foreach(var ModuleName in ModuleNames) { UEBuildModule Module = null; if (Config.bHasModuleRules) { Module = Target.FindOrCreateModuleByName(ModuleName); if (Module.Binary != null) { throw new BuildException("Module \"{0}\" linked into both {1} and {2}, which creates ambiguous linkage for dependents.", ModuleName, Module.Binary.Config.OutputFilePath, Config.OutputFilePath); } Module.Binary = this; Module.bIncludedInTarget = true; } // We set whether the binary is being compiled monolithic here to know later - specifically // when we are determining whether to use SharedPCHs or not for static lib builds of plugins. Config.bCompileMonolithic = Target.ShouldCompileMonolithic(); // We also need to know what the actual build target configuration is later in the process // where we do not have access to the Target itself... This is for generating the paths // to the plugins. Config.TargetConfiguration = Target.Configuration; Config.TargetName = Target.GetAppName(); if (Module != null && (Target.Rules == null || Target.Rules.bOutputToEngineBinaries == false)) { // Fix up the binary path if this is module specifies an alternate output directory Config.OutputFilePath = Module.FixupOutputPath(Config.OutputFilePath); } } } /// /// 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 override List GetAllDependencyModules(bool bIncludeDynamicallyLoaded, bool bForceCircular) { var OrderedModules = new List(); var ReferencedModules = new Dictionary( StringComparer.InvariantCultureIgnoreCase ); foreach( var ModuleName in ModuleNames ) { if( !ReferencedModules.ContainsKey( ModuleName ) ) { var Module = Target.GetModuleByName( ModuleName ); ReferencedModules[ ModuleName ] = Module; bool bOnlyDirectDependencies = false; Module.GetAllDependencyModules(ref ReferencedModules, ref OrderedModules, bIncludeDynamicallyLoaded, bForceCircular, bOnlyDirectDependencies); OrderedModules.Add( Module ); } } return OrderedModules; } /// /// Process all modules that aren't yet bound, creating binaries for modules that don't yet have one (if needed), /// and updating modules for circular dependencies. /// /// The target we are currently building /// True to build only specific modules, false for all /// The specific modules to build /// List of newly-created binaries (may be empty) public override List ProcessUnboundModules() { var Binaries = new Dictionary( StringComparer.InvariantCultureIgnoreCase ); if (Config.bHasModuleRules) { foreach (var ModuleName in ModuleNames) { var Module = Target.FindOrCreateModuleByName(ModuleName); Module.RecursivelyProcessUnboundModules(Target, ref Binaries, this); } } else { // There's only one module in this case, so just bind it to this binary foreach (var ModuleName in ModuleNames) { Binaries.Add(ModuleName, this); } } // Now build a final list of newly-created binaries that were bound to. The hash may contain duplicates, so // we filter those out here. var BinaryList = new List(); foreach( var CurBinary in Binaries.Values ) { // Never include ourselves in the new binary list (monolithic case) if( CurBinary != this ) { if( !BinaryList.Contains( CurBinary ) ) { BinaryList.Add( CurBinary ); } } } return BinaryList; } /// /// Sets whether to create a separate import library to resolve circular dependencies for this binary /// /// True to create a separate import library public override void SetCreateImportLibrarySeparately(bool bInCreateImportLibrarySeparately) { bCreateImportLibrarySeparately = bInCreateImportLibrarySeparately; } /// /// Sets whether to include dependent libraries when building a static library /// /// public override void SetIncludeDependentLibrariesInLibrary(bool bInIncludeDependentLibrariesInLibrary) { bIncludeDependentLibrariesInLibrary = bInIncludeDependentLibrariesInLibrary; } /// /// Builds the binary. /// /// The environment to compile the binary in /// The environment to link the binary in /// public override IEnumerable Build(IUEToolChain TargetToolChain, CPPEnvironment CompileEnvironment, LinkEnvironment LinkEnvironment) { // Determine the type of binary we're linking. switch (Config.Type) { case UEBuildBinaryType.DynamicLinkLibrary: CompileEnvironment.Config.bIsBuildingDLL = true; CompileEnvironment.Config.bIsBuildingLibrary = false; break; case UEBuildBinaryType.StaticLibrary: CompileEnvironment.Config.bIsBuildingDLL = false; CompileEnvironment.Config.bIsBuildingLibrary = true; break; default: CompileEnvironment.Config.bIsBuildingDLL = false; CompileEnvironment.Config.bIsBuildingLibrary = false; break; }; var OutputFiles = new List(); var BinaryCompileEnvironment = CompileEnvironment.DeepCopy(); var BinaryLinkEnvironment = LinkEnvironment.DeepCopy(); // Process each module that is linked into the binary. var BinaryDependencies = new List(); var LinkEnvironmentVisitedModules = new Dictionary(); // @Hack: This to prevent UHT from listing CoreUObject.generated.cpp as its dependency. // We flag the compile environment when we build UHT so that we don't need to check // this for each file when generating their dependencies. BinaryCompileEnvironment.bHackHeaderGenerator = (Target.GetAppName() == "UnrealHeaderTool"); // Set the original file name macro; used in PCLaunch.rc to set the binary metadata fields. var OriginalFilename = !String.IsNullOrEmpty(Config.OriginalOutputFilePath) ? Path.GetFileName(Config.OriginalOutputFilePath) : Path.GetFileName(Config.OutputFilePath); BinaryCompileEnvironment.Config.Definitions.Add("ORIGINAL_FILE_NAME=\"" + OriginalFilename + "\""); foreach (var ModuleName in ModuleNames) { var Module = Target.GetModuleByName(ModuleName); // Compile each module. Log.TraceVerbose("Compile module: " + ModuleName); var LinkInputFiles = Module.Compile(CompileEnvironment, BinaryCompileEnvironment, Config.bCompileMonolithic); // NOTE: Because of 'Shared PCHs', in monolithic builds the same PCH file may appear as a link input // multiple times for a single binary. We'll check for that here, and only add it once. This avoids // a linker warning about redundant .obj files. The same is true for .res files. We only take the first one. bool bHasResFile = false; foreach (var InputFile in BinaryLinkEnvironment.InputFiles) { if (InputFile.AbsolutePath.EndsWith(".res") && !InputFile.AbsolutePath.EndsWith(".inl.res")) { bHasResFile = true; break; } } foreach( var LinkInputFile in LinkInputFiles ) { bool bIsResourceFile = LinkInputFile.AbsolutePath.EndsWith(".res") && !LinkInputFile.AbsolutePath.EndsWith(".inl.res"); if( !BinaryLinkEnvironment.InputFiles.Contains( LinkInputFile ) && (!bHasResFile || !bIsResourceFile)) { BinaryLinkEnvironment.InputFiles.Add( LinkInputFile ); bHasResFile = bHasResFile || bIsResourceFile; } } // Allow the module to modify the link environment for the binary. Module.SetupPrivateLinkEnvironment(ref BinaryLinkEnvironment,ref BinaryDependencies,ref LinkEnvironmentVisitedModules); } // Allow the binary dependencies to modify the link environment. foreach(var BinaryDependency in BinaryDependencies) { BinaryDependency.SetupDependentLinkEnvironment(ref BinaryLinkEnvironment); } // Set the link output file. BinaryLinkEnvironment.Config.OutputFilePath = Config.OutputFilePath; // Set whether the link is allowed to have exports. BinaryLinkEnvironment.Config.bHasExports = Config.bAllowExports; // Set the output folder for intermediate files BinaryLinkEnvironment.Config.IntermediateDirectory = Config.IntermediateDirectory; // Put the non-executable output files (PDB, import library, etc) in the same directory as the production BinaryLinkEnvironment.Config.OutputDirectory = Path.GetDirectoryName(Config.OutputFilePath); // Determine the type of binary we're linking. switch (Config.Type) { case UEBuildBinaryType.DynamicLinkLibrary: BinaryLinkEnvironment.Config.bIsBuildingDLL = true; BinaryLinkEnvironment.Config.bIsBuildingLibrary = false; break; case UEBuildBinaryType.StaticLibrary: BinaryLinkEnvironment.Config.bIsBuildingDLL = false; BinaryLinkEnvironment.Config.bIsBuildingLibrary = true; break; default: BinaryLinkEnvironment.Config.bIsBuildingDLL = false; BinaryLinkEnvironment.Config.bIsBuildingLibrary = false; break; }; if( ProjectFileGenerator.bGenerateProjectFiles ) { // We're generating projects. Since we only need include paths and definitions, there is no need // to go ahead and run through the linking logic. OutputFiles = BinaryLinkEnvironment.InputFiles; } else if( BuildConfiguration.bEnableCodeAnalysis ) { // We're only analyzing code, so we won't actually link any executables. Instead, our output // files will simply be the .obj files that were compiled during static analysis. OutputFiles = BinaryLinkEnvironment.InputFiles; } else { if(bCreateImportLibrarySeparately) { // Mark the link environment as cross-referenced. BinaryLinkEnvironment.Config.bIsCrossReferenced = true; if (BinaryLinkEnvironment.Config.Target.Platform != CPPTargetPlatform.Mac && BinaryLinkEnvironment.Config.Target.Platform != CPPTargetPlatform.Linux) { // Create the import library. OutputFiles.Add(BinaryLinkEnvironment.LinkExecutable(true)); } } BinaryLinkEnvironment.Config.bIncludeDependentLibrariesInLibrary = bIncludeDependentLibrariesInLibrary; // Link the binary. FileItem Executable = BinaryLinkEnvironment.LinkExecutable(false); OutputFiles.Add(Executable); // Produce additional console app if requested if (BinaryLinkEnvironment.Config.CanProduceAdditionalConsoleApp && UEBuildConfiguration.bBuildEditor) { // Produce additional binary but link it as a console app var ConsoleAppLinkEvironment = BinaryLinkEnvironment.DeepCopy(); ConsoleAppLinkEvironment.Config.bIsBuildingConsoleApplication = true; ConsoleAppLinkEvironment.Config.WindowsEntryPointOverride = "WinMainCRTStartup"; // For WinMain() instead of "main()" for Launch module ConsoleAppLinkEvironment.Config.OutputFilePath = GetAdditionalConsoleAppPath(ConsoleAppLinkEvironment.Config.OutputFilePath); // Link the console app executable OutputFiles.Add(ConsoleAppLinkEvironment.LinkExecutable(false)); } OutputFiles.AddRange(TargetToolChain.PostBuild(Executable, BinaryLinkEnvironment)); } return OutputFiles; } /// /// Writes an XML summary of the build environment for this binary /// /// The environment to compile the binary in /// The environment to link the binary in /// The output XML writer /// public override void WriteBuildEnvironment(CPPEnvironment CompileEnvironment, LinkEnvironment LinkEnvironment, XmlWriter Writer) { // Output documentation for each module Writer.WriteStartElement("binary"); Writer.WriteAttributeString("name", Path.GetFileName(Config.OutputFilePath)); Writer.WriteAttributeString("type", "cpp"); foreach (var ModuleName in ModuleNames) { UEBuildModule Module = Target.GetModuleByName(ModuleName); Module.WriteBuildEnvironment(CompileEnvironment, Writer); } Writer.WriteEndElement(); } /// /// 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 override void SetupDependentLinkEnvironment(ref LinkEnvironment DependentLinkEnvironment) { string LibraryFileName; if (Config.Type == UEBuildBinaryType.StaticLibrary || DependentLinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Mac || DependentLinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Linux) { LibraryFileName = Config.OutputFilePath; } else { LibraryFileName = Path.Combine(Config.IntermediateDirectory, Path.GetFileNameWithoutExtension(Config.OutputFilePath) + ".lib"); } DependentLinkEnvironment.Config.AdditionalLibraries.Add(LibraryFileName); } /// /// Called to allow the binary to to determine if it matches the Only module "short module name". /// /// /// The OnlyModule if found, null if not public override OnlyModule FindOnlyModule(List OnlyModules) { foreach (var ModuleName in ModuleNames) { foreach (var OnlyModule in OnlyModules) { if (OnlyModule.OnlyModuleName.ToLower() == ModuleName.ToLower()) { return OnlyModule; } } } return null; } // Object interface. /// /// ToString implementation /// /// Returns the OutputFilePath for this binary public override string ToString() { return Config.OutputFilePath; } }; /// /// A DLL built by MSBuild from a C# project. /// public class UEBuildBinaryCSDLL : UEBuildBinary { /// /// Create an instance initialized to the given configuration /// /// The build binary configuration to initialize the instance to public UEBuildBinaryCSDLL(UEBuildTarget InTarget, UEBuildBinaryConfiguration InConfig) : base(InTarget, InConfig) { } /// /// Builds the binary. /// /// The toolchain to use for building /// The environment to compile the binary in /// The environment to link the binary in /// public override IEnumerable Build(IUEToolChain ToolChain, CPPEnvironment CompileEnvironment, LinkEnvironment LinkEnvironment) { var ProjectCSharpEnviroment = new CSharpEnvironment(); if (LinkEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Debug) { ProjectCSharpEnviroment.TargetConfiguration = CSharpTargetConfiguration.Debug; } else { ProjectCSharpEnviroment.TargetConfiguration = CSharpTargetConfiguration.Development; } ProjectCSharpEnviroment.EnvironmentTargetPlatform = LinkEnvironment.Config.Target.Platform; // Currently only supported by windows... UEToolChain.GetPlatformToolChain(CPPTargetPlatform.Win64).CompileCSharpProject( ProjectCSharpEnviroment, Config.ProjectFilePath, Config.OutputFilePath); return new FileItem[] { FileItem.GetItemByPath(Config.OutputFilePath) }; } /// /// Writes an XML summary of the build environment for this binary /// /// The environment to compile the binary in /// The environment to link the binary in /// The output XML writer /// public override void WriteBuildEnvironment(CPPEnvironment CompileEnvironment, LinkEnvironment LinkEnvironment, XmlWriter Writer) { Writer.WriteStartElement("binary"); Writer.WriteAttributeString("name", Path.GetFileName(Config.OutputFilePath)); Writer.WriteAttributeString("type", "cs"); Writer.WriteStartElement("project"); Writer.WriteAttributeString("path", Config.ProjectFilePath); Writer.WriteEndElement(); Writer.WriteEndElement(); } }; }