// 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();
}
};
}