Skip to content

Instantly share code, notes, and snippets.

@andrewgrant
Created September 16, 2016 15:06
Show Gist options
  • Save andrewgrant/d0838c6cbbf5d15a0d3e9731c983de9b to your computer and use it in GitHub Desktop.
Save andrewgrant/d0838c6cbbf5d15a0d3e9731c983de9b to your computer and use it in GitHub Desktop.
Modified UBT so that checked out/writable files excluded by adaptive unity have optimization turned off for ease of debugging. Prior to UE 4.14 this also requires a modification to VCToolChain due to assumptions about optimization settings.
// Copyright 1998-2016 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.Text.RegularExpressions;
using System.Xml;
using Tools.DotNETCommon.CaselessDictionary;
namespace UnrealBuildTool
{
/// <summary>
/// Distribution level of module.
/// Note: The name of each entry is used to search for/create folders
/// </summary>
public enum UEBuildModuleDistribution
{
/// Binaries can be distributed to everyone
Public,
/// Can be used by UE4 but not required
CarefullyRedist,
/// Epic Employees and Contractors
NotForLicensees,
/// Epic Employees only
NoRedist,
}
/// <summary>
/// A unit of code compilation and linking.
/// </summary>
public abstract class UEBuildModule
{
/// <summary>
/// Checks what distribution level a particular path should have by checking for key folders anywhere in it
/// </summary>
public static UEBuildModuleDistribution GetModuleDistributionLevelBasedOnLocation(string FilePath)
{
// Get full path to ensure all folder separators are the same
FilePath = Path.GetFullPath(FilePath);
// Check from highest to lowest (don't actually need to check 'Everyone')
for (UEBuildModuleDistribution DistributionLevel = UEBuildModuleDistribution.NoRedist; DistributionLevel > UEBuildModuleDistribution.Public; DistributionLevel--)
{
string DistributionFolderName = String.Format("{0}{1}{0}", Path.DirectorySeparatorChar, DistributionLevel.ToString());
if (FilePath.IndexOf(DistributionFolderName, StringComparison.InvariantCultureIgnoreCase) >= 0)
{
return DistributionLevel;
}
if (DistributionLevel == UEBuildModuleDistribution.NotForLicensees)
{
// Extra checks for PS4 and XboxOne folders, which are equivalent to NotForLicensees
string PS4FolderName = String.Format("{0}ps4{0}", Path.DirectorySeparatorChar);
string XboxFolderName = String.Format("{0}xboxone{0}", Path.DirectorySeparatorChar);
if (FilePath.IndexOf(PS4FolderName, StringComparison.InvariantCultureIgnoreCase) >= 0
|| FilePath.IndexOf(XboxFolderName, StringComparison.InvariantCultureIgnoreCase) >= 0)
{
return UEBuildModuleDistribution.NotForLicensees;
}
}
}
return UEBuildModuleDistribution.Public;
}
/// <summary>
/// Determines the distribution level of a module based on its directory and includes.
/// </summary>
public UEBuildModuleDistribution DetermineDistributionLevel()
{
List<string> PathsToCheck = new List<string>();
PathsToCheck.Add(ModuleDirectory.FullName);
PathsToCheck.AddRange(PublicIncludePaths);
PathsToCheck.AddRange(PrivateIncludePaths);
// Not sure if these two are necessary as paths will usually be in basic includes too
PathsToCheck.AddRange(PublicSystemIncludePaths);
PathsToCheck.AddRange(PublicLibraryPaths);
UEBuildModuleDistribution DistributionLevel = UEBuildModuleDistribution.Public;
if (!Rules.bOutputPubliclyDistributable)
{
// Keep checking as long as we haven't reached the maximum level
for (int PathIndex = 0; PathIndex < PathsToCheck.Count && DistributionLevel != UEBuildModuleDistribution.NoRedist; ++PathIndex)
{
DistributionLevel = Utils.Max(DistributionLevel, UEBuildModule.GetModuleDistributionLevelBasedOnLocation(PathsToCheck[PathIndex]));
}
}
return DistributionLevel;
}
/// <summary>
/// Converts an optional string list parameter to a well-defined hash set.
/// </summary>
protected static HashSet<string> HashSetFromOptionalEnumerableStringParameter(IEnumerable<string> InEnumerableStrings)
{
return InEnumerableStrings == null ? new HashSet<string>() : new HashSet<string>(InEnumerableStrings);
}
/// <summary>
/// The name that uniquely identifies the module.
/// </summary>
public readonly string Name;
/// <summary>
/// The type of module being built. Used to switch between debug/development and precompiled/source configurations.
/// </summary>
public UHTModuleType Type;
/// <summary>
/// The rules for this module
/// </summary>
public ModuleRules Rules;
/// <summary>
/// Path to the module directory
/// </summary>
public readonly DirectoryReference ModuleDirectory;
/// <summary>
/// Is this module allowed to be redistributed.
/// </summary>
private readonly bool? IsRedistributableOverride;
/// <summary>
/// The name of the .Build.cs file this module was created from, if any
/// </summary>
public FileReference RulesFile;
/// <summary>
/// The binary the module will be linked into for the current target. Only set after UEBuildBinary.BindModules is called.
/// </summary>
public UEBuildBinary Binary = null;
/// <summary>
/// Include path for this module's base directory, relative to the Engine/Source directory
/// </summary>
protected string NormalizedModuleIncludePath;
/// <summary>
/// The name of the _API define for this module
/// </summary>
protected readonly string ModuleApiDefine;
protected readonly HashSet<string> PublicDefinitions;
protected readonly HashSet<string> PublicIncludePaths;
protected readonly HashSet<string> PrivateIncludePaths;
protected readonly HashSet<string> PublicSystemIncludePaths;
protected readonly HashSet<string> PublicLibraryPaths;
protected readonly HashSet<string> PublicAdditionalLibraries;
protected readonly HashSet<string> PublicFrameworks;
protected readonly HashSet<string> PublicWeakFrameworks;
protected readonly HashSet<UEBuildFramework> PublicAdditionalFrameworks;
protected readonly HashSet<string> PublicAdditionalShadowFiles;
protected readonly HashSet<UEBuildBundleResource> PublicAdditionalBundleResources;
/// <summary>
/// Names of modules with header files that this module's public interface needs access to.
/// </summary>
protected List<UEBuildModule> PublicIncludePathModules;
/// <summary>
/// Names of modules that this module's public interface depends on.
/// </summary>
protected List<UEBuildModule> PublicDependencyModules;
/// <summary>
/// Names of DLLs that this module should delay load
/// </summary>
protected HashSet<string> PublicDelayLoadDLLs;
/// <summary>
/// Names of modules with header files that this module's private implementation needs access to.
/// </summary>
protected List<UEBuildModule> PrivateIncludePathModules;
/// <summary>
/// Names of modules that this module's private implementation depends on.
/// </summary>
protected List<UEBuildModule> PrivateDependencyModules;
/// <summary>
/// Extra modules this module may require at run time
/// </summary>
protected List<UEBuildModule> DynamicallyLoadedModules;
/// <summary>
/// Extra modules this module may require at run time, that are on behalf of another platform (i.e. shader formats and the like)
/// </summary>
protected List<UEBuildModule> PlatformSpecificDynamicallyLoadedModules;
/// <summary>
/// Files which this module depends on at runtime.
/// </summary>
public RuntimeDependencyList RuntimeDependencies;
/// <summary>
/// Returns a list of this module's immediate dependencies.
/// </summary>
/// <returns>An enumerable containing the dependencies of the module.</returns>
public IEnumerable<UEBuildModule> GetDirectDependencyModules()
{
return PublicDependencyModules.Concat(PrivateDependencyModules).Concat(DynamicallyLoadedModules).Concat(PlatformSpecificDynamicallyLoadedModules);
}
public UEBuildModule(
string InName,
UHTModuleType InType,
DirectoryReference InModuleDirectory,
ModuleRules InRules,
FileReference InRulesFile
)
{
Name = InName;
Type = InType;
ModuleDirectory = InModuleDirectory;
Rules = InRules;
RulesFile = InRulesFile;
NormalizedModuleIncludePath = Utils.CleanDirectorySeparators(ModuleDirectory.MakeRelativeTo(UnrealBuildTool.EngineSourceDirectory), '/');
ModuleApiDefine = Name.ToUpperInvariant() + "_API";
PublicDefinitions = HashSetFromOptionalEnumerableStringParameter(InRules.Definitions);
PublicIncludePaths = HashSetFromOptionalEnumerableStringParameter(InRules.PublicIncludePaths);
PublicSystemIncludePaths = HashSetFromOptionalEnumerableStringParameter(InRules.PublicSystemIncludePaths);
PublicLibraryPaths = HashSetFromOptionalEnumerableStringParameter(InRules.PublicLibraryPaths);
PublicAdditionalLibraries = HashSetFromOptionalEnumerableStringParameter(InRules.PublicAdditionalLibraries);
PublicFrameworks = HashSetFromOptionalEnumerableStringParameter(InRules.PublicFrameworks);
PublicWeakFrameworks = HashSetFromOptionalEnumerableStringParameter(InRules.PublicWeakFrameworks);
PublicAdditionalFrameworks = InRules.PublicAdditionalFrameworks == null ? new HashSet<UEBuildFramework>() : new HashSet<UEBuildFramework>(InRules.PublicAdditionalFrameworks);
PublicAdditionalShadowFiles = HashSetFromOptionalEnumerableStringParameter(InRules.PublicAdditionalShadowFiles);
PublicAdditionalBundleResources = InRules.AdditionalBundleResources == null ? new HashSet<UEBuildBundleResource>() : new HashSet<UEBuildBundleResource>(InRules.AdditionalBundleResources);
PublicDelayLoadDLLs = HashSetFromOptionalEnumerableStringParameter(InRules.PublicDelayLoadDLLs);
PrivateIncludePaths = HashSetFromOptionalEnumerableStringParameter(InRules.PrivateIncludePaths);
RuntimeDependencies = (InRules.RuntimeDependencies == null) ? new RuntimeDependencyList() : new RuntimeDependencyList(InRules.RuntimeDependencies);
IsRedistributableOverride = InRules.IsRedistributableOverride;
}
/// <summary>
/// Determines whether this module has a circular dependency on the given module
/// </summary>
public bool HasCircularDependencyOn(string ModuleName)
{
return Rules.CircularlyReferencedDependentModules.Contains(ModuleName);
}
/// <summary>
/// Find all the modules which affect the public compile environment. Searches through
/// </summary>
/// <param name="Modules"></param>
/// <param name="bIncludePathsOnly"></param>
/// <param name="DependencyModules"></param>
protected void FindModulesInPublicCompileEnvironment(List<UEBuildModule> Modules, Dictionary<UEBuildModule, bool> ModuleToIncludePathsOnlyFlag)
{
//
bool bModuleIncludePathsOnly;
if (!ModuleToIncludePathsOnlyFlag.TryGetValue(this, out bModuleIncludePathsOnly))
{
Modules.Add(this);
}
else if (!bModuleIncludePathsOnly)
{
return;
}
ModuleToIncludePathsOnlyFlag[this] = false;
foreach (UEBuildModule DependencyModule in PublicDependencyModules)
{
DependencyModule.FindModulesInPublicCompileEnvironment(Modules, ModuleToIncludePathsOnlyFlag);
}
// Now add an include paths from modules with header files that we need access to, but won't necessarily be importing
foreach (UEBuildModule IncludePathModule in PublicIncludePathModules)
{
IncludePathModule.FindIncludePathModulesInPublicCompileEnvironment(Modules, ModuleToIncludePathsOnlyFlag);
}
}
/// <summary>
/// Find all the modules which affect the public compile environment. Searches through
/// </summary>
/// <param name="Modules"></param>
/// <param name="bIncludePathsOnly"></param>
/// <param name="DependencyModules"></param>
protected void FindIncludePathModulesInPublicCompileEnvironment(List<UEBuildModule> Modules, Dictionary<UEBuildModule, bool> ModuleToIncludePathsOnlyFlag)
{
if (!ModuleToIncludePathsOnlyFlag.ContainsKey(this))
{
// Add this module to the list
Modules.Add(this);
ModuleToIncludePathsOnlyFlag.Add(this, true);
// Include any of its public include path modules in the compile environment too
foreach (UEBuildModule IncludePathModule in PublicIncludePathModules)
{
IncludePathModule.FindIncludePathModulesInPublicCompileEnvironment(Modules, ModuleToIncludePathsOnlyFlag);
}
}
}
/// <summary>
/// Sets up the environment for compiling any module that includes the public interface of this module.
/// </summary>
public void AddModuleToCompileEnvironment(
UEBuildBinary SourceBinary,
bool bIncludePathsOnly,
HashSet<string> IncludePaths,
HashSet<string> SystemIncludePaths,
List<string> Definitions,
List<UEBuildFramework> AdditionalFrameworks
)
{
// Add this module's public include paths and definitions.
AddIncludePathsWithChecks(IncludePaths, PublicIncludePaths);
AddIncludePathsWithChecks(SystemIncludePaths, PublicSystemIncludePaths);
Definitions.AddRange(PublicDefinitions);
// If this module is being built into a DLL or EXE, set up an IMPORTS or EXPORTS definition for it.
if (Binary == null)
{
// If we're referencing include paths for a module that's not being built, we don't actually need to import anything from it, but we need to avoid barfing when
// the compiler encounters an _API define. We also want to avoid changing the compile environment in cases where the module happens to be compiled because it's a dependency
// of something else, which cause a fall-through to the code below and set up an empty _API define.
if (bIncludePathsOnly)
{
Log.TraceVerbose("{0}: Include paths only for {1} (no binary)", SourceBinary.Config.OutputFilePaths[0].GetFileNameWithoutExtension(), Name);
Definitions.Add(ModuleApiDefine + "=");
}
}
else
{
FileReference BinaryPath = Binary.Config.OutputFilePaths[0];
FileReference SourceBinaryPath = SourceBinary.Config.OutputFilePaths[0];
if (ProjectFileGenerator.bGenerateProjectFiles || (Binary.Config.Type == UEBuildBinaryType.StaticLibrary))
{
// When generating IntelliSense files, never add dllimport/dllexport specifiers as it
// simply confuses the compiler
Definitions.Add(ModuleApiDefine + "=");
}
else if (Binary == SourceBinary)
{
if (Binary.Config.bAllowExports)
{
Log.TraceVerbose("{0}: Exporting {1} from {2}", SourceBinaryPath.GetFileNameWithoutExtension(), Name, BinaryPath.GetFileNameWithoutExtension());
Definitions.Add(ModuleApiDefine + "=DLLEXPORT");
}
else
{
Log.TraceVerbose("{0}: Not importing/exporting {1} (binary: {2})", SourceBinaryPath.GetFileNameWithoutExtension(), Name, BinaryPath.GetFileNameWithoutExtension());
Definitions.Add(ModuleApiDefine + "=");
}
}
else
{
// @todo SharedPCH: Public headers included from modules that are not importing the module of that public header, seems invalid.
// Those public headers have no business having APIs in them. OnlineSubsystem has some public headers like this. Without changing
// this, we need to suppress warnings at compile time.
if (bIncludePathsOnly)
{
Log.TraceVerbose("{0}: Include paths only for {1} (binary: {2})", SourceBinaryPath.GetFileNameWithoutExtension(), Name, BinaryPath.GetFileNameWithoutExtension());
Definitions.Add(ModuleApiDefine + "=");
}
else if (Binary.Config.bAllowExports)
{
Log.TraceVerbose("{0}: Importing {1} from {2}", SourceBinaryPath.GetFileNameWithoutExtension(), Name, BinaryPath.GetFileNameWithoutExtension());
Definitions.Add(ModuleApiDefine + "=DLLIMPORT");
}
else
{
Log.TraceVerbose("{0}: Not importing/exporting {1} (binary: {2})", SourceBinaryPath.GetFileNameWithoutExtension(), Name, BinaryPath.GetFileNameWithoutExtension());
Definitions.Add(ModuleApiDefine + "=");
}
}
}
// Add the module's directory to the include path, so we can root #includes to it
IncludePaths.Add(NormalizedModuleIncludePath);
// Add the additional frameworks so that the compiler can know about their #include paths
AdditionalFrameworks.AddRange(PublicAdditionalFrameworks);
// Remember the module so we can refer to it when needed
foreach (UEBuildFramework Framework in PublicAdditionalFrameworks)
{
Framework.OwningModule = this;
}
}
static Regex VCMacroRegex = new Regex(@"\$\([A-Za-z0-9_]+\)");
/// <summary>
/// Checks if path contains a VC macro
/// </summary>
protected bool DoesPathContainVCMacro(string Path)
{
return VCMacroRegex.IsMatch(Path);
}
/// <summary>
/// Adds PathsToAdd to IncludePaths, performing path normalization and ignoring duplicates.
/// </summary>
protected void AddIncludePathsWithChecks(HashSet<string> IncludePaths, HashSet<string> PathsToAdd)
{
if (ProjectFileGenerator.bGenerateProjectFiles)
{
// Extra checks are switched off for IntelliSense generation as they provide
// no additional value and cause performance impact.
IncludePaths.UnionWith(PathsToAdd);
}
else
{
foreach (string Path in PathsToAdd)
{
string NormalizedPath = Path.TrimEnd('/');
// If path doesn't exist, it may contain VC macro (which is passed directly to and expanded by compiler).
if (Directory.Exists(NormalizedPath) || DoesPathContainVCMacro(NormalizedPath))
{
IncludePaths.Add(NormalizedPath);
}
}
}
}
/// <summary>
/// Sets up the environment for compiling this module.
/// </summary>
protected virtual void SetupPrivateCompileEnvironment(
HashSet<string> IncludePaths,
HashSet<string> SystemIncludePaths,
List<string> Definitions,
List<UEBuildFramework> AdditionalFrameworks
)
{
HashSet<UEBuildModule> VisitedModules = new HashSet<UEBuildModule>();
if (this.Type.IsGameModule())
{
Definitions.Add("DEPRECATED_FORGAME=DEPRECATED");
}
// Add this module's private include paths and definitions.
AddIncludePathsWithChecks(IncludePaths, PrivateIncludePaths);
// Find all the modules that are part of the public compile environment for this module.
List<UEBuildModule> Modules = new List<UEBuildModule>();
Dictionary<UEBuildModule, bool> ModuleToIncludePathsOnlyFlag = new Dictionary<UEBuildModule, bool>();
FindModulesInPublicCompileEnvironment(Modules, ModuleToIncludePathsOnlyFlag);
// Add in all the modules that are private dependencies
foreach (UEBuildModule DependencyModule in PrivateDependencyModules)
{
DependencyModule.FindModulesInPublicCompileEnvironment(Modules, ModuleToIncludePathsOnlyFlag);
}
// And finally add in all the modules that are include path only dependencies
foreach (UEBuildModule IncludePathModule in PrivateIncludePathModules)
{
IncludePathModule.FindIncludePathModulesInPublicCompileEnvironment(Modules, ModuleToIncludePathsOnlyFlag);
}
// Now set up the compile environment for the modules in the original order that we encountered them
foreach (UEBuildModule Module in Modules)
{
Module.AddModuleToCompileEnvironment(Binary, ModuleToIncludePathsOnlyFlag[Module], IncludePaths, SystemIncludePaths, Definitions, AdditionalFrameworks);
}
}
/// <summary>
/// Sets up the environment for linking any module that includes the public interface of this module.
/// </summary>
protected virtual void SetupPublicLinkEnvironment(
UEBuildBinary SourceBinary,
List<string> LibraryPaths,
List<string> AdditionalLibraries,
List<string> Frameworks,
List<string> WeakFrameworks,
List<UEBuildFramework> AdditionalFrameworks,
List<string> AdditionalShadowFiles,
List<UEBuildBundleResource> AdditionalBundleResources,
List<string> DelayLoadDLLs,
List<UEBuildBinary> BinaryDependencies,
HashSet<UEBuildModule> VisitedModules
)
{
// There may be circular dependencies in compile dependencies, so we need to avoid reentrance.
if (VisitedModules.Add(this))
{
// Add this module's binary to the binary dependencies.
if (Binary != null
&& Binary != SourceBinary
&& !BinaryDependencies.Contains(Binary))
{
BinaryDependencies.Add(Binary);
}
// If this module belongs to a static library that we are not currently building, recursively add the link environment settings for all of its dependencies too.
// Keep doing this until we reach a module that is not part of a static library (or external module, since they have no associated binary).
// Static libraries do not contain the symbols for their dependencies, so we need to recursively gather them to be linked into other binary types.
bool bIsBuildingAStaticLibrary = (SourceBinary != null && SourceBinary.Config.Type == UEBuildBinaryType.StaticLibrary);
bool bIsModuleBinaryAStaticLibrary = (Binary != null && Binary.Config.Type == UEBuildBinaryType.StaticLibrary);
if (!bIsBuildingAStaticLibrary && bIsModuleBinaryAStaticLibrary)
{
// Gather all dependencies and recursively call SetupPublicLinkEnvironmnet
List<UEBuildModule> AllDependencyModules = new List<UEBuildModule>();
AllDependencyModules.AddRange(PrivateDependencyModules);
AllDependencyModules.AddRange(PublicDependencyModules);
foreach (UEBuildModule DependencyModule in AllDependencyModules)
{
bool bIsExternalModule = (DependencyModule as UEBuildExternalModule != null);
bool bIsInStaticLibrary = (DependencyModule.Binary != null && DependencyModule.Binary.Config.Type == UEBuildBinaryType.StaticLibrary);
if (bIsExternalModule || bIsInStaticLibrary)
{
DependencyModule.SetupPublicLinkEnvironment(SourceBinary, LibraryPaths, AdditionalLibraries, Frameworks, WeakFrameworks,
AdditionalFrameworks, AdditionalShadowFiles, AdditionalBundleResources, DelayLoadDLLs, BinaryDependencies, VisitedModules);
}
}
}
// Add this module's public include library paths and additional libraries.
LibraryPaths.AddRange(PublicLibraryPaths);
AdditionalLibraries.AddRange(PublicAdditionalLibraries);
Frameworks.AddRange(PublicFrameworks);
WeakFrameworks.AddRange(PublicWeakFrameworks);
AdditionalBundleResources.AddRange(PublicAdditionalBundleResources);
// Remember the module so we can refer to it when needed
foreach (UEBuildFramework Framework in PublicAdditionalFrameworks)
{
Framework.OwningModule = this;
}
AdditionalFrameworks.AddRange(PublicAdditionalFrameworks);
AdditionalShadowFiles.AddRange(PublicAdditionalShadowFiles);
DelayLoadDLLs.AddRange(PublicDelayLoadDLLs);
}
}
/// <summary>
/// Sets up the environment for linking this module.
/// </summary>
public virtual void SetupPrivateLinkEnvironment(
UEBuildBinary SourceBinary,
LinkEnvironment LinkEnvironment,
List<UEBuildBinary> BinaryDependencies,
HashSet<UEBuildModule> VisitedModules
)
{
// Allow the module's public dependencies to add library paths and additional libraries to the link environment.
SetupPublicLinkEnvironment(SourceBinary, LinkEnvironment.Config.LibraryPaths, LinkEnvironment.Config.AdditionalLibraries, LinkEnvironment.Config.Frameworks, LinkEnvironment.Config.WeakFrameworks,
LinkEnvironment.Config.AdditionalFrameworks, LinkEnvironment.Config.AdditionalShadowFiles, LinkEnvironment.Config.AdditionalBundleResources, LinkEnvironment.Config.DelayLoadDLLs, BinaryDependencies, VisitedModules);
// Also allow the module's public and private dependencies to modify the link environment.
List<UEBuildModule> AllDependencyModules = new List<UEBuildModule>();
AllDependencyModules.AddRange(PrivateDependencyModules);
AllDependencyModules.AddRange(PublicDependencyModules);
foreach (UEBuildModule DependencyModule in AllDependencyModules)
{
DependencyModule.SetupPublicLinkEnvironment(SourceBinary, LinkEnvironment.Config.LibraryPaths, LinkEnvironment.Config.AdditionalLibraries, LinkEnvironment.Config.Frameworks, LinkEnvironment.Config.WeakFrameworks,
LinkEnvironment.Config.AdditionalFrameworks, LinkEnvironment.Config.AdditionalShadowFiles, LinkEnvironment.Config.AdditionalBundleResources, LinkEnvironment.Config.DelayLoadDLLs, BinaryDependencies, VisitedModules);
}
}
/// <summary>
/// Compiles the module, and returns a list of files output by the compiler.
/// </summary>
public abstract List<FileItem> Compile(UEBuildTarget Target, UEToolChain ToolChain, CPPEnvironment GlobalCompileEnvironment, CPPEnvironment CompileEnvironment);
// Object interface.
public override string ToString()
{
return Name;
}
/// <summary>
/// Finds the modules referenced by this module which have not yet been bound to a binary
/// </summary>
/// <returns>List of unbound modules</returns>
public List<UEBuildModule> GetUnboundReferences()
{
List<UEBuildModule> Modules = new List<UEBuildModule>();
Modules.AddRange(PrivateDependencyModules.Where(x => x.Binary == null));
Modules.AddRange(PublicDependencyModules.Where(x => x.Binary == null));
return Modules;
}
[DebuggerDisplay("{Index}: {Module}")]
public class ModuleIndexPair
{
public UEBuildModule Module;
public int Index;
}
/// <summary>
/// Gets all of the modules referenced by this module
/// </summary>
/// <param name="ReferencedModules">Hash of all referenced modules with their addition index.</param>
/// <param name="IgnoreReferencedModules">Hashset used to ignore modules which are already added to the list</param>
/// <param name="bIncludeDynamicallyLoaded">True if dynamically loaded modules (and all of their dependent modules) should be included.</param>
/// <param name="bForceCircular">True if circular dependencies should be processed</param>
/// <param name="bOnlyDirectDependencies">True to return only this module's direct dependencies</param>
public virtual void GetAllDependencyModules(List<UEBuildModule> ReferencedModules, HashSet<UEBuildModule> IgnoreReferencedModules, bool bIncludeDynamicallyLoaded, bool bForceCircular, bool bOnlyDirectDependencies)
{
}
/// <summary>
/// Gets all of the modules precompiled along with this module
/// </summary>
/// <param name="Modules">Set of all the precompiled modules</param>
public virtual void RecursivelyAddPrecompiledModules(List<UEBuildModule> Modules)
{
}
/// <summary>
/// Creates all the modules required for this target
/// </summary>
public void RecursivelyCreateModules(UEBuildTarget Target)
{
// Create all the include path modules. These modules may not be added to the target (and we don't process their dependencies), but they need
// to be created to set up their compile environment.
RecursivelyCreateIncludePathModulesByName(Target, Rules.PublicIncludePathModuleNames, ref PublicIncludePathModules);
RecursivelyCreateIncludePathModulesByName(Target, Rules.PrivateIncludePathModuleNames, ref PrivateIncludePathModules);
// Create all the dependency modules
RecursivelyCreateModulesByName(Target, Rules.PublicDependencyModuleNames, ref PublicDependencyModules);
RecursivelyCreateModulesByName(Target, Rules.PrivateDependencyModuleNames, ref PrivateDependencyModules);
RecursivelyCreateModulesByName(Target, Rules.DynamicallyLoadedModuleNames, ref DynamicallyLoadedModules);
RecursivelyCreateModulesByName(Target, Rules.PlatformSpecificDynamicallyLoadedModuleNames, ref PlatformSpecificDynamicallyLoadedModules);
}
private static void RecursivelyCreateModulesByName(UEBuildTarget Target, List<string> ModuleNames, ref List<UEBuildModule> Modules)
{
// Check whether the module list is already set. We set this immediately (via the ref) to avoid infinite recursion.
if (Modules == null)
{
Modules = new List<UEBuildModule>();
foreach (string ModuleName in ModuleNames)
{
UEBuildModule Module = Target.FindOrCreateModuleByName(ModuleName);
if (!Modules.Contains(Module))
{
Module.RecursivelyCreateModules(Target);
Modules.Add(Module);
}
}
}
}
private static void RecursivelyCreateIncludePathModulesByName(UEBuildTarget Target, List<string> ModuleNames, ref List<UEBuildModule> Modules)
{
// Check whether the module list is already set. We set this immediately (via the ref) to avoid infinite recursion.
if (Modules == null)
{
Modules = new List<UEBuildModule>();
foreach (string ModuleName in ModuleNames)
{
UEBuildModule Module = Target.FindOrCreateModuleByName(ModuleName);
RecursivelyCreateIncludePathModulesByName(Target, Module.Rules.PublicIncludePathModuleNames, ref Module.PublicIncludePathModules);
Modules.Add(Module);
}
}
}
};
/// <summary>
/// A module that is never compiled by us, and is only used to group include paths and libraries into a dependency unit.
/// </summary>
class UEBuildExternalModule : UEBuildModule
{
public UEBuildExternalModule(
UHTModuleType InType,
string InName,
DirectoryReference InModuleDirectory,
ModuleRules InRules,
FileReference InRulesFile
)
: base(
InType: InType,
InName: InName,
InModuleDirectory: InModuleDirectory,
InRules: InRules,
InRulesFile: InRulesFile
)
{
}
// UEBuildModule interface.
public override List<FileItem> Compile(UEBuildTarget Target, UEToolChain ToolChain, CPPEnvironment GlobalCompileEnvironment, CPPEnvironment CompileEnvironment)
{
return new List<FileItem>();
}
};
/// <summary>
/// A module that is compiled from C++ code.
/// </summary>
public class UEBuildModuleCPP : UEBuildModule
{
public class AutoGenerateCppInfoClass
{
public class BuildInfoClass
{
/// <summary>
/// The wildcard of the *.generated.cpp file which was generated for the module
/// </summary>
public readonly string FileWildcard;
public BuildInfoClass(string InWildcard)
{
Debug.Assert(InWildcard != null);
FileWildcard = InWildcard;
}
}
/// <summary>
/// Information about how to build the .generated.cpp files. If this is null, then we're not building .generated.cpp files for this module.
/// </summary>
public BuildInfoClass BuildInfo;
public AutoGenerateCppInfoClass(BuildInfoClass InBuildInfo)
{
BuildInfo = InBuildInfo;
}
}
/// <summary>
/// Information about the .generated.cpp file. If this is null then this module doesn't have any UHT-produced code.
/// </summary>
public AutoGenerateCppInfoClass AutoGenerateCppInfo = null;
public class SourceFilesClass
{
public readonly List<FileItem> MissingFiles = new List<FileItem>();
public readonly List<FileItem> CPPFiles = new List<FileItem>();
public readonly List<FileItem> CFiles = new List<FileItem>();
public readonly List<FileItem> CCFiles = new List<FileItem>();
public readonly List<FileItem> MMFiles = new List<FileItem>();
public readonly List<FileItem> RCFiles = new List<FileItem>();
public readonly List<FileItem> OtherFiles = new List<FileItem>();
public int Count
{
get
{
return MissingFiles.Count +
CPPFiles.Count +
CFiles.Count +
CCFiles.Count +
MMFiles.Count +
RCFiles.Count +
OtherFiles.Count;
}
}
/// <summary>
/// Copy from list to list helper.
/// </summary>
/// <param name="From">Source list.</param>
/// <param name="To">Destination list.</param>
private static void CopyFromListToList(List<FileItem> From, List<FileItem> To)
{
To.Clear();
To.AddRange(From);
}
/// <summary>
/// Copies file lists from other SourceFilesClass to this.
/// </summary>
/// <param name="Other">Source object.</param>
public void CopyFrom(SourceFilesClass Other)
{
CopyFromListToList(Other.MissingFiles, MissingFiles);
CopyFromListToList(Other.CPPFiles, CPPFiles);
CopyFromListToList(Other.CFiles, CFiles);
CopyFromListToList(Other.CCFiles, CCFiles);
CopyFromListToList(Other.MMFiles, MMFiles);
CopyFromListToList(Other.RCFiles, RCFiles);
CopyFromListToList(Other.OtherFiles, OtherFiles);
}
}
/// <summary>
/// Adds additional source cpp files for this module.
/// </summary>
/// <param name="Files">Files to add.</param>
public void AddAdditionalCPPFiles(IEnumerable<FileItem> Files)
{
SourceFilesToBuild.CPPFiles.AddRange(Files);
}
/// <summary>
/// A list of the absolute paths of source files to be built in this module.
/// </summary>
public readonly SourceFilesClass SourceFilesToBuild = new SourceFilesClass();
/// <summary>
/// A list of the source files that were found for the module.
/// </summary>
public readonly SourceFilesClass SourceFilesFound = new SourceFilesClass();
/// <summary>
/// The directory for this module's generated code
/// </summary>
public readonly DirectoryReference GeneratedCodeDirectory;
/// <summary>
/// The preprocessor definitions used to compile this module's private implementation.
/// </summary>
HashSet<string> Definitions;
/// When set, allows this module to report compiler definitions and include paths for Intellisense
IntelliSenseGatherer IntelliSenseGatherer;
public List<string> IncludeSearchPaths = new List<string>();
public class ProcessedDependenciesClass
{
/// <summary>
/// The file, if any, which is used as the unique PCH for this module
/// </summary>
public FileItem UniquePCHHeaderFile = null;
}
/// <summary>
/// The processed dependencies for the class
/// </summary>
public ProcessedDependenciesClass ProcessedDependencies = null;
/// <summary>
/// Hack to skip adding definitions to compile environment. They will be baked into source code by external code.
/// </summary>
public bool bSkipDefinitionsForCompileEnvironment = false;
public int FindNumberOfGeneratedCppFiles()
{
return ((null == GeneratedCodeDirectory) || !GeneratedCodeDirectory.Exists()) ? 0
: (GeneratedCodeDirectory.EnumerateFileReferences("*.generated.*.cpp", SearchOption.AllDirectories).Count()
+ GeneratedCodeDirectory.EnumerateFileReferences("*.generated.cpp", SearchOption.AllDirectories).Count());
}
/// <summary>
/// Categorizes source files into per-extension buckets
/// </summary>
private static void CategorizeSourceFiles(IEnumerable<FileItem> InSourceFiles, SourceFilesClass OutSourceFiles)
{
foreach (FileItem SourceFile in InSourceFiles)
{
string Extension = Path.GetExtension(SourceFile.AbsolutePath).ToUpperInvariant();
if (!SourceFile.bExists)
{
OutSourceFiles.MissingFiles.Add(SourceFile);
}
else if (Extension == ".CPP")
{
OutSourceFiles.CPPFiles.Add(SourceFile);
}
else if (Extension == ".C")
{
OutSourceFiles.CFiles.Add(SourceFile);
}
else if (Extension == ".CC")
{
OutSourceFiles.CCFiles.Add(SourceFile);
}
else if (Extension == ".MM" || Extension == ".M")
{
OutSourceFiles.MMFiles.Add(SourceFile);
}
else if (Extension == ".RC")
{
OutSourceFiles.RCFiles.Add(SourceFile);
}
else
{
OutSourceFiles.OtherFiles.Add(SourceFile);
}
}
}
public UEBuildModuleCPP(
string InName,
UHTModuleType InType,
DirectoryReference InModuleDirectory,
DirectoryReference InGeneratedCodeDirectory,
IntelliSenseGatherer InIntelliSenseGatherer,
IEnumerable<FileItem> InSourceFiles,
ModuleRules InRules,
bool bInBuildSourceFiles,
FileReference InRulesFile
)
: base(
InName,
InType,
InModuleDirectory,
InRules,
InRulesFile
)
{
GeneratedCodeDirectory = InGeneratedCodeDirectory;
IntelliSenseGatherer = InIntelliSenseGatherer;
CategorizeSourceFiles(InSourceFiles, SourceFilesFound);
if (bInBuildSourceFiles)
{
SourceFilesToBuild.CopyFrom(SourceFilesFound);
}
Definitions = HashSetFromOptionalEnumerableStringParameter(InRules.Definitions);
foreach (string Def in Definitions)
{
Log.TraceVerbose("Compile Env {0}: {1}", Name, Def);
}
}
// UEBuildModule interface.
public override List<FileItem> Compile(UEBuildTarget Target, UEToolChain ToolChain, CPPEnvironment GlobalCompileEnvironment, CPPEnvironment CompileEnvironment)
{
UEBuildPlatform BuildPlatform = UEBuildPlatform.GetBuildPlatformForCPPTargetPlatform(CompileEnvironment.Config.Target.Platform);
List<FileItem> LinkInputFiles = new List<FileItem>();
if (ProjectFileGenerator.bGenerateProjectFiles && IntelliSenseGatherer == null)
{
// Nothing to do for IntelliSense, bail out early
return LinkInputFiles;
}
CPPEnvironment ModuleCompileEnvironment = CreateModuleCompileEnvironment(Target, CompileEnvironment);
IncludeSearchPaths = ModuleCompileEnvironment.Config.CPPIncludeInfo.IncludePaths.ToList();
IncludeSearchPaths.AddRange(ModuleCompileEnvironment.Config.CPPIncludeInfo.SystemIncludePaths.ToList());
if (IntelliSenseGatherer != null)
{
// Update project file's set of preprocessor definitions and include paths
IntelliSenseGatherer.AddIntelliSensePreprocessorDefinitions(ModuleCompileEnvironment.Config.Definitions);
IntelliSenseGatherer.AddInteliiSenseIncludePaths(ModuleCompileEnvironment.Config.CPPIncludeInfo.SystemIncludePaths, bAddingSystemIncludes: true);
IntelliSenseGatherer.AddInteliiSenseIncludePaths(ModuleCompileEnvironment.Config.CPPIncludeInfo.IncludePaths, bAddingSystemIncludes: false);
// Bail out. We don't need to actually compile anything while generating project files.
return LinkInputFiles;
}
// Throw an error if the module's source file list referenced any non-existent files.
if (SourceFilesToBuild.MissingFiles.Count > 0)
{
throw new BuildException(
"UBT ERROR: Module \"{0}\" references non-existent files:\n{1} (perhaps a file was added to the project but not checked in)",
Name,
string.Join("\n", SourceFilesToBuild.MissingFiles.Select(M => M.AbsolutePath))
);
}
// For an executable or a static library do not use the default RC file -
// If the executable wants it, it will be in their source list anyway.
// The issue here is that when making a monolithic game, the processing
// of the other game modules will stomp the game-specific rc file.
if (Binary.Config.Type == UEBuildBinaryType.DynamicLinkLibrary)
{
// Add default PCLaunch.rc file if this module has no own resource file specified
if (SourceFilesToBuild.RCFiles.Count <= 0)
{
FileReference DefRC = FileReference.Combine(UnrealBuildTool.EngineSourceDirectory, "Runtime", "Launch", "Resources", "Windows", "PCLaunch.rc");
FileItem Item = FileItem.GetItemByFileReference(DefRC);
SourceFilesToBuild.RCFiles.Add(Item);
}
// Always compile in the API version resource separately. This is required for the module manager to detect compatible API versions.
FileReference ModuleVersionRC = FileReference.Combine(UnrealBuildTool.EngineSourceDirectory, "Runtime", "Core", "Resources", "Windows", "ModuleVersionResource.rc.inl");
FileItem ModuleVersionItem = FileItem.GetItemByFileReference(ModuleVersionRC);
if (!SourceFilesToBuild.RCFiles.Contains(ModuleVersionItem))
{
SourceFilesToBuild.RCFiles.Add(ModuleVersionItem);
}
}
{
// Process all of the header file dependencies for this module
this.CachePCHUsageForModuleSourceFiles(Target, ModuleCompileEnvironment);
// Make sure our RC files have cached includes.
foreach (FileItem RCFile in SourceFilesToBuild.RCFiles)
{
RCFile.CachedCPPIncludeInfo = ModuleCompileEnvironment.Config.CPPIncludeInfo;
}
}
// Check to see if this is an Engine module (including program or plugin modules). That is, the module is located under the "Engine" folder
bool IsPluginModule = ModuleDirectory.IsUnderDirectory(DirectoryReference.Combine(Target.ProjectDirectory, "Plugins"));
bool IsGameModule = !IsPluginModule && !ModuleDirectory.IsUnderDirectory(UnrealBuildTool.EngineDirectory);
// Should we force a precompiled header to be generated for this module? Usually, we only bother with a
// precompiled header if there are at least several source files in the module (after combining them for unity
// builds.) But for game modules, it can be convenient to always have a precompiled header to single-file
// changes to code is really quick to compile.
int MinFilesUsingPrecompiledHeader = BuildConfiguration.MinFilesUsingPrecompiledHeader;
if (Rules.MinFilesUsingPrecompiledHeaderOverride != 0)
{
MinFilesUsingPrecompiledHeader = Rules.MinFilesUsingPrecompiledHeaderOverride;
}
else if (IsGameModule && BuildConfiguration.bForcePrecompiledHeaderForGameModules)
{
// This is a game module with only a small number of source files, so go ahead and force a precompiled header
// to be generated to make incremental changes to source files as fast as possible for small projects.
MinFilesUsingPrecompiledHeader = 1;
}
// Engine modules will always use unity build mode unless MinSourceFilesForUnityBuildOverride is specified in
// the module rules file. By default, game modules only use unity of they have enough source files for that
// to be worthwhile. If you have a lot of small game modules, consider specifying MinSourceFilesForUnityBuildOverride=0
// in the modules that you don't typically iterate on source files in very frequently.
int MinSourceFilesForUnityBuild = 0;
if (Rules.MinSourceFilesForUnityBuildOverride != 0)
{
MinSourceFilesForUnityBuild = Rules.MinSourceFilesForUnityBuildOverride;
}
else if (IsGameModule)
{
// Game modules with only a small number of source files are usually better off having faster iteration times
// on single source file changes, so we forcibly disable unity build for those modules
MinSourceFilesForUnityBuild = BuildConfiguration.MinGameModuleSourceFilesForUnityBuild;
}
// Should we use unity build mode for this module?
bool bModuleUsesUnityBuild = false;
if (BuildConfiguration.bUseUnityBuild || BuildConfiguration.bForceUnityBuild)
{
if (BuildConfiguration.bForceUnityBuild)
{
Log.TraceVerbose("Module '{0}' using unity build mode (bForceUnityBuild enabled for this module)", this.Name);
bModuleUsesUnityBuild = true;
}
else if (Rules.bFasterWithoutUnity)
{
Log.TraceVerbose("Module '{0}' not using unity build mode (bFasterWithoutUnity enabled for this module)", this.Name);
bModuleUsesUnityBuild = false;
}
else if (SourceFilesToBuild.CPPFiles.Count < MinSourceFilesForUnityBuild)
{
Log.TraceVerbose("Module '{0}' not using unity build mode (module with fewer than {1} source files)", this.Name, MinSourceFilesForUnityBuild);
bModuleUsesUnityBuild = false;
}
else
{
Log.TraceVerbose("Module '{0}' using unity build mode (enabled in BuildConfiguration)", this.Name);
bModuleUsesUnityBuild = true;
}
}
else
{
Log.TraceVerbose("Module '{0}' not using unity build mode (disabled in BuildConfiguration)", this.Name);
}
// The environment with which to compile the CPP files
CPPEnvironment CPPCompileEnvironment = ModuleCompileEnvironment;
// Precompiled header support.
bool bWasModuleCodeCompiled = false;
if (BuildPlatform.ShouldUsePCHFiles(CompileEnvironment.Config.Target.Platform, CompileEnvironment.Config.Target.Configuration))
{
DateTime PCHGenTimerStart = DateTime.UtcNow;
// The code below will figure out whether this module will either use a "unique PCH" (private PCH that will only be included by
// this module's code files), or a "shared PCH" (potentially included by many code files in many modules.) Only one or the other
// will be used.
FileItem SharedPCHHeaderFile = null;
// In the case of a shared PCH, we also need to keep track of which module that PCH's header file is a member of
string SharedPCHModuleName = String.Empty;
if (BuildConfiguration.bUseSharedPCHs && CompileEnvironment.Config.bIsBuildingLibrary)
{
Log.TraceVerbose("Module '{0}' was not allowed to use Shared PCHs, because we're compiling to a library", this.Name);
}
bool bUseSharedPCHFiles = BuildConfiguration.bUseSharedPCHs && !CompileEnvironment.Config.bIsBuildingLibrary && GlobalCompileEnvironment.SharedPCHHeaderFiles.Count > 0;
if (bUseSharedPCHFiles)
{
FileReference SharingPCHHeaderFilePath = null;
bool bIsASharedPCHModule = bUseSharedPCHFiles && GlobalCompileEnvironment.SharedPCHHeaderFiles.Any(PCH => PCH.Module == this);
if (bIsASharedPCHModule)
{
SharingPCHHeaderFilePath = FileReference.Combine(UnrealBuildTool.EngineSourceDirectory, Rules.SharedPCHHeaderFile);
}
// We can't use a shared PCH file when compiling a module
// with exports, because the shared PCH can only have imports in it to work correctly.
bool bAllowSharedPCH = (Rules.PCHUsage == ModuleRules.PCHUsageMode.NoSharedPCHs) ? false : true;
bool bCanModuleUseOwnSharedPCH = bAllowSharedPCH && bIsASharedPCHModule && !Binary.Config.bAllowExports && ProcessedDependencies.UniquePCHHeaderFile.Reference == SharingPCHHeaderFilePath;
if (bAllowSharedPCH && (!bIsASharedPCHModule || bCanModuleUseOwnSharedPCH))
{
// Figure out which shared PCH tier we're in
List<UEBuildModule> ReferencedModules = new List<UEBuildModule>();
{
this.GetAllDependencyModules(ReferencedModules, new HashSet<UEBuildModule>(), bIncludeDynamicallyLoaded: false, bForceCircular: false, bOnlyDirectDependencies: true);
}
int LargestSharedPCHHeaderFileIndex = -1;
foreach (UEBuildModule DependencyModule in ReferencedModules)
{
// These Shared PCHs are ordered from least complex to most complex. We'll start at the last one and search backwards.
for (int SharedPCHHeaderFileIndex = GlobalCompileEnvironment.SharedPCHHeaderFiles.Count - 1; SharedPCHHeaderFileIndex > LargestSharedPCHHeaderFileIndex; --SharedPCHHeaderFileIndex)
{
SharedPCHHeaderInfo CurSharedPCHHeaderFile = GlobalCompileEnvironment.SharedPCHHeaderFiles[SharedPCHHeaderFileIndex];
if (DependencyModule == CurSharedPCHHeaderFile.Module ||
(bIsASharedPCHModule && CurSharedPCHHeaderFile.Module == this)) // If we ourselves are a shared PCH module, always at least use our own module as our shared PCH header if we can't find anything better
{
SharedPCHModuleName = CurSharedPCHHeaderFile.Module.Name;
SharedPCHHeaderFile = CurSharedPCHHeaderFile.PCHHeaderFile;
LargestSharedPCHHeaderFileIndex = SharedPCHHeaderFileIndex;
break;
}
}
if (LargestSharedPCHHeaderFileIndex == GlobalCompileEnvironment.SharedPCHHeaderFiles.Count - 1)
{
// We've determined that the module is using our most complex PCH header, so we can early-out
break;
}
}
// Did we not find a shared PCH header that is being included by this module? This could happen if the module is not including Core.h, even indirectly.
if (String.IsNullOrEmpty(SharedPCHModuleName))
{
throw new BuildException("Module {0} doesn't use a Shared PCH! Please add a dependency on a Shared PCH module to this module's dependency list", this.Name);
}
// Keep track of how many modules make use of this PCH for performance diagnostics
SharedPCHHeaderInfo LargestSharedPCHHeader = GlobalCompileEnvironment.SharedPCHHeaderFiles[LargestSharedPCHHeaderFileIndex];
++LargestSharedPCHHeader.NumModulesUsingThisPCH;
// Don't allow game modules to use engine PCHs in DebugGame - the optimization settings aren't correct.
// @todo: we should be creating shared PCHs ahead of time, and only using them if our settings match. as it is, the first modules compiled
// (which are currently plugins) get to call the shots for how the shared PCH gets built, and that might be a game plugin built in debug...
if (Target.Configuration == UnrealTargetConfiguration.DebugGame && SharedPCHHeaderFile.Reference.IsUnderDirectory(UnrealBuildTool.EngineDirectory) && !RulesFile.IsUnderDirectory(UnrealBuildTool.EngineDirectory))
{
SharedPCHModuleName = null;
SharedPCHHeaderFile = null;
}
}
else
{
Log.TraceVerbose("Module '{0}' cannot create or use Shared PCHs, because it needs its own private PCH", this.Name);
}
}
// The precompiled header environment for all source files in this module that use a precompiled header, if we even need one
PrecompileHeaderEnvironment ModulePCHEnvironment = null;
// If there was one header that was included first by enough C++ files, use it as the precompiled header.
// Only use precompiled headers for projects with enough files to make the PCH creation worthwhile.
if (SharedPCHHeaderFile != null || SourceFilesToBuild.CPPFiles.Count >= MinFilesUsingPrecompiledHeader)
{
FileItem PCHToUse;
if (SharedPCHHeaderFile != null)
{
ModulePCHEnvironment = ApplySharedPCH(GlobalCompileEnvironment, CompileEnvironment, ModuleCompileEnvironment, SourceFilesToBuild.CPPFiles, ref SharedPCHHeaderFile);
if (ModulePCHEnvironment != null)
{
// @todo SharedPCH: Ideally we would exhaustively check for a compatible compile environment (definitions, imports/exports, etc)
// Currently, it's possible for the shared PCH to be compiled differently depending on which module UBT happened to have
// include it first during the build phase. This could create problems with deterministic builds, or turn up compile
// errors unexpectedly due to compile environment differences.
Log.TraceVerbose("Module " + Name + " uses existing Shared PCH '" + ModulePCHEnvironment.PrecompiledHeaderIncludeFilename + "' (from module " + ModulePCHEnvironment.ModuleName + ")");
}
PCHToUse = SharedPCHHeaderFile;
}
else
{
PCHToUse = ProcessedDependencies.UniquePCHHeaderFile;
}
if (PCHToUse != null)
{
// Update all CPPFiles to point to the PCH
foreach (FileItem CPPFile in SourceFilesToBuild.CPPFiles)
{
CPPFile.PCHHeaderNameInCode = PCHToUse.AbsolutePath;
CPPFile.PrecompiledHeaderIncludeFilename = PCHToUse.Reference;
}
}
// A shared PCH was not already set up for us, so set one up.
if (ModulePCHEnvironment == null && SourceFilesToBuild.CPPFiles.Count > 0)
{
FileItem PCHHeaderFile = ProcessedDependencies.UniquePCHHeaderFile;
string PCHModuleName = this.Name;
if (SharedPCHHeaderFile != null)
{
PCHHeaderFile = SharedPCHHeaderFile;
PCHModuleName = SharedPCHModuleName;
}
string PCHHeaderNameInCode = SourceFilesToBuild.CPPFiles[0].PCHHeaderNameInCode;
ModulePCHEnvironment = new PrecompileHeaderEnvironment(PCHModuleName, PCHHeaderNameInCode, PCHHeaderFile, ModuleCompileEnvironment.Config.CLRMode, ModuleCompileEnvironment.Config.OptimizeCode);
if (SharedPCHHeaderFile != null)
{
// Add to list of shared PCH environments
GlobalCompileEnvironment.SharedPCHEnvironments.Add(ModulePCHEnvironment);
Log.TraceVerbose("Module " + Name + " uses new Shared PCH '" + ModulePCHEnvironment.PrecompiledHeaderIncludeFilename + "'");
}
else
{
Log.TraceVerbose("Module " + Name + " uses a Unique PCH '" + ModulePCHEnvironment.PrecompiledHeaderIncludeFilename + "'");
}
}
}
else
{
Log.TraceVerbose("Module " + Name + " doesn't use a Shared PCH, and only has " + SourceFilesToBuild.CPPFiles.Count.ToString() + " source file(s). No Unique PCH will be generated.");
}
// Compile the C++ source or the unity C++ files that use a PCH environment.
if (ModulePCHEnvironment != null)
{
// Setup a new compile environment for this module's source files. It's pretty much the exact same as the
// module's compile environment, except that it will include a PCH file.
CPPEnvironment ModulePCHCompileEnvironment = ModuleCompileEnvironment.DeepCopy();
ModulePCHCompileEnvironment.Config.PrecompiledHeaderAction = PrecompiledHeaderAction.Include;
ModulePCHCompileEnvironment.Config.PrecompiledHeaderIncludeFilename = ModulePCHEnvironment.PrecompiledHeaderIncludeFilename.Reference;
ModulePCHCompileEnvironment.Config.PCHHeaderNameInCode = ModulePCHEnvironment.PCHHeaderNameInCode;
if (SharedPCHHeaderFile != null)
{
// Shared PCH headers need to be force included, because we're basically forcing the module to use
// the precompiled header that we want, instead of the "first include" in each respective .cpp file
ModulePCHCompileEnvironment.Config.bForceIncludePrecompiledHeader = true;
}
List<FileItem> CPPFilesToBuild = SourceFilesToBuild.CPPFiles;
if (bModuleUsesUnityBuild)
{
// unity files generated for only the set of files which share the same PCH environment
CPPFilesToBuild = Unity.GenerateUnityCPPs(ToolChain, Target, CPPFilesToBuild, ModulePCHCompileEnvironment, Name);
}
// Check if there are enough unity files to warrant pch generation (and we haven't already generated the shared one)
if (ModulePCHEnvironment.PrecompiledHeaderFile == null)
{
if (SharedPCHHeaderFile != null || CPPFilesToBuild.Count >= MinFilesUsingPrecompiledHeader)
{
CPPOutput PCHOutput;
if (SharedPCHHeaderFile == null)
{
PCHOutput = PrecompileHeaderEnvironment.GeneratePCHCreationAction(
ToolChain,
Target,
CPPFilesToBuild[0].PCHHeaderNameInCode,
ModulePCHEnvironment.PrecompiledHeaderIncludeFilename,
ModuleCompileEnvironment,
ModuleCompileEnvironment.Config.OutputDirectory,
ModuleCompileEnvironment.Config.PCHOutputDirectory,
Name,
true);
}
else
{
UEBuildModuleCPP SharedPCHModule = (UEBuildModuleCPP)Target.FindOrCreateModuleByName(SharedPCHModuleName);
CPPEnvironment SharedPCHCompileEnvironment = GlobalCompileEnvironment.DeepCopy();
SharedPCHCompileEnvironment.Config.bEnableShadowVariableWarning = SharedPCHModule.Rules.bEnableShadowVariableWarnings;
List<UEBuildModule> Modules = new List<UEBuildModule>();
Dictionary<UEBuildModule, bool> ModuleToIncludePathsOnlyFlag = new Dictionary<UEBuildModule, bool>();
SharedPCHModule.FindModulesInPublicCompileEnvironment(Modules, ModuleToIncludePathsOnlyFlag);
foreach (UEBuildModule Module in Modules)
{
Module.AddModuleToCompileEnvironment(
Binary,
ModuleToIncludePathsOnlyFlag[Module],
SharedPCHCompileEnvironment.Config.CPPIncludeInfo.IncludePaths,
SharedPCHCompileEnvironment.Config.CPPIncludeInfo.SystemIncludePaths,
SharedPCHCompileEnvironment.Config.Definitions,
SharedPCHCompileEnvironment.Config.AdditionalFrameworks);
}
PCHOutput = PrecompileHeaderEnvironment.GeneratePCHCreationAction(
ToolChain,
Target,
CPPFilesToBuild[0].PCHHeaderNameInCode,
ModulePCHEnvironment.PrecompiledHeaderIncludeFilename,
SharedPCHCompileEnvironment,
DirectoryReference.Combine(CompileEnvironment.Config.OutputDirectory, "SharedPCHs"),
(CompileEnvironment.Config.PCHOutputDirectory == null) ? null : DirectoryReference.Combine(CompileEnvironment.Config.PCHOutputDirectory, "SharedPCHs"),
"Shared",
false);
}
ModulePCHEnvironment.PrecompiledHeaderFile = PCHOutput.PrecompiledHeaderFile;
ModulePCHEnvironment.OutputObjectFiles.Clear();
ModulePCHEnvironment.OutputObjectFiles.AddRange(PCHOutput.ObjectFiles);
}
else if (CPPFilesToBuild.Count < MinFilesUsingPrecompiledHeader)
{
Log.TraceVerbose("Module " + Name + " doesn't use a Shared PCH, and only has " + CPPFilesToBuild.Count.ToString() + " unity source file(s). No Unique PCH will be generated.");
}
}
if (ModulePCHEnvironment.PrecompiledHeaderFile != null)
{
// Link in the object files produced by creating the precompiled header.
LinkInputFiles.AddRange(ModulePCHEnvironment.OutputObjectFiles);
// if pch action was generated for the environment then use pch
ModulePCHCompileEnvironment.PrecompiledHeaderFile = ModulePCHEnvironment.PrecompiledHeaderFile;
// Use this compile environment from now on
CPPCompileEnvironment = ModulePCHCompileEnvironment;
}
// In unity pass files through CompileUnityFilesWithToolChain which can opt adaptive unity files out of optimization
if (bModuleUsesUnityBuild)
{
LinkInputFiles.AddRange(CompileUnityFilesWithToolChain(ToolChain, Target, CPPCompileEnvironment, CPPFilesToBuild, Name).ObjectFiles);
}
else
{
LinkInputFiles.AddRange(ToolChain.CompileCPPFiles(Target, CPPCompileEnvironment, CPPFilesToBuild, Name).ObjectFiles);
}
bWasModuleCodeCompiled = true;
}
if (BuildConfiguration.bPrintPerformanceInfo)
{
double PCHGenTime = (DateTime.UtcNow - PCHGenTimerStart).TotalSeconds;
TotalPCHGenTime += PCHGenTime;
}
}
if (!bWasModuleCodeCompiled && SourceFilesToBuild.CPPFiles.Count > 0)
{
List<FileItem> CPPFilesToCompile = SourceFilesToBuild.CPPFiles;
if (bModuleUsesUnityBuild)
{
CPPFilesToCompile = Unity.GenerateUnityCPPs(ToolChain, Target, CPPFilesToCompile, CPPCompileEnvironment, Name);
// In unity pass files through CompileUnityFilesWithToolChain which can opt adaptive unity files out of optimization
LinkInputFiles.AddRange(CompileUnityFilesWithToolChain(ToolChain, Target, CPPCompileEnvironment, CPPFilesToCompile, Name).ObjectFiles);
}
else
{
LinkInputFiles.AddRange(ToolChain.CompileCPPFiles(Target, CPPCompileEnvironment, CPPFilesToCompile, Name).ObjectFiles);
}
}
if (AutoGenerateCppInfo != null && AutoGenerateCppInfo.BuildInfo != null && !CPPCompileEnvironment.bHackHeaderGenerator)
{
string[] GeneratedFiles = Directory.GetFiles(Path.GetDirectoryName(AutoGenerateCppInfo.BuildInfo.FileWildcard), Path.GetFileName(AutoGenerateCppInfo.BuildInfo.FileWildcard));
foreach (string GeneratedFilename in GeneratedFiles)
{
FileItem GeneratedCppFileItem = FileItem.GetItemByPath(GeneratedFilename);
CachePCHUsageForModuleSourceFile(Target, CPPCompileEnvironment, GeneratedCppFileItem);
// @todo ubtmake: Check for ALL other places where we might be injecting .cpp or .rc files for compiling without caching CachedCPPIncludeInfo first (anything platform specific?)
LinkInputFiles.AddRange(ToolChain.CompileCPPFiles(Target, CPPCompileEnvironment, new List<FileItem> { GeneratedCppFileItem }, Name).ObjectFiles);
}
}
// Compile C files directly.
LinkInputFiles.AddRange(ToolChain.CompileCPPFiles(Target, CPPCompileEnvironment, SourceFilesToBuild.CFiles, Name).ObjectFiles);
// Compile CC files directly.
LinkInputFiles.AddRange(ToolChain.CompileCPPFiles(Target, CPPCompileEnvironment, SourceFilesToBuild.CCFiles, Name).ObjectFiles);
// Compile MM files directly.
LinkInputFiles.AddRange(ToolChain.CompileCPPFiles(Target, CPPCompileEnvironment, SourceFilesToBuild.MMFiles, Name).ObjectFiles);
// Compile RC files.
LinkInputFiles.AddRange(ToolChain.CompileRCFiles(Target, CPPCompileEnvironment, SourceFilesToBuild.RCFiles).ObjectFiles);
return LinkInputFiles;
}
/// <summary>
/// Compiles the provided CPP unity files. Will
/// </summary>
private CPPOutput CompileUnityFilesWithToolChain(UEToolChain ToolChain, UEBuildTarget Target, CPPEnvironment CompileEnvironment, List<FileItem> SourceFiles, string ModuleName)
{
List<FileItem> NormalFiles = new List<FileItem>();
List<FileItem> AdaptiveFiles = new List<FileItem>();
foreach (FileItem File in SourceFiles)
{
// Basic check as to whether something in this module is/isn't a unity file...
if (File.ToString().StartsWith("Module."))
{
NormalFiles.Add(File);
}
else
{
AdaptiveFiles.Add(File);
}
}
CPPOutput OutputFiles = new CPPOutput();
if (NormalFiles.Count > 0)
{
OutputFiles = ToolChain.CompileCPPFiles(Target, CompileEnvironment, NormalFiles, Name);
}
if (AdaptiveFiles.Count > 0)
{
// Create an unoptmized compilation environment. Need to turn of PCH due to different
// compiler settings
CPPEnvironment UnoptimziedEnvironment = CompileEnvironment.DeepCopy();
UnoptimziedEnvironment.Config.OptimizeCode = ModuleRules.CodeOptimization.Never;
UnoptimziedEnvironment.Config.PrecompiledHeaderAction = PrecompiledHeaderAction.None;
// Compile the files
CPPOutput AdaptiveOutput = ToolChain.CompileCPPFiles(Target, UnoptimziedEnvironment, AdaptiveFiles, Name);
// Merge output
OutputFiles.ObjectFiles.AddRange(AdaptiveOutput.ObjectFiles);
OutputFiles.DebugDataFiles.AddRange(AdaptiveOutput.DebugDataFiles);
}
return OutputFiles;
}
private PrecompileHeaderEnvironment ApplySharedPCH(CPPEnvironment GlobalCompileEnvironment, CPPEnvironment CompileEnvironment, CPPEnvironment ModuleCompileEnvironment, List<FileItem> CPPFiles, ref FileItem SharedPCHHeaderFile)
{
// Check to see if we have a PCH header already setup that we can use
FileItem SharedPCHHeaderFileCopy = SharedPCHHeaderFile;
PrecompileHeaderEnvironment SharedPCHEnvironment = GlobalCompileEnvironment.SharedPCHEnvironments.Find(Env => Env.PrecompiledHeaderIncludeFilename == SharedPCHHeaderFileCopy);
if (SharedPCHEnvironment == null)
{
return null;
}
// Don't mix CLR modes
if (SharedPCHEnvironment.CLRMode != ModuleCompileEnvironment.Config.CLRMode)
{
Log.TraceVerbose("Module {0} cannot use existing Shared PCH '{1}' (from module '{2}') because CLR modes don't match", Name, SharedPCHEnvironment.PrecompiledHeaderIncludeFilename.AbsolutePath, SharedPCHEnvironment.ModuleName);
SharedPCHHeaderFile = null;
return null;
}
// Don't mix RTTI modes
if (Rules.bUseRTTI)
{
Log.TraceVerbose("Module {0} cannot use existing Shared PCH '{1}' (from module '{2}') because RTTI modes don't match", Name, SharedPCHEnvironment.PrecompiledHeaderIncludeFilename.AbsolutePath, SharedPCHEnvironment.ModuleName);
SharedPCHHeaderFile = null;
return null;
}
// Don't mix non-optimized code with optimized code (PCHs won't be compatible)
ModuleRules.CodeOptimization SharedPCHCodeOptimization = SharedPCHEnvironment.OptimizeCode;
ModuleRules.CodeOptimization ModuleCodeOptimization = ModuleCompileEnvironment.Config.OptimizeCode;
if (CompileEnvironment.Config.Target.Configuration != CPPTargetConfiguration.Debug)
{
if (SharedPCHCodeOptimization == ModuleRules.CodeOptimization.InNonDebugBuilds)
{
SharedPCHCodeOptimization = ModuleRules.CodeOptimization.Always;
}
if (ModuleCodeOptimization == ModuleRules.CodeOptimization.InNonDebugBuilds)
{
ModuleCodeOptimization = ModuleRules.CodeOptimization.Always;
}
}
if (SharedPCHCodeOptimization != ModuleCodeOptimization)
{
Log.TraceVerbose("Module {0} cannot use existing Shared PCH '{1}' (from module '{2}') because optimization levels don't match", Name, SharedPCHEnvironment.PrecompiledHeaderIncludeFilename.AbsolutePath, SharedPCHEnvironment.ModuleName);
SharedPCHHeaderFile = null;
return null;
}
return SharedPCHEnvironment;
}
public static FileItem CachePCHUsageForModuleSourceFile(UEBuildTarget Target, CPPEnvironment ModuleCompileEnvironment, FileItem CPPFile)
{
if (!CPPFile.bExists)
{
throw new BuildException("Required source file not found: " + CPPFile.AbsolutePath);
}
DateTime PCHCacheTimerStart = DateTime.UtcNow;
UEBuildPlatform BuildPlatform = UEBuildPlatform.GetBuildPlatformForCPPTargetPlatform(ModuleCompileEnvironment.Config.Target.Platform);
List<string> IncludePathsToSearch = ModuleCompileEnvironment.Config.CPPIncludeInfo.GetIncludesPathsToSearch(CPPFile);
// Store the module compile environment along with the .cpp file. This is so that we can use it later on when looking
// for header dependencies
CPPFile.CachedCPPIncludeInfo = ModuleCompileEnvironment.Config.CPPIncludeInfo;
FileItem PCHFile = CachePCHUsageForCPPFile(Target, CPPFile, BuildPlatform, IncludePathsToSearch, ModuleCompileEnvironment.Config.CPPIncludeInfo.IncludeFileSearchDictionary);
if (BuildConfiguration.bPrintPerformanceInfo)
{
double PCHCacheTime = (DateTime.UtcNow - PCHCacheTimerStart).TotalSeconds;
TotalPCHCacheTime += PCHCacheTime;
}
return PCHFile;
}
public void CachePCHUsageForModuleSourceFiles(UEBuildTarget Target, CPPEnvironment ModuleCompileEnvironment)
{
if (ProcessedDependencies == null)
{
DateTime PCHCacheTimerStart = DateTime.UtcNow;
UEBuildPlatform BuildPlatform = UEBuildPlatform.GetBuildPlatformForCPPTargetPlatform(ModuleCompileEnvironment.Config.Target.Platform);
bool bFoundAProblemWithPCHs = false;
FileItem UniquePCH = null;
foreach (FileItem CPPFile in SourceFilesFound.CPPFiles) // @todo ubtmake: We're not caching CPPEnvironments for .c/.mm files, etc. Even though they don't use PCHs, they still have #includes! This can break dependency checking!
{
// Build a single list of include paths to search.
List<string> IncludePathsToSearch = ModuleCompileEnvironment.Config.CPPIncludeInfo.GetIncludesPathsToSearch(CPPFile);
// Store the module compile environment along with the .cpp file. This is so that we can use it later on when looking
// for header dependencies
CPPFile.CachedCPPIncludeInfo = ModuleCompileEnvironment.Config.CPPIncludeInfo;
// Find headers used by the source file.
FileItem PCH = UEBuildModuleCPP.CachePCHUsageForCPPFile(Target, CPPFile, BuildPlatform, IncludePathsToSearch, ModuleCompileEnvironment.Config.CPPIncludeInfo.IncludeFileSearchDictionary);
if (PCH == null)
{
throw new BuildException("Source file \"{0}\" is not including any headers. We expect all modules to include a header file for precompiled header generation. Please add an #include statement.", CPPFile.AbsolutePath);
}
if (UniquePCH == null)
{
UniquePCH = PCH;
}
else if (!UniquePCH.Info.Name.Equals(PCH.Info.Name, StringComparison.InvariantCultureIgnoreCase)) // @todo ubtmake: We do a string compare on the file name (not path) here, because sometimes the include resolver will pick an Intermediate copy of a PCH header file and throw off our comparisons
{
// OK, looks like we have multiple source files including a different header file first. We'll keep track of this and print out
// helpful information afterwards.
bFoundAProblemWithPCHs = true;
}
}
ProcessedDependencies = new ProcessedDependenciesClass { UniquePCHHeaderFile = UniquePCH };
if (bFoundAProblemWithPCHs)
{
// Map from pch header string to the source files that use that PCH
Dictionary<FileReference, List<FileItem>> UsageMapPCH = new Dictionary<FileReference, List<FileItem>>();
foreach (FileItem CPPFile in SourceFilesToBuild.CPPFiles)
{
// Create a new entry if not in the pch usage map
UsageMapPCH.GetOrAddNew(CPPFile.PrecompiledHeaderIncludeFilename).Add(CPPFile);
}
if (BuildConfiguration.bPrintDebugInfo)
{
Log.TraceVerbose("{0} PCH files for module {1}:", UsageMapPCH.Count, Name);
int MostFilesIncluded = 0;
foreach (KeyValuePair<FileReference, List<FileItem>> CurPCH in UsageMapPCH)
{
if (CurPCH.Value.Count > MostFilesIncluded)
{
MostFilesIncluded = CurPCH.Value.Count;
}
Log.TraceVerbose(" {0} ({1} files including it: {2}, ...)", CurPCH.Key, CurPCH.Value.Count, CurPCH.Value[0].AbsolutePath);
}
}
if (UsageMapPCH.Count > 1)
{
// Keep track of the PCH file that is most used within this module
FileReference MostFilesAreIncludingPCH = null;
int MostFilesIncluded = 0;
foreach (KeyValuePair<FileReference, List<FileItem>> CurPCH in UsageMapPCH.Where(PCH => PCH.Value.Count > MostFilesIncluded))
{
MostFilesAreIncludingPCH = CurPCH.Key;
MostFilesIncluded = CurPCH.Value.Count;
}
// Find all of the files that are not including our "best" PCH header
StringBuilder FilesNotIncludingBestPCH = new StringBuilder();
foreach (KeyValuePair<FileReference, List<FileItem>> CurPCH in UsageMapPCH.Where(PCH => PCH.Key != MostFilesAreIncludingPCH))
{
foreach (FileItem SourceFile in CurPCH.Value)
{
FilesNotIncludingBestPCH.AppendFormat("{0} (including {1})\n", SourceFile.AbsolutePath, CurPCH.Key);
}
}
// Bail out and let the user know which source files may need to be fixed up
throw new BuildException(
"All source files in module \"{0}\" must include the same precompiled header first. Currently \"{1}\" is included by most of the source files. The following source files are not including \"{1}\" as their first include:\n\n{2}",
Name,
MostFilesAreIncludingPCH,
FilesNotIncludingBestPCH);
}
}
if (BuildConfiguration.bPrintPerformanceInfo)
{
double PCHCacheTime = (DateTime.UtcNow - PCHCacheTimerStart).TotalSeconds;
TotalPCHCacheTime += PCHCacheTime;
}
}
}
private static FileItem CachePCHUsageForCPPFile(UEBuildTarget Target, FileItem CPPFile, UEBuildPlatform BuildPlatform, List<string> IncludePathsToSearch, Dictionary<string, FileItem> IncludeFileSearchDictionary)
{
// @todo ubtmake: We don't really need to scan every file looking for PCH headers, just need one. The rest is just for error checking.
// @todo ubtmake: We don't need all of the direct includes either. We just need the first, unless we want to check for errors.
List<DependencyInclude> DirectIncludeFilenames = CPPEnvironment.GetDirectIncludeDependencies(Target, CPPFile, BuildPlatform, bOnlyCachedDependencies: false);
if (BuildConfiguration.bPrintDebugInfo)
{
Log.TraceVerbose("Found direct includes for {0}: {1}", Path.GetFileName(CPPFile.AbsolutePath), string.Join(", ", DirectIncludeFilenames.Select(F => F.IncludeName)));
}
if (DirectIncludeFilenames.Count == 0)
{
return null;
}
DependencyInclude FirstInclude = DirectIncludeFilenames[0];
// The pch header should always be the first include in the source file.
// NOTE: This is not an absolute path. This is just the literal include string from the source file!
CPPFile.PCHHeaderNameInCode = FirstInclude.IncludeName;
// Resolve the PCH header to an absolute path.
// Check NullOrEmpty here because if the file could not be resolved we need to throw an exception
if (FirstInclude.IncludeResolvedNameIfSuccessful != null &&
// ignore any preexisting resolve cache if we are not configured to use it.
BuildConfiguration.bUseIncludeDependencyResolveCache &&
// if we are testing the resolve cache, we force UBT to resolve every time to look for conflicts
!BuildConfiguration.bTestIncludeDependencyResolveCache)
{
CPPFile.PrecompiledHeaderIncludeFilename = FirstInclude.IncludeResolvedNameIfSuccessful;
return FileItem.GetItemByFileReference(CPPFile.PrecompiledHeaderIncludeFilename);
}
// search the include paths to resolve the file.
FileItem PrecompiledHeaderIncludeFile = CPPEnvironment.FindIncludedFile(CPPFile.PCHHeaderNameInCode, !BuildConfiguration.bCheckExternalHeadersForModification, IncludePathsToSearch, IncludeFileSearchDictionary);
if (PrecompiledHeaderIncludeFile == null)
{
throw new BuildException("The first include statement in source file '{0}' is trying to include the file '{1}' as the precompiled header, but that file could not be located in any of the module's include search paths.", CPPFile.AbsolutePath, CPPFile.PCHHeaderNameInCode);
}
CPPEnvironment.IncludeDependencyCache[Target].CacheResolvedIncludeFullPath(CPPFile, 0, PrecompiledHeaderIncludeFile.Reference);
CPPFile.PrecompiledHeaderIncludeFilename = PrecompiledHeaderIncludeFile.Reference;
return PrecompiledHeaderIncludeFile;
}
/// <summary>
/// Creates a compile environment from a base environment based on the module settings.
/// </summary>
/// <param name="BaseCompileEnvironment">An existing environment to base the module compile environment on.</param>
/// <returns>The new module compile environment.</returns>
public CPPEnvironment CreateModuleCompileEnvironment(UEBuildTarget Target, CPPEnvironment BaseCompileEnvironment)
{
CPPEnvironment Result = BaseCompileEnvironment.DeepCopy();
if (Binary == null)
{
// Adding this check here as otherwise the call to Binary.Config.IntermediateDirectory will give an
// unhandled exception
throw new BuildException("UEBuildBinary not set up for module {0}", this.ToString());
}
// Override compile environment
Result.Config.bFasterWithoutUnity = Rules.bFasterWithoutUnity;
Result.Config.OptimizeCode = Rules.OptimizeCode;
Result.Config.bUseRTTI = Rules.bUseRTTI;
Result.Config.bUseAVX = Rules.bUseAVX;
Result.Config.bEnableBufferSecurityChecks = Rules.bEnableBufferSecurityChecks;
Result.Config.MinSourceFilesForUnityBuildOverride = Rules.MinSourceFilesForUnityBuildOverride;
Result.Config.MinFilesUsingPrecompiledHeaderOverride = Rules.MinFilesUsingPrecompiledHeaderOverride;
Result.Config.bBuildLocallyWithSNDBS = Rules.bBuildLocallyWithSNDBS;
Result.Config.bEnableExceptions = Rules.bEnableExceptions;
Result.Config.bEnableShadowVariableWarning = Rules.bEnableShadowVariableWarnings;
Result.Config.bUseStaticCRT = (Target.Rules != null && Target.Rules.bUseStaticCRT);
Result.Config.OutputDirectory = DirectoryReference.Combine(Binary.Config.IntermediateDirectory, Name);
// Switch the optimization flag if we're building a game module. Also pass the definition for building in DebugGame along (see ModuleManager.h for notes).
if (Target.Configuration == UnrealTargetConfiguration.DebugGame)
{
if (!ModuleDirectory.IsUnderDirectory(UnrealBuildTool.EngineDirectory))
{
Result.Config.Target.Configuration = CPPTargetConfiguration.Debug;
Result.Config.Definitions.Add("UE_BUILD_DEVELOPMENT_WITH_DEBUGGAME=1");
}
}
// Add the module's private definitions.
Result.Config.Definitions.AddRange(Definitions);
// Setup the compile environment for the module.
SetupPrivateCompileEnvironment(Result.Config.CPPIncludeInfo.IncludePaths, Result.Config.CPPIncludeInfo.SystemIncludePaths, Result.Config.Definitions, Result.Config.AdditionalFrameworks);
// @hack to skip adding definitions to compile environment, they will be baked into source code files
if (bSkipDefinitionsForCompileEnvironment)
{
Result.Config.Definitions.Clear();
Result.Config.CPPIncludeInfo.IncludePaths = new HashSet<string>(BaseCompileEnvironment.Config.CPPIncludeInfo.IncludePaths);
}
return Result;
}
public class UHTModuleInfoCacheType
{
public UHTModuleInfoCacheType(IEnumerable<string> InHeaderFilenames, UHTModuleInfo InInfo)
{
HeaderFilenames = InHeaderFilenames;
Info = InInfo;
}
public IEnumerable<string> HeaderFilenames = null;
public UHTModuleInfo Info = null;
}
private UHTModuleInfoCacheType UHTModuleInfoCache = null;
/// Total time spent generating PCHs for modules (not actually compiling, but generating the PCH's input data)
public static double TotalPCHGenTime = 0.0;
/// Time spent caching which PCH header is included by each module and source file
public static double TotalPCHCacheTime = 0.0;
/// <summary>
/// If any of this module's source files contain UObject definitions, this will return those header files back to the caller
/// </summary>
/// <returns>
public UHTModuleInfoCacheType GetCachedUHTModuleInfo(EGeneratedCodeVersion GeneratedCodeVersion)
{
if (UHTModuleInfoCache == null)
{
IEnumerable<string> HeaderFilenames = Directory.GetFiles(ModuleDirectory.FullName, "*.h", SearchOption.AllDirectories);
UHTModuleInfo Info = ExternalExecution.CreateUHTModuleInfo(HeaderFilenames, Name, ModuleDirectory, Type, GeneratedCodeVersion);
UHTModuleInfoCache = new UHTModuleInfoCacheType(Info.PublicUObjectHeaders.Concat(Info.PublicUObjectClassesHeaders).Concat(Info.PrivateUObjectHeaders).Select(x => x.AbsolutePath).ToList(), Info);
}
return UHTModuleInfoCache;
}
public override void GetAllDependencyModules(List<UEBuildModule> ReferencedModules, HashSet<UEBuildModule> IgnoreReferencedModules, bool bIncludeDynamicallyLoaded, bool bForceCircular, bool bOnlyDirectDependencies)
{
List<UEBuildModule> AllDependencyModules = new List<UEBuildModule>();
AllDependencyModules.AddRange(PrivateDependencyModules);
AllDependencyModules.AddRange(PublicDependencyModules);
if (bIncludeDynamicallyLoaded)
{
AllDependencyModules.AddRange(DynamicallyLoadedModules);
AllDependencyModules.AddRange(PlatformSpecificDynamicallyLoadedModules);
}
foreach (UEBuildModule DependencyModule in AllDependencyModules)
{
if (!IgnoreReferencedModules.Contains(DependencyModule))
{
// Don't follow circular back-references!
bool bIsCircular = HasCircularDependencyOn(DependencyModule.Name);
if (bForceCircular || !bIsCircular)
{
IgnoreReferencedModules.Add(DependencyModule);
if (!bOnlyDirectDependencies)
{
// Recurse into dependent modules first
DependencyModule.GetAllDependencyModules(ReferencedModules, IgnoreReferencedModules, bIncludeDynamicallyLoaded, bForceCircular, bOnlyDirectDependencies);
}
ReferencedModules.Add(DependencyModule);
}
}
}
}
public override void RecursivelyAddPrecompiledModules(List<UEBuildModule> Modules)
{
if (!Modules.Contains(this))
{
Modules.Add(this);
// Get the dependent modules
List<UEBuildModule> DependentModules = new List<UEBuildModule>();
if (PrivateDependencyModules != null)
{
DependentModules.AddRange(PrivateDependencyModules);
}
if (PublicDependencyModules != null)
{
DependentModules.AddRange(PublicDependencyModules);
}
if (DynamicallyLoadedModules != null)
{
DependentModules.AddRange(DynamicallyLoadedModules);
}
if (PlatformSpecificDynamicallyLoadedModules != null)
{
DependentModules.AddRange(PlatformSpecificDynamicallyLoadedModules);
}
// Find modules for each of them, and add their dependencies too
foreach (UEBuildModule DependentModule in DependentModules)
{
DependentModule.RecursivelyAddPrecompiledModules(Modules);
}
}
}
}
/// <summary>
/// A module that is compiled from C++ CLR code.
/// </summary>
class UEBuildModuleCPPCLR : UEBuildModuleCPP
{
/// <summary>
/// The assemblies referenced by the module's private implementation.
/// </summary>
HashSet<string> PrivateAssemblyReferences;
public UEBuildModuleCPPCLR(
string InName,
UHTModuleType InType,
DirectoryReference InModuleDirectory,
DirectoryReference InGeneratedCodeDirectory,
IntelliSenseGatherer InIntelliSenseGatherer,
IEnumerable<FileItem> InSourceFiles,
ModuleRules InRules,
bool bInBuildSourceFiles,
FileReference InRulesFile
)
: base(InName, InType, InModuleDirectory, InGeneratedCodeDirectory, InIntelliSenseGatherer,
InSourceFiles, InRules,
bInBuildSourceFiles, InRulesFile)
{
PrivateAssemblyReferences = HashSetFromOptionalEnumerableStringParameter(InRules.PrivateAssemblyReferences);
}
// UEBuildModule interface.
public override List<FileItem> Compile(UEBuildTarget Target, UEToolChain ToolChain, CPPEnvironment GlobalCompileEnvironment, CPPEnvironment CompileEnvironment)
{
CPPEnvironment ModuleCLREnvironment = CompileEnvironment.DeepCopy();
// Setup the module environment for the project CLR mode
ModuleCLREnvironment.Config.CLRMode = CPPCLRMode.CLREnabled;
// Add the private assembly references to the compile environment.
foreach (string PrivateAssemblyReference in PrivateAssemblyReferences)
{
ModuleCLREnvironment.AddPrivateAssembly(PrivateAssemblyReference);
}
// Pass the CLR compilation environment to the standard C++ module compilation code.
return base.Compile(Target, ToolChain, GlobalCompileEnvironment, ModuleCLREnvironment);
}
public override void SetupPrivateLinkEnvironment(
UEBuildBinary SourceBinary,
LinkEnvironment LinkEnvironment,
List<UEBuildBinary> BinaryDependencies,
HashSet<UEBuildModule> VisitedModules
)
{
base.SetupPrivateLinkEnvironment(SourceBinary, LinkEnvironment, BinaryDependencies, VisitedModules);
// Setup the link environment for linking a CLR binary.
LinkEnvironment.Config.CLRMode = CPPCLRMode.CLREnabled;
}
}
public class UEBuildFramework
{
public UEBuildFramework(string InFrameworkName)
{
FrameworkName = InFrameworkName;
}
public UEBuildFramework(string InFrameworkName, string InFrameworkZipPath)
{
FrameworkName = InFrameworkName;
FrameworkZipPath = InFrameworkZipPath;
}
public UEBuildFramework(string InFrameworkName, string InFrameworkZipPath, string InCopyBundledAssets)
{
FrameworkName = InFrameworkName;
FrameworkZipPath = InFrameworkZipPath;
CopyBundledAssets = InCopyBundledAssets;
}
public UEBuildModule OwningModule = null;
public string FrameworkName = null;
public string FrameworkZipPath = null;
public string CopyBundledAssets = null;
}
public class UEBuildBundleResource
{
public UEBuildBundleResource(string InResourcePath, string InBundleContentsSubdir = "Resources", bool bInShouldLog = true)
{
ResourcePath = InResourcePath;
BundleContentsSubdir = InBundleContentsSubdir;
bShouldLog = bInShouldLog;
}
public string ResourcePath = null;
public string BundleContentsSubdir = null;
public bool bShouldLog = true;
}
public class PrecompileHeaderEnvironment
{
/// <summary>
/// The name of the module this PCH header is a member of
/// </summary>
public readonly string ModuleName;
/// <summary>
/// PCH header file name as it appears in an #include statement in source code (might include partial, or no relative path.)
/// This is needed by some compilers to use PCH features.
/// </summary>
public string PCHHeaderNameInCode;
/// <summary>
/// The source header file that this precompiled header will be generated for
/// </summary>
public readonly FileItem PrecompiledHeaderIncludeFilename;
/// <summary>
/// Whether this precompiled header will be built with CLR features enabled. We won't mix and match CLR PCHs with non-CLR PCHs
/// </summary>
public readonly CPPCLRMode CLRMode;
/// <summary>
/// Whether this precompiled header will be built with code optimization enabled.
/// </summary>
public readonly ModuleRules.CodeOptimization OptimizeCode;
/// <summary>
/// The PCH file we're generating
/// </summary>
public FileItem PrecompiledHeaderFile = null;
/// <summary>
/// Object files emitted from the compiler when generating this precompiled header. These will be linked into modules that
/// include this PCH
/// </summary>
public readonly List<FileItem> OutputObjectFiles = new List<FileItem>();
public PrecompileHeaderEnvironment(string InitModuleName, string InitPCHHeaderNameInCode, FileItem InitPrecompiledHeaderIncludeFilename, CPPCLRMode InitCLRMode, ModuleRules.CodeOptimization InitOptimizeCode)
{
ModuleName = InitModuleName;
PCHHeaderNameInCode = InitPCHHeaderNameInCode;
PrecompiledHeaderIncludeFilename = InitPrecompiledHeaderIncludeFilename;
CLRMode = InitCLRMode;
OptimizeCode = InitOptimizeCode;
}
/// <summary>
/// Creates a precompiled header action to generate a new pch file
/// </summary>
/// <param name="PCHHeaderNameInCode">The precompiled header name as it appeared in an #include statement</param>
/// <param name="PrecompiledHeaderIncludeFilename">Name of the header used for pch.</param>
/// <param name="ProjectCPPEnvironment">The environment the C/C++ files in the project are compiled with.</param>
/// <param name="OutputDirectory">The folder to save the generated PCH file to</param>
/// <param name="ModuleName">Name of the module this PCH is being generated for</param>
/// <param name="bAllowDLLExports">True if we should allow DLLEXPORT definitions for this PCH</param>
/// <returns>the compilation output result of the created pch.</returns>
public static CPPOutput GeneratePCHCreationAction(UEToolChain ToolChain, UEBuildTarget Target, string PCHHeaderNameInCode, FileItem PrecompiledHeaderIncludeFilename, CPPEnvironment ProjectCPPEnvironment, DirectoryReference OutputDirectory, DirectoryReference PCHOutputDirectory, string ModuleName, bool bAllowDLLExports)
{
// Find the header file to be precompiled. Don't skip external headers
if (PrecompiledHeaderIncludeFilename.bExists)
{
// Create a Dummy wrapper around the PCH to avoid problems with #pragma once on clang
string PCHGuardDefine = Path.GetFileNameWithoutExtension(PrecompiledHeaderIncludeFilename.AbsolutePath).ToUpperInvariant();
string LocalPCHHeaderNameInCode = ToolChain.ConvertPath(PrecompiledHeaderIncludeFilename.AbsolutePath);
string TmpPCHHeaderContents = String.Format("#ifndef __AUTO_{0}_H__\n#define __AUTO_{0}_H__\n//Last Write: {2}\n#include \"{1}\"\n#endif//__AUTO_{0}_H__", PCHGuardDefine, LocalPCHHeaderNameInCode, PrecompiledHeaderIncludeFilename.LastWriteTime);
FileReference DummyPath = FileReference.Combine(
ProjectCPPEnvironment.Config.OutputDirectory,
Path.GetFileName(PrecompiledHeaderIncludeFilename.AbsolutePath));
FileItem DummyPCH = FileItem.CreateIntermediateTextFile(DummyPath, TmpPCHHeaderContents);
// Create a new C++ environment that is used to create the PCH.
CPPEnvironment ProjectPCHEnvironment = ProjectCPPEnvironment.DeepCopy();
ProjectPCHEnvironment.Config.PrecompiledHeaderAction = PrecompiledHeaderAction.Create;
ProjectPCHEnvironment.Config.PrecompiledHeaderIncludeFilename = PrecompiledHeaderIncludeFilename.Reference;
ProjectPCHEnvironment.Config.PCHHeaderNameInCode = PCHHeaderNameInCode;
ProjectPCHEnvironment.Config.OutputDirectory = OutputDirectory;
ProjectPCHEnvironment.Config.PCHOutputDirectory = PCHOutputDirectory;
if (!bAllowDLLExports)
{
for (int CurDefinitionIndex = 0; CurDefinitionIndex < ProjectPCHEnvironment.Config.Definitions.Count; ++CurDefinitionIndex)
{
// We change DLLEXPORT to DLLIMPORT for "shared" PCH headers
string OldDefinition = ProjectPCHEnvironment.Config.Definitions[CurDefinitionIndex];
if (OldDefinition.EndsWith("=DLLEXPORT"))
{
ProjectPCHEnvironment.Config.Definitions[CurDefinitionIndex] = OldDefinition.Replace("DLLEXPORT", "DLLIMPORT");
}
}
}
// Cache our CPP environment so that we can check for outdatedness quickly. Only files that have includes need this.
DummyPCH.CachedCPPIncludeInfo = ProjectPCHEnvironment.Config.CPPIncludeInfo;
Log.TraceVerbose("Found PCH file \"{0}\".", PrecompiledHeaderIncludeFilename);
// Create the action to compile the PCH file.
return ToolChain.CompileCPPFiles(Target, ProjectPCHEnvironment, new List<FileItem>() { DummyPCH }, ModuleName);
}
throw new BuildException("Couldn't find PCH file \"{0}\".", PrecompiledHeaderIncludeFilename);
}
}
}
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.Win32;
using System.Text;
namespace UnrealBuildTool
{
public class VCToolChain : UEToolChain
{
bool bSupportWindowsXP;
public VCToolChain(CPPTargetPlatform CppPlatform, bool bInSupportWindowsXP)
: base(CppPlatform)
{
bSupportWindowsXP = bInSupportWindowsXP;
}
static void AddDefinition(StringBuilder String, string Definition)
{
// Split the definition into name and value
int ValueIdx = Definition.IndexOf('=');
if (ValueIdx == -1)
{
AddDefinition(String, Definition, null);
}
else
{
AddDefinition(String, Definition.Substring(0, ValueIdx), Definition.Substring(ValueIdx + 1));
}
}
static void AddDefinition(StringBuilder String, string Variable, string Value)
{
// If the value has a space in it and isn't wrapped in quotes, do that now
if (Value != null && !Value.StartsWith("\"") && (Value.Contains(" ") || Value.Contains("$")))
{
Value = "\"" + Value + "\"";
}
if (WindowsPlatform.bUseVCCompilerArgs)
{
if (Value != null)
{
String.Append(" /D" + Variable + "=" + Value);
}
else
{
String.Append(" /D" + Variable);
}
}
else
{
if (Value != null)
{
String.Append(" -D " + Variable + "=" + Value);
}
else
{
String.Append(" -D " + Variable);
}
}
}
static void AddIncludePath(StringBuilder String, string IncludePath)
{
// If the value has a space in it and isn't wrapped in quotes, do that now
if (!IncludePath.StartsWith("\"") && (IncludePath.Contains(" ") || IncludePath.Contains("$")))
{
IncludePath = "\"" + IncludePath + "\"";
}
if (WindowsPlatform.bUseVCCompilerArgs)
{
String.Append(" /I " + IncludePath);
}
else
{
String.Append(" -I" + IncludePath);
}
}
void AppendCLArguments_Global(CPPEnvironment CompileEnvironment, VCEnvironment EnvVars, StringBuilder Arguments)
{
// @todo UWP: Why do we ever need WinRT headers when building regular Win32? Is this just needed for the Windows 10 SDK?
// @todo UWP: These include paths should be added in SetUpEnvironment(), not here. Do they need to be the last includes or something?
if (WindowsPlatform.Compiler == WindowsCompiler.VisualStudio2015 && WindowsPlatform.bUseWindowsSDK10)
{
if (Directory.Exists(EnvVars.WindowsSDKExtensionDir))
{
CompileEnvironment.Config.CPPIncludeInfo.SystemIncludePaths.Add(string.Format(@"{0}\Include\{1}\um", EnvVars.WindowsSDKExtensionDir, EnvVars.WindowsSDKExtensionHeaderLibVersion));
CompileEnvironment.Config.CPPIncludeInfo.SystemIncludePaths.Add(string.Format(@"{0}\Include\{1}\shared", EnvVars.WindowsSDKExtensionDir, EnvVars.WindowsSDKExtensionHeaderLibVersion));
CompileEnvironment.Config.CPPIncludeInfo.SystemIncludePaths.Add(string.Format(@"{0}\Include\{1}\winrt", EnvVars.WindowsSDKExtensionDir, EnvVars.WindowsSDKExtensionHeaderLibVersion));
}
if (Directory.Exists(EnvVars.NetFxSDKExtensionDir))
{
CompileEnvironment.Config.CPPIncludeInfo.SystemIncludePaths.Add(string.Format(@"{0}\Include\um", EnvVars.NetFxSDKExtensionDir));
}
}
// NOTE: Uncommenting this line will print includes as they are encountered by the preprocessor. This can help with diagnosing include order problems.
if (WindowsPlatform.bCompileWithClang && !WindowsPlatform.bUseVCCompilerArgs)
{
// Arguments.Append( " -H" );
}
else
{
// Arguments.Append( " /showIncludes" );
}
if (WindowsPlatform.bCompileWithClang)
{
// Arguments.Append( " -###" ); // @todo clang: Print Clang command-lines (instead of outputting compile results!)
if (!WindowsPlatform.bUseVCCompilerArgs)
{
Arguments.Append(" -std=c++11");
Arguments.Append(" -fdiagnostics-format=msvc");
Arguments.Append(" -Xclang -relaxed-aliasing -Xclang --dependent-lib=msvcrt -Xclang --dependent-lib=oldnames -gline-tables-only -ffunction-sections");
}
// @todo clang: We're impersonating the Visual C++ compiler by setting MSC_VER and _MSC_FULL_VER to values that MSVC would set
string VersionString;
string FullVersionString;
switch (WindowsPlatform.Compiler)
{
case WindowsCompiler.VisualStudio2013:
VersionString = "18.0";
FullVersionString = "1800";
break;
case WindowsCompiler.VisualStudio2015:
VersionString = "19.0";
FullVersionString = "1900";
break;
default:
throw new BuildException("Unexpected value for WindowsPlatform.Compiler: " + WindowsPlatform.Compiler.ToString());
}
Arguments.Append(" -fms-compatibility-version=" + VersionString);
AddDefinition(Arguments, "_MSC_FULL_VER", FullVersionString + "00000");
}
// @todo clang: Clang on Windows doesn't respect "#pragma warning (error: ####)", and we're not passing "/WX", so warnings are not
// treated as errors when compiling on Windows using Clang right now.
// NOTE re: clang: the arguments for clang-cl can be found at http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Driver/CLCompatOptions.td?view=markup
// This will show the cl.exe options that map to clang.exe ones, which ones are ignored and which ones are unsupported.
if (BuildConfiguration.bEnableCodeAnalysis)
{
Arguments.Append(" /analyze");
// Don't cause analyze warnings to be errors
Arguments.Append(" /analyze:WX-");
// Report functions that use a LOT of stack space. You can lower this value if you
// want more aggressive checking for functions that use a lot of stack memory.
Arguments.Append(" /analyze:stacksize81940");
// Don't bother generating code, only analyze code (may report fewer warnings though.)
//Arguments.Append(" /analyze:only");
}
if (WindowsPlatform.bUseVCCompilerArgs)
{
// Prevents the compiler from displaying its logo for each invocation.
Arguments.Append(" /nologo");
// Enable intrinsic functions.
Arguments.Append(" /Oi");
}
else
{
// Enable intrinsic functions.
Arguments.Append(" -fbuiltin");
}
if (WindowsPlatform.bCompileWithClang)
{
// Tell the Clang compiler whether we want to generate 32-bit code or 64-bit code
if (CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64)
{
Arguments.Append(" --target=x86_64-pc-windows-msvc");
}
else
{
Arguments.Append(" --target=i686-pc-windows-msvc");
}
}
// Compile into an .obj file, and skip linking.
if (WindowsPlatform.bUseVCCompilerArgs)
{
Arguments.Append(" /c");
}
else
{
Arguments.Append(" -c");
}
if (WindowsPlatform.bUseVCCompilerArgs)
{
// Separate functions for linker.
Arguments.Append(" /Gy");
// Allow 800% of the default memory allocation limit.
Arguments.Append(" /Zm800");
// Disable "The file contains a character that cannot be represented in the current code page" warning for non-US windows.
Arguments.Append(" /wd4819");
}
// @todo UWP: UE4 is non-compliant when it comes to use of %s and %S
// Previously %s meant "the current character set" and %S meant "the other one".
// Now %s means multibyte and %S means wide. %Ts means "natural width".
// Reverting this behaviour until the UE4 source catches up.
if (WindowsPlatform.Compiler == WindowsCompiler.VisualStudio2015)
{
AddDefinition(Arguments, "_CRT_STDIO_LEGACY_WIDE_SPECIFIERS=1");
}
// @todo UWP: Silence the hash_map deprecation errors for now. This should be replaced with unordered_map for the real fix.
if (WindowsPlatform.Compiler == WindowsCompiler.VisualStudio2015)
{
AddDefinition(Arguments, "_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS=1");
}
if (BuildConfiguration.bUseSharedPCHs)
{
if (WindowsPlatform.bUseVCCompilerArgs)
{
// @todo SharedPCH: Disable warning about PCH defines not matching .cpp defines. We "cheat" these defines a little
// bit to make shared PCHs work. But it's totally safe. Trust us.
Arguments.Append(" /wd4651");
// @todo SharedPCH: Disable warning about redefining *API macros. The PCH header is compiled with various DLLIMPORTs, but
// when a module that uses that PCH header *IS* one of those imports, that module is compiled with EXPORTS, so the macro
// is redefined on the command-line. We need to clobber those defines to make shared PCHs work properly!
Arguments.Append(" /wd4005");
}
}
// If compiling as a DLL, set the relevant defines
if (CompileEnvironment.Config.bIsBuildingDLL)
{
AddDefinition(Arguments, "_WINDLL");
}
// When targeting Windows XP with Visual Studio 2012+, we need to tell the compiler to use the older Windows SDK that works
// with Windows XP (http://blogs.msdn.com/b/vcblog/archive/2012/10/08/10357555.aspx)
if (bSupportWindowsXP)
{
AddDefinition(Arguments, "_USING_V110_SDK71_");
}
// Handle Common Language Runtime support (C++/CLI)
if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLREnabled)
{
Arguments.Append(" /clr");
// Don't use default lib path, we override it explicitly to use the 4.0 reference assemblies.
Arguments.Append(" /clr:nostdlib");
}
//
// Debug
//
if (CompileEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Debug)
{
if (WindowsPlatform.bUseVCCompilerArgs)
{
// Disable compiler optimization.
Arguments.Append(" /Od");
// Favor code size (especially useful for embedded platforms).
Arguments.Append(" /Os");
// Allow inline method expansion unless E&C support is requested
if (!BuildConfiguration.bSupportEditAndContinue)
{
Arguments.Append(" /Ob2");
}
if ((CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.Win32) ||
(CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64))
{
// Runtime stack checks are not allowed when compiling for CLR
if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLRDisabled)
{
Arguments.Append(" /RTCs");
}
}
}
}
//
// Development and LTCG
//
else
{
if (WindowsPlatform.bUseVCCompilerArgs)
{
if (CompileEnvironment.Config.OptimizeCode == ModuleRules.CodeOptimization.Never ||
(CompileEnvironment.Config.OptimizeCode == ModuleRules.CodeOptimization.InShippingBuildsOnly && CompileEnvironment.Config.Target.Configuration != CPPTargetConfiguration.Shipping))
{
// Disable compiler optimization.
Arguments.Append(" /Od");
}
else
{
// Maximum optimizations if desired.
if (CompileEnvironment.Config.OptimizeCode >= ModuleRules.CodeOptimization.InNonDebugBuilds)
{
Arguments.Append(" /Ox");
}
// Favor code speed.
Arguments.Append(" /Ot");
// Only omit frame pointers on the PC (which is implied by /Ox) if wanted.
if (BuildConfiguration.bOmitFramePointers == false
&& ((CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.Win32) ||
(CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64)))
{
Arguments.Append(" /Oy-");
}
}
// Allow inline method expansion
Arguments.Append(" /Ob2");
//
// LTCG
//
if (CompileEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Shipping)
{
if (BuildConfiguration.bAllowLTCG)
{
// Enable link-time code generation.
Arguments.Append(" /GL");
}
}
}
else
{
if (CompileEnvironment.Config.OptimizeCode != ModuleRules.CodeOptimization.InShippingBuildsOnly || CompileEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Shipping)
{
// Maximum optimizations if desired.
if (CompileEnvironment.Config.OptimizeCode >= ModuleRules.CodeOptimization.InNonDebugBuilds)
{
Arguments.Append(" -O3");
}
}
}
}
//
// PC
//
if ((CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.Win32) ||
(CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64))
{
// SSE options are not allowed when using CLR compilation
if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLRDisabled)
{
if (WindowsPlatform.bUseVCCompilerArgs)
{
if (CompileEnvironment.Config.bUseAVX)
{
// Allow the compiler to generate AVX instructions.
Arguments.Append(" /arch:AVX");
}
// SSE options are not allowed when using the 64 bit toolchain
// (enables SSE2 automatically)
else if (CompileEnvironment.Config.Target.Platform != CPPTargetPlatform.Win64)
{
// Allow the compiler to generate SSE2 instructions.
Arguments.Append(" /arch:SSE2");
}
}
else
{
if (CompileEnvironment.Config.bUseAVX)
{
// Allow the compiler to generate AVX instructions.
Arguments.Append(" -mavx");
}
else if (CompileEnvironment.Config.Target.Platform != CPPTargetPlatform.Win64)
{
// Allow the compiler to generate SSE2 instructions.
Arguments.Append(" -msse2");
}
}
}
if (WindowsPlatform.bUseVCCompilerArgs)
{
// Prompt the user before reporting internal errors to Microsoft.
Arguments.Append(" /errorReport:prompt");
}
if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLRDisabled)
{
// Enable C++ exceptions when building with the editor or when building UHT.
if (!WindowsPlatform.bCompileWithClang && // @todo clang: C++ exceptions are not supported with Clang on Windows yet
(CompileEnvironment.Config.bEnableExceptions || UEBuildConfiguration.bBuildEditor || UEBuildConfiguration.bForceEnableExceptions))
{
// Enable C++ exception handling, but not C exceptions.
Arguments.Append(" /EHsc");
}
else
{
// This is required to disable exception handling in VC platform headers.
CompileEnvironment.Config.Definitions.Add("_HAS_EXCEPTIONS=0");
if (!WindowsPlatform.bUseVCCompilerArgs)
{
Arguments.Append(" -fno-exceptions");
}
}
}
else
{
// For C++/CLI all exceptions must be left enabled
Arguments.Append(" /EHa");
}
}
else if (CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.HTML5)
{
Arguments.Append(" /EHsc");
}
// If enabled, create debug information.
if (CompileEnvironment.Config.bCreateDebugInfo)
{
if (WindowsPlatform.bUseVCCompilerArgs)
{
// Store debug info in .pdb files.
// @todo clang: PDB files are emited from Clang but do not fully work with Visual Studio yet (breakpoints won't hit due to "symbol read error")
// @todo clang (update): as of clang 3.9 breakpoints work with PDBs, and the callstack is correct, so could be used for crash dumps. However debugging is still impossible due to the large amount of unreadable variables and unpredictable breakpoint stepping behaviour
if (BuildConfiguration.bUsePDBFiles)
{
// Create debug info suitable for E&C if wanted.
if (BuildConfiguration.bSupportEditAndContinue &&
// We only need to do this in debug as that's the only configuration that supports E&C.
CompileEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Debug)
{
Arguments.Append(" /ZI");
}
// Regular PDB debug information.
else
{
Arguments.Append(" /Zi");
}
// We need to add this so VS won't lock the PDB file and prevent synchronous updates. This forces serialization through MSPDBSRV.exe.
// See http://msdn.microsoft.com/en-us/library/dn502518.aspx for deeper discussion of /FS switch.
if (BuildConfiguration.bUseIncrementalLinking)
{
Arguments.Append(" /FS");
}
}
// Store C7-format debug info in the .obj files, which is faster.
else
{
Arguments.Append(" /Z7");
}
}
}
// Specify the appropriate runtime library based on the platform and config.
if (CompileEnvironment.Config.bUseStaticCRT)
{
if (CompileEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Debug && BuildConfiguration.bDebugBuildsActuallyUseDebugCRT)
{
if (WindowsPlatform.bUseVCCompilerArgs)
{
Arguments.Append(" /MTd");
}
else
{
AddDefinition(Arguments, "_MT");
AddDefinition(Arguments, "_DEBUG");
}
}
else
{
if (WindowsPlatform.bUseVCCompilerArgs)
{
Arguments.Append(" /MT");
}
else
{
AddDefinition(Arguments, "_MT");
}
}
}
else
{
if (CompileEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Debug && BuildConfiguration.bDebugBuildsActuallyUseDebugCRT)
{
if (WindowsPlatform.bUseVCCompilerArgs)
{
Arguments.Append(" /MDd");
}
else
{
AddDefinition(Arguments, "_MT");
AddDefinition(Arguments, "_DEBUG");
AddDefinition(Arguments, "_DLL");
}
}
else
{
if (WindowsPlatform.bUseVCCompilerArgs)
{
Arguments.Append(" /MD");
}
else
{
AddDefinition(Arguments, "_MT");
AddDefinition(Arguments, "_DLL");
}
}
}
if (WindowsPlatform.bUseVCCompilerArgs && !BuildConfiguration.bRunUnrealCodeAnalyzer)
{
if (!WindowsPlatform.bCompileWithClang)
{
// Allow large object files to avoid hitting the 2^16 section limit when running with -StressTestUnity.
// Note: not needed for clang, it implicitly upgrades COFF files to bigobj format when necessary.
Arguments.Append(" /bigobj");
}
// Relaxes floating point precision semantics to allow more optimization.
Arguments.Append(" /fp:fast");
if (CompileEnvironment.Config.OptimizeCode >= ModuleRules.CodeOptimization.InNonDebugBuilds)
{
// Allow optimized code to be debugged more easily. This makes PDBs a bit larger, but doesn't noticeably affect
// compile times. The executable code is not affected at all by this switch, only the debugging information.
if (EnvVars.CLExeVersion >= new Version("18.0.30723"))
{
// VC2013 Update 3 has a new flag for doing this
Arguments.Append(" /Zo");
}
else
{
Arguments.Append(" /d2Zi+");
}
}
}
else
{
// Relaxes floating point precision semantics to allow more optimization.
Arguments.Append(" -ffast-math");
}
// Disabled when compiling UnrealCodeAnalyzer as it breaks compilation (some structs in clang/llvm headers require 8-byte alignment in 32-bit compilation)
if (!UnrealBuildTool.CommandLineContains(@"UnrealCodeAnalyzer")
// and when running UnrealCodeAnalyzer as it doesn't understand the /Zp syntax.
&& !BuildConfiguration.bRunUnrealCodeAnalyzer)
{
if (CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64)
{
// Pack struct members on 8-byte boundaries.
if (WindowsPlatform.bUseVCCompilerArgs)
{
Arguments.Append(" /Zp8");
}
else
{
Arguments.Append(" -fpack-struct=8");
}
}
else
{
// Pack struct members on 4-byte boundaries.
if (WindowsPlatform.bUseVCCompilerArgs)
{
Arguments.Append(" /Zp4");
}
else
{
Arguments.Append(" -fpack-struct=4");
}
}
}
//
// Options required by UnrealCodeAnalyzer
//
if (BuildConfiguration.bRunUnrealCodeAnalyzer)
{
AddDefinition(Arguments, "UNREAL_CODE_ANALYZER");
}
//@todo: Disable warnings for VS2015. These should be reenabled as we clear the reasons for them out of the engine source and the VS2015 toolchain evolves.
if (WindowsPlatform.Compiler == WindowsCompiler.VisualStudio2015 && WindowsPlatform.bUseVCCompilerArgs)
{
// Disable shadow variable warnings
if (CompileEnvironment.Config.bEnableShadowVariableWarning == false)
{
Arguments.Append(" /wd4456"); // 4456 - declaration of 'LocalVariable' hides previous local declaration
Arguments.Append(" /wd4458"); // 4458 - declaration of 'parameter' hides class member
Arguments.Append(" /wd4459"); // 4459 - declaration of 'LocalVariable' hides global declaration
}
Arguments.Append(" /wd4463"); // 4463 - overflow; assigning 1 to bit-field that can only hold values from -1 to 0
Arguments.Append(" /wd4838"); // 4838: conversion from 'type1' to 'type2' requires a narrowing conversion
}
}
static void AppendCLArguments_CPP(CPPEnvironment CompileEnvironment, StringBuilder Arguments)
{
if (!WindowsPlatform.bCompileWithClang)
{
// Explicitly compile the file as C++.
Arguments.Append(" /TP");
}
else
{
string FileSpecifier = "c++";
if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Create)
{
// Tell Clang to generate a PCH header
FileSpecifier += "-header";
}
if (WindowsPlatform.bUseVCCompilerArgs)
{
Arguments.AppendFormat(" -Xclang -x -Xclang \"{0}\"", FileSpecifier);
}
else
{
Arguments.Append(" -x " + FileSpecifier);
}
}
if (!CompileEnvironment.Config.bEnableBufferSecurityChecks)
{
if (WindowsPlatform.bUseVCCompilerArgs)
{
// This will disable buffer security checks (which are enabled by default) that the MS compiler adds around arrays on the stack,
// Which can add some performance overhead, especially in performance intensive code
// Only disable this if you know what you are doing, because it will be disabled for the entire module!
Arguments.Append(" /GS-");
}
else
{
Arguments.Append(" -fno-stack-protector");
}
}
// C++/CLI requires that RTTI is left enabled
if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLRDisabled)
{
if (CompileEnvironment.Config.bUseRTTI)
{
// Enable C++ RTTI.
if (WindowsPlatform.bUseVCCompilerArgs)
{
Arguments.Append(" /GR");
}
else
{
Arguments.Append(" -Xclang -frtti-data");
}
}
else
{
// Disable C++ RTTI.
if (WindowsPlatform.bUseVCCompilerArgs)
{
Arguments.Append(" /GR-");
}
else
{
Arguments.Append(" -Xclang -fno-rtti-data");
}
}
}
// Set warning level.
if (WindowsPlatform.bUseVCCompilerArgs)
{
if (!BuildConfiguration.bRunUnrealCodeAnalyzer)
{
// Restrictive during regular compilation.
Arguments.Append(" /W4");
}
else
{
// If we had /W4 with clang on windows we would be flooded with warnings. This will be fixed incrementally.
Arguments.Append(" /W0");
}
}
else
{
Arguments.Append(" -Wall");
}
if (WindowsPlatform.bCompileWithClang)
{
// Disable specific warnings that cause problems with Clang
// NOTE: These must appear after we set the MSVC warning level
// @todo clang: Ideally we want as few warnings disabled as possible
//
// Allow Microsoft-specific syntax to slide, even though it may be non-standard. Needed for Windows headers.
Arguments.Append(" -Wno-microsoft");
// @todo clang: Hack due to how we have our 'DummyPCH' wrappers setup when using unity builds. This warning should not be disabled!!
Arguments.Append(" -Wno-msvc-include");
if (CompileEnvironment.Config.bEnableShadowVariableWarning)
{
Arguments.Append(" -Wshadow" + (BuildConfiguration.bShadowVariableErrors ? "" : " -Wno-error=shadow"));
}
// @todo clang: Kind of a shame to turn these off. We'd like to catch unused variables, but it is tricky with how our assertion macros work.
Arguments.Append(" -Wno-inconsistent-missing-override");
Arguments.Append(" -Wno-unused-variable");
Arguments.Append(" -Wno-unused-local-typedefs");
Arguments.Append(" -Wno-unused-function");
Arguments.Append(" -Wno-unused-private-field");
Arguments.Append(" -Wno-unused-value");
Arguments.Append(" -Wno-inline-new-delete"); // @todo clang: We declare operator new as inline. Clang doesn't seem to like that.
Arguments.Append(" -Wno-implicit-exception-spec-mismatch");
// Sometimes we compare 'this' pointers against nullptr, which Clang warns about by default
Arguments.Append(" -Wno-undefined-bool-conversion");
// @todo clang: Disabled warnings were copied from MacToolChain for the most part
Arguments.Append(" -Wno-deprecated-declarations");
Arguments.Append(" -Wno-deprecated-writable-strings");
Arguments.Append(" -Wno-deprecated-register");
Arguments.Append(" -Wno-switch-enum");
Arguments.Append(" -Wno-logical-op-parentheses"); // needed for external headers we shan't change
Arguments.Append(" -Wno-null-arithmetic"); // needed for external headers we shan't change
Arguments.Append(" -Wno-deprecated-declarations"); // needed for wxWidgets
Arguments.Append(" -Wno-return-type-c-linkage"); // needed for PhysX
Arguments.Append(" -Wno-ignored-attributes"); // needed for nvtesslib
Arguments.Append(" -Wno-uninitialized");
Arguments.Append(" -Wno-tautological-compare");
Arguments.Append(" -Wno-switch");
Arguments.Append(" -Wno-invalid-offsetof"); // needed to suppress warnings about using offsetof on non-POD types.
// @todo clang: Sorry for adding more of these, but I couldn't read my output log. Most should probably be looked at
Arguments.Append(" -Wno-unused-parameter"); // Unused function parameter. A lot are named 'bUnused'...
Arguments.Append(" -Wno-ignored-qualifiers"); // const ignored when returning by value e.g. 'const int foo() { return 4; }'
Arguments.Append(" -Wno-expansion-to-defined"); // Usage of 'defined(X)' in a macro definition. Gives different results under MSVC
Arguments.Append(" -Wno-gnu-string-literal-operator-template"); // String literal operator"" in template, used by delegates
Arguments.Append(" -Wno-sign-compare"); // Signed/unsigned comparison - millions of these
Arguments.Append(" -Wno-undefined-var-template"); // Variable template instantiation required but no definition available
Arguments.Append(" -Wno-missing-field-initializers"); // Stupid warning, generated when you initialize with MyStruct A = {0};
}
}
static void AppendCLArguments_C(StringBuilder Arguments)
{
if (WindowsPlatform.bUseVCCompilerArgs)
{
Arguments.Append(" -x c");
}
else
{
// Explicitly compile the file as C.
Arguments.Append(" /TC");
}
if (WindowsPlatform.bUseVCCompilerArgs)
{
// Level 0 warnings. Needed for external C projects that produce warnings at higher warning levels.
Arguments.Append(" /W0");
}
}
void AppendLinkArguments(LinkEnvironment LinkEnvironment, StringBuilder Arguments)
{
if (WindowsPlatform.bCompileWithClang && WindowsPlatform.bAllowClangLinker)
{
// This tells LLD to run in "Windows emulation" mode, meaning that it will accept MSVC Link arguments
Arguments.Append(" -flavor link");
// @todo clang: The following static libraries aren't linking correctly with Clang:
// tbbmalloc.lib, zlib_64.lib, libpng_64.lib, freetype2412MT.lib, IlmImf.lib
// LLD: Assertion failed: result.size() == 1, file ..\tools\lld\lib\ReaderWriter\FileArchive.cpp, line 71
//
// Only omit frame pointers on the PC (which is implied by /Ox) if wanted.
if (!BuildConfiguration.bOmitFramePointers)
{
Arguments.Append(" --disable-fp-elim");
}
}
// Don't create a side-by-side manifest file for the executable.
Arguments.Append(" /MANIFEST:NO");
// Prevents the linker from displaying its logo for each invocation.
Arguments.Append(" /NOLOGO");
if (LinkEnvironment.Config.bCreateDebugInfo)
{
// Output debug info for the linked executable.
Arguments.Append(" /DEBUG");
// Allow partial PDBs for faster linking
if (WindowsPlatform.Compiler == WindowsCompiler.VisualStudio2015 && BuildConfiguration.bUseFastPDBLinking)
{
Arguments.Append(":FASTLINK");
}
}
// Prompt the user before reporting internal errors to Microsoft.
Arguments.Append(" /errorReport:prompt");
//
// PC
//
if ((LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win32) ||
(LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64))
{
// Set machine type/ architecture to be 64 bit.
if (LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64)
{
Arguments.Append(" /MACHINE:x64");
}
// 32 bit executable/ target.
else
{
Arguments.Append(" /MACHINE:x86");
}
{
if (LinkEnvironment.Config.bIsBuildingConsoleApplication)
{
Arguments.Append(" /SUBSYSTEM:CONSOLE");
}
else
{
Arguments.Append(" /SUBSYSTEM:WINDOWS");
}
// When targeting Windows XP in Visual Studio 2012+, we need to tell the linker we are going to support execution
// on that older platform. The compiler defaults to version 6.0+. We'll modify the SUBSYSTEM parameter here.
if (bSupportWindowsXP)
{
Arguments.Append(LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64 ? ",5.02" : ",5.01");
}
}
if (LinkEnvironment.Config.bIsBuildingConsoleApplication && !LinkEnvironment.Config.bIsBuildingDLL && !String.IsNullOrEmpty(LinkEnvironment.Config.WindowsEntryPointOverride))
{
// Use overridden entry point
Arguments.Append(" /ENTRY:" + LinkEnvironment.Config.WindowsEntryPointOverride);
}
// Allow the OS to load the EXE at different base addresses than its preferred base address.
Arguments.Append(" /FIXED:No");
// Option is only relevant with 32 bit toolchain.
if ((LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win32) && WindowsPlatform.bBuildLargeAddressAwareBinary)
{
// Disables the 2GB address space limit on 64-bit Windows and 32-bit Windows with /3GB specified in boot.ini
Arguments.Append(" /LARGEADDRESSAWARE");
}
// Explicitly declare that the executable is compatible with Data Execution Prevention.
Arguments.Append(" /NXCOMPAT");
// Set the default stack size.
Arguments.Append(" /STACK:5000000");
// E&C can't use /SAFESEH. Also, /SAFESEH isn't compatible with 64-bit linking
if (!BuildConfiguration.bSupportEditAndContinue &&
LinkEnvironment.Config.Target.Platform != CPPTargetPlatform.Win64)
{
// Generates a table of Safe Exception Handlers. Documentation isn't clear whether they actually mean
// Structured Exception Handlers.
Arguments.Append(" /SAFESEH");
}
// Allow delay-loaded DLLs to be explicitly unloaded.
Arguments.Append(" /DELAY:UNLOAD");
if (LinkEnvironment.Config.bIsBuildingDLL)
{
Arguments.Append(" /DLL");
}
if (LinkEnvironment.Config.CLRMode == CPPCLRMode.CLREnabled)
{
// DLLs built with managed code aren't allowed to have entry points as they will try to initialize
// complex static variables. Managed code isn't allowed to run during DLLMain, we can't allow
// these variables to be initialized here.
if (LinkEnvironment.Config.bIsBuildingDLL)
{
// NOTE: This appears to only be needed if we want to get call stacks for debugging exit crashes related to the above
// Result += " /NOENTRY /NODEFAULTLIB:nochkclr.obj";
}
}
}
// Don't embed the full PDB path; we want to be able to move binaries elsewhere. They will always be side by side.
Arguments.Append(" /PDBALTPATH:%_PDB%");
//
// Shipping & LTCG
//
if (BuildConfiguration.bAllowLTCG &&
LinkEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Shipping)
{
// Use link-time code generation.
Arguments.Append(" /LTCG");
// This is where we add in the PGO-Lite linkorder.txt if we are using PGO-Lite
//Result += " /ORDER:@linkorder.txt";
//Result += " /VERBOSE";
}
//
// Shipping binary
//
if (LinkEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Shipping)
{
// Generate an EXE checksum.
Arguments.Append(" /RELEASE");
// Eliminate unreferenced symbols.
Arguments.Append(" /OPT:REF");
// Remove redundant COMDATs.
Arguments.Append(" /OPT:ICF");
}
//
// Regular development binary.
//
else
{
// Keep symbols that are unreferenced.
Arguments.Append(" /OPT:NOREF");
// Disable identical COMDAT folding.
Arguments.Append(" /OPT:NOICF");
}
// Enable incremental linking if wanted.
// NOTE: Don't bother using incremental linking for C++/CLI projects, as that's not supported and the option
// will silently be ignored anyway
if (BuildConfiguration.bUseIncrementalLinking && LinkEnvironment.Config.CLRMode != CPPCLRMode.CLREnabled)
{
Arguments.Append(" /INCREMENTAL");
}
else
{
Arguments.Append(" /INCREMENTAL:NO");
}
// Disable
//LINK : warning LNK4199: /DELAYLOAD:nvtt_64.dll ignored; no imports found from nvtt_64.dll
// type warning as we leverage the DelayLoad option to put third-party DLLs into a
// non-standard location. This requires the module(s) that use said DLL to ensure that it
// is loaded prior to using it.
Arguments.Append(" /ignore:4199");
// Suppress warnings about missing PDB files for statically linked libraries. We often don't want to distribute
// PDB files for these libraries.
Arguments.Append(" /ignore:4099"); // warning LNK4099: PDB '<file>' was not found with '<file>'
}
void AppendLibArguments(LinkEnvironment LinkEnvironment, StringBuilder Arguments)
{
// Prevents the linker from displaying its logo for each invocation.
Arguments.Append(" /NOLOGO");
// Prompt the user before reporting internal errors to Microsoft.
Arguments.Append(" /errorReport:prompt");
//
// PC
//
if (LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win32 || LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64)
{
// Set machine type/ architecture to be 64 bit.
if (LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64)
{
Arguments.Append(" /MACHINE:x64");
}
// 32 bit executable/ target.
else
{
Arguments.Append(" /MACHINE:x86");
}
{
if (LinkEnvironment.Config.bIsBuildingConsoleApplication)
{
Arguments.Append(" /SUBSYSTEM:CONSOLE");
}
else
{
Arguments.Append(" /SUBSYSTEM:WINDOWS");
}
// When targeting Windows XP in Visual Studio 2012+, we need to tell the linker we are going to support execution
// on that older platform. The compiler defaults to version 6.0+. We'll modify the SUBSYSTEM parameter here.
if (bSupportWindowsXP)
{
Arguments.Append(LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64 ? ",5.02" : ",5.01");
}
}
}
//
// Shipping & LTCG
//
if (LinkEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Shipping)
{
// Use link-time code generation.
Arguments.Append(" /LTCG");
}
}
public override CPPOutput CompileCPPFiles(UEBuildTarget Target, CPPEnvironment CompileEnvironment, List<FileItem> SourceFiles, string ModuleName)
{
VCEnvironment EnvVars = VCEnvironment.SetEnvironment(CompileEnvironment.Config.Target.Platform, bSupportWindowsXP);
StringBuilder SharedArguments = new StringBuilder();
AppendCLArguments_Global(CompileEnvironment, EnvVars, SharedArguments);
// Add include paths to the argument list.
if (!BuildConfiguration.bRunUnrealCodeAnalyzer)
{
foreach (string IncludePath in CompileEnvironment.Config.CPPIncludeInfo.IncludePaths)
{
AddIncludePath(SharedArguments, IncludePath);
}
}
else
{
foreach (string IncludePath in CompileEnvironment.Config.CPPIncludeInfo.IncludePaths)
{
AddIncludePath(SharedArguments, System.IO.Path.GetFullPath(IncludePath));
}
}
foreach (string IncludePath in CompileEnvironment.Config.CPPIncludeInfo.SystemIncludePaths)
{
if (WindowsPlatform.bCompileWithClang)
{
// @todo Clang: Clang uses a special command-line syntax for system headers. This is used for two reasons. The first is that Clang will automatically
// suppress compiler warnings in headers found in these directories, such as the DirectX SDK headers. The other reason this is important is in the case
// where there the same header include path is passed as both a regular include path and a system include path (extracted from INCLUDE environment). In
// this case Clang will ignore any earlier occurrence of the include path, preventing a system header include path from overriding a different system
// include path set later on by a module. NOTE: When passing "-Xclang", these options will always appear at the end of the command-line string, meaning
// they will be forced to appear *after* all environment-variable-extracted includes. This is technically okay though.
if (WindowsPlatform.bUseVCCompilerArgs)
{
SharedArguments.AppendFormat(" -Xclang -internal-isystem -Xclang \"{0}\"", IncludePath);
}
else
{
SharedArguments.AppendFormat(" -isystem \"{0}\"", IncludePath);
}
}
else
{
AddIncludePath(SharedArguments, IncludePath);
}
}
if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLREnabled)
{
// Add .NET framework assembly paths. This is needed so that C++/CLI projects
// can reference assemblies with #using, without having to hard code a path in the
// .cpp file to the assembly's location.
foreach (string AssemblyPath in CompileEnvironment.Config.SystemDotNetAssemblyPaths)
{
SharedArguments.AppendFormat(" /AI \"{0}\"", AssemblyPath);
}
// Add explicit .NET framework assembly references
foreach (string AssemblyName in CompileEnvironment.Config.FrameworkAssemblyDependencies)
{
SharedArguments.AppendFormat(" /FU \"{0}\"", AssemblyName);
}
// Add private assembly references
foreach (PrivateAssemblyInfo CurAssemblyInfo in CompileEnvironment.PrivateAssemblyDependencies)
{
SharedArguments.AppendFormat(" /FU \"{0}\"", CurAssemblyInfo.FileItem.AbsolutePath);
}
}
if (!WindowsPlatform.bCompileWithClang && WindowsPlatform.bLogDetailedCompilerTimingInfo)
{
// Force MSVC
SharedArguments.Append(" /Bt+");
}
// Add preprocessor definitions to the argument list.
foreach (string Definition in CompileEnvironment.Config.Definitions)
{
// Escape all quotation marks so that they get properly passed with the command line.
string DefinitionArgument = Definition.Contains("\"") ? Definition.Replace("\"", "\\\"") : Definition;
AddDefinition(SharedArguments, DefinitionArgument);
}
UEBuildPlatform BuildPlatform = UEBuildPlatform.GetBuildPlatformForCPPTargetPlatform(CompileEnvironment.Config.Target.Platform);
// Create a compile action for each source file.
CPPOutput Result = new CPPOutput();
foreach (FileItem SourceFile in SourceFiles)
{
Action CompileAction = new Action(ActionType.Compile);
CompileAction.CommandDescription = "Compile";
// ensure compiler timings are captured when we execute the action.
if (!WindowsPlatform.bCompileWithClang && WindowsPlatform.bLogDetailedCompilerTimingInfo)
{
CompileAction.bPrintDebugInfo = true;
}
StringBuilder FileArguments = new StringBuilder();
bool bIsPlainCFile = Path.GetExtension(SourceFile.AbsolutePath).ToUpperInvariant() == ".C";
// Add the C++ source file and its included files to the prerequisite item list.
AddPrerequisiteSourceFile(Target, BuildPlatform, CompileEnvironment, SourceFile, CompileAction.PrerequisiteItems);
// If this is a CLR file then make sure our dependent assemblies are added as prerequisites
if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLREnabled)
{
foreach (PrivateAssemblyInfo CurPrivateAssemblyDependency in CompileEnvironment.PrivateAssemblyDependencies)
{
CompileAction.PrerequisiteItems.Add(CurPrivateAssemblyDependency.FileItem);
}
}
if (WindowsPlatform.bCompileWithClang)
{
CompileAction.OutputEventHandler = new DataReceivedEventHandler(ClangCompilerOutputFormatter);
}
bool bEmitsObjectFile = true;
if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Create)
{
// Generate a CPP File that just includes the precompiled header.
string PCHCPPFilename = "PCH." + CompileEnvironment.Config.PrecompiledHeaderIncludeFilename.GetFileName() + ".cpp";
FileReference PCHCPPPath = FileReference.Combine(CompileEnvironment.Config.OutputDirectory, PCHCPPFilename);
FileItem PCHCPPFile = FileItem.CreateIntermediateTextFile(
PCHCPPPath,
string.Format("#include \"{0}\"\r\n", CompileEnvironment.Config.PrecompiledHeaderIncludeFilename)
);
// Make sure the original source directory the PCH header file existed in is added as an include
// path -- it might be a private PCH header and we need to make sure that its found!
string OriginalPCHHeaderDirectory = Path.GetDirectoryName(SourceFile.AbsolutePath);
AddIncludePath(FileArguments, OriginalPCHHeaderDirectory);
string PrecompiledFileExtension = UEBuildPlatform.GetBuildPlatform(UnrealTargetPlatform.Win64).GetBinaryExtension(UEBuildBinaryType.PrecompiledHeader);
// Add the precompiled header file to the produced items list.
FileItem PrecompiledHeaderFile = FileItem.GetItemByFileReference(
FileReference.Combine(
CompileEnvironment.Config.PCHOutputDirectory ?? CompileEnvironment.Config.OutputDirectory,
Path.GetFileName(SourceFile.AbsolutePath) + PrecompiledFileExtension
)
);
CompileAction.ProducedItems.Add(PrecompiledHeaderFile);
Result.PrecompiledHeaderFile = PrecompiledHeaderFile;
if (WindowsPlatform.bUseVCCompilerArgs)
{
// Add the parameters needed to compile the precompiled header file to the command-line.
FileArguments.AppendFormat(" /Yc\"{0}\"", CompileEnvironment.Config.PrecompiledHeaderIncludeFilename);
FileArguments.AppendFormat(" /Fp\"{0}\"", PrecompiledHeaderFile.AbsolutePath);
// If we're creating a PCH that will be used to compile source files for a library, we need
// the compiled modules to retain a reference to PCH's module, so that debugging information
// will be included in the library. This is also required to avoid linker warning "LNK4206"
// when linking an application that uses this library.
if (CompileEnvironment.Config.bIsBuildingLibrary)
{
// NOTE: The symbol name we use here is arbitrary, and all that matters is that it is
// unique per PCH module used in our library
string FakeUniquePCHSymbolName = CompileEnvironment.Config.PrecompiledHeaderIncludeFilename.GetFileNameWithoutExtension();
FileArguments.AppendFormat(" /Yl{0}", FakeUniquePCHSymbolName);
}
}
else
{
FileArguments.Append(" -Xclang -emit-pch");
FileArguments.AppendFormat(" -o\"{0}\"", PrecompiledHeaderFile.AbsolutePath);
// Clang PCH generation doesn't create an .obj file to link in, unlike MSVC
bEmitsObjectFile = false;
}
FileArguments.AppendFormat(" \"{0}\"", PCHCPPFile.AbsolutePath);
CompileAction.StatusDescription = PCHCPPFilename;
}
else
{
if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Include)
{
CompileAction.bIsUsingPCH = true;
CompileAction.PrerequisiteItems.Add(CompileEnvironment.PrecompiledHeaderFile);
if (WindowsPlatform.bCompileWithClang)
{
// NOTE: With Clang, PCH headers are ALWAYS forcibly included!
// NOTE: This needs to be before the other include paths to ensure Clang uses it instead of the source header file.
if (WindowsPlatform.bUseVCCompilerArgs)
{
FileArguments.AppendFormat(" /FI\"{0}\"", Path.ChangeExtension(CompileEnvironment.PrecompiledHeaderFile.AbsolutePath, null));
}
else
{
// FileArguments.Append( " -Xclang -fno-validate-pch" ); // @todo clang: Interesting option for faster performance
FileArguments.AppendFormat(" -include-pch \"{0}\"", CompileEnvironment.PrecompiledHeaderFile.AbsolutePath);
}
}
else
{
FileArguments.AppendFormat(" /Yu\"{0}\"", CompileEnvironment.Config.PCHHeaderNameInCode);
FileArguments.AppendFormat(" /Fp\"{0}\"", CompileEnvironment.PrecompiledHeaderFile.AbsolutePath);
// Is it unsafe to always force inclusion? Clang is doing it, and .generated.cpp files
// won't work otherwise, because they're not located in the context of the module,
// so they can't access the module's PCH without an absolute path.
//if( CompileEnvironment.Config.bForceIncludePrecompiledHeader )
{
// Force include the precompiled header file. This is needed because we may have selected a
// precompiled header that is different than the first direct include in the C++ source file, but
// we still need to make sure that our precompiled header is the first thing included!
FileArguments.AppendFormat(" /FI\"{0}\"", CompileEnvironment.Config.PCHHeaderNameInCode);
}
}
}
// UnrealCodeAnalyzer requires compiled file name to be first argument.
if (!BuildConfiguration.bRunUnrealCodeAnalyzer)
{
// Add the source file path to the command-line.
FileArguments.AppendFormat(" \"{0}\"", SourceFile.AbsolutePath);
}
CompileAction.StatusDescription = Path.GetFileName(SourceFile.AbsolutePath);
}
if (bEmitsObjectFile)
{
string ObjectFileExtension = UEBuildPlatform.GetBuildPlatform(UnrealTargetPlatform.Win64).GetBinaryExtension(UEBuildBinaryType.Object);
// Add the object file to the produced item list.
FileItem ObjectFile = FileItem.GetItemByFileReference(
FileReference.Combine(
CompileEnvironment.Config.OutputDirectory,
Path.GetFileName(SourceFile.AbsolutePath) + ObjectFileExtension
)
);
CompileAction.ProducedItems.Add(ObjectFile);
Result.ObjectFiles.Add(ObjectFile);
// UnrealCodeAnalyzer requires specific position of output file.
if (!BuildConfiguration.bRunUnrealCodeAnalyzer)
{
if (WindowsPlatform.bUseVCCompilerArgs)
{
FileArguments.AppendFormat(" /Fo\"{0}\"", ObjectFile.AbsolutePath);
}
else
{
FileArguments.AppendFormat(" -o\"{0}\"", ObjectFile.AbsolutePath);
}
}
}
// Create PDB files if we were configured to do that.
//
// Also, when debug info is off and XGE is enabled, force PDBs, otherwise it will try to share
// a PDB file, which causes PCH creation to be serial rather than parallel (when debug info is disabled)
// --> See https://udn.epicgames.com/lists/showpost.php?id=50619&list=unprog3
if (BuildConfiguration.bUsePDBFiles ||
(BuildConfiguration.bAllowXGE && !CompileEnvironment.Config.bCreateDebugInfo))
{
string PDBFileName;
bool bActionProducesPDB = false;
// All files using the same PCH are required to share the same PDB that was used when compiling the PCH
if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Include)
{
PDBFileName = "PCH." + CompileEnvironment.Config.PrecompiledHeaderIncludeFilename.GetFileName();
}
// Files creating a PCH use a PDB per file.
else if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Create)
{
PDBFileName = "PCH." + CompileEnvironment.Config.PrecompiledHeaderIncludeFilename.GetFileName();
bActionProducesPDB = true;
}
// Ungrouped C++ files use a PDB per file.
else if (!bIsPlainCFile)
{
PDBFileName = Path.GetFileName(SourceFile.AbsolutePath);
bActionProducesPDB = true;
}
// Group all plain C files that doesn't use PCH into the same PDB
else
{
PDBFileName = "MiscPlainC";
}
{
// Specify the PDB file that the compiler should write to.
FileItem PDBFile = FileItem.GetItemByFileReference(
FileReference.Combine(
CompileEnvironment.Config.OutputDirectory,
PDBFileName + ".pdb"
)
);
FileArguments.AppendFormat(" /Fd\"{0}\"", PDBFile.AbsolutePath);
// Only use the PDB as an output file if we want PDBs and this particular action is
// the one that produces the PDB (as opposed to no debug info, where the above code
// is needed, but not the output PDB, or when multiple files share a single PDB, so
// only the action that generates it should count it as output directly)
if (BuildConfiguration.bUsePDBFiles && bActionProducesPDB)
{
CompileAction.ProducedItems.Add(PDBFile);
Result.DebugDataFiles.Add(PDBFile);
}
}
}
// Add C or C++ specific compiler arguments.
if (bIsPlainCFile)
{
AppendCLArguments_C(FileArguments);
}
else
{
AppendCLArguments_CPP(CompileEnvironment, FileArguments);
}
CompileAction.WorkingDirectory = UnrealBuildTool.EngineSourceDirectory.FullName;
if (BuildConfiguration.bRunUnrealCodeAnalyzer)
{
CompileAction.CommandPath = System.IO.Path.Combine(CompileAction.WorkingDirectory, @"..", @"Binaries", @"Win32", @"UnrealCodeAnalyzer.exe");
}
else
{
CompileAction.CommandPath = EnvVars.CompilerPath;
}
string UnrealCodeAnalyzerArguments = "";
if (BuildConfiguration.bRunUnrealCodeAnalyzer)
{
string UCAMode = BuildConfiguration.bUCACheckUObjectThreadSafety ? @"-CheckThreadSafety " : @"-CreateIncludeFiles ";
string ObjectFileExtension = BuildConfiguration.bUCACheckUObjectThreadSafety ? @".tsc" : @".includes";
FileItem ObjectFile = FileItem.GetItemByFileReference(FileReference.Combine(CompileEnvironment.Config.OutputDirectory, Path.GetFileName(SourceFile.AbsolutePath) + ObjectFileExtension));
string ClangPath = System.IO.Path.Combine(CompileAction.WorkingDirectory, @"ThirdParty", @"llvm", @"3.6.2", @"bin", @"vs2015", @"x86", @"release", @"clang++.exe");
UnrealCodeAnalyzerArguments = UCAMode + SourceFile.AbsolutePath + @" -OutputFile=""" + ObjectFile.AbsolutePath + @""" -- " + ClangPath + @" --driver-mode=cl ";
}
if (!ProjectFileGenerator.bGenerateProjectFiles
&& !WindowsPlatform.bCompileWithClang
&& CompileAction.ProducedItems.Count > 0)
{
FileItem TargetFile = CompileAction.ProducedItems[0];
string ResponseFileName = TargetFile.AbsolutePath + ".response";
List<string> ResponseLines = new List<string>();
string ResponseLine = UnrealCodeAnalyzerArguments + SharedArguments.ToString() + FileArguments.ToString() + CompileEnvironment.Config.AdditionalArguments;
ResponseLines.Add(ActionThread.ExpandEnvironmentVariables(ResponseLine));
ResponseFile.Create(new FileReference(ResponseFileName), ResponseLines);
CompileAction.CommandArguments = " @\"" + ResponseFileName + "\"";
CompileAction.PrerequisiteItems.Add(FileItem.GetExistingItemByPath(ResponseFileName));
}
else
{
CompileAction.CommandArguments = UnrealCodeAnalyzerArguments + SharedArguments.ToString() + FileArguments.ToString() + CompileEnvironment.Config.AdditionalArguments;
}
if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Create)
{
Log.TraceVerbose("Creating PCH " + CompileEnvironment.Config.PrecompiledHeaderIncludeFilename + ": \"" + CompileAction.CommandPath + "\"" + CompileAction.CommandArguments);
}
else
{
Log.TraceVerbose(" Compiling " + CompileAction.StatusDescription + ": \"" + CompileAction.CommandPath + "\"" + CompileAction.CommandArguments);
}
if (WindowsPlatform.bCompileWithClang || BuildConfiguration.bRunUnrealCodeAnalyzer)
{
// Clang doesn't print the file names by default, so we'll do it ourselves
CompileAction.bShouldOutputStatusDescription = true;
}
else
{
// VC++ always outputs the source file name being compiled, so we don't need to emit this ourselves
CompileAction.bShouldOutputStatusDescription = false;
}
// Don't farm out creation of precompiled headers as it is the critical path task.
CompileAction.bCanExecuteRemotely =
CompileEnvironment.Config.PrecompiledHeaderAction != PrecompiledHeaderAction.Create ||
BuildConfiguration.bAllowRemotelyCompiledPCHs
;
// @todo: XGE has problems remote compiling C++/CLI files that use .NET Framework 4.0
if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLREnabled)
{
CompileAction.bCanExecuteRemotely = false;
}
// When compiling with SN-DBS, modules that contain a #import must be built locally
if (CompileEnvironment.Config.bBuildLocallyWithSNDBS == true && BuildConfiguration.bAllowSNDBS)
{
CompileAction.bCanExecuteRemotely = false;
}
}
return Result;
}
public override CPPOutput CompileRCFiles(UEBuildTarget Target, CPPEnvironment Environment, List<FileItem> RCFiles)
{
VCEnvironment EnvVars = VCEnvironment.SetEnvironment(Environment.Config.Target.Platform, bSupportWindowsXP);
CPPOutput Result = new CPPOutput();
UEBuildPlatform BuildPlatform = UEBuildPlatform.GetBuildPlatformForCPPTargetPlatform(Environment.Config.Target.Platform);
foreach (FileItem RCFile in RCFiles)
{
Action CompileAction = new Action(ActionType.Compile);
CompileAction.CommandDescription = "Resource";
CompileAction.WorkingDirectory = UnrealBuildTool.EngineSourceDirectory.FullName;
CompileAction.CommandPath = EnvVars.ResourceCompilerPath;
CompileAction.StatusDescription = Path.GetFileName(RCFile.AbsolutePath);
// Resource tool can run remotely if possible
CompileAction.bCanExecuteRemotely = true;
StringBuilder Arguments = new StringBuilder();
if (WindowsPlatform.bCompileWithClang)
{
CompileAction.OutputEventHandler = new DataReceivedEventHandler(ClangCompilerOutputFormatter);
}
// Suppress header spew
Arguments.Append(" /nologo");
// If we're compiling for 64-bit Windows, also add the _WIN64 definition to the resource
// compiler so that we can switch on that in the .rc file using #ifdef.
if (Environment.Config.Target.Platform == CPPTargetPlatform.Win64)
{
AddDefinition(Arguments, "_WIN64");
}
// When targeting Windows XP with Visual Studio 2012+, we need to tell the compiler to use the older Windows SDK that works
// with Windows XP (http://blogs.msdn.com/b/vcblog/archive/2012/10/08/10357555.aspx)
if (bSupportWindowsXP)
{
AddDefinition(Arguments, "_USING_V110_SDK71_");
}
// Language
Arguments.AppendFormat(" /l 0x409");
// Include paths.
foreach (string IncludePath in Environment.Config.CPPIncludeInfo.IncludePaths)
{
AddIncludePath(Arguments, IncludePath);
}
// System include paths.
foreach (string SystemIncludePath in Environment.Config.CPPIncludeInfo.SystemIncludePaths)
{
AddIncludePath(Arguments, SystemIncludePath);
}
// Preprocessor definitions.
foreach (string Definition in Environment.Config.Definitions)
{
AddDefinition(Arguments, Definition);
}
// Add the RES file to the produced item list.
FileItem CompiledResourceFile = FileItem.GetItemByFileReference(
FileReference.Combine(
Environment.Config.OutputDirectory,
Path.GetFileName(RCFile.AbsolutePath) + ".res"
)
);
CompileAction.ProducedItems.Add(CompiledResourceFile);
Arguments.AppendFormat(" /fo \"{0}\"", CompiledResourceFile.AbsolutePath);
Result.ObjectFiles.Add(CompiledResourceFile);
// Add the RC file as a prerequisite of the action.
Arguments.AppendFormat(" \"{0}\"", RCFile.AbsolutePath);
CompileAction.CommandArguments = Arguments.ToString();
// Add the C++ source file and its included files to the prerequisite item list.
AddPrerequisiteSourceFile(Target, BuildPlatform, Environment, RCFile, CompileAction.PrerequisiteItems);
}
return Result;
}
public override FileItem LinkFiles(LinkEnvironment LinkEnvironment, bool bBuildImportLibraryOnly)
{
VCEnvironment EnvVars = VCEnvironment.SetEnvironment(LinkEnvironment.Config.Target.Platform, bSupportWindowsXP);
// @todo UWP: These paths should be added in SetUpEnvironment(), not here. Also is this actually needed for classic desktop targets or only UWP?
if (WindowsPlatform.Compiler == WindowsCompiler.VisualStudio2015 && WindowsPlatform.bUseWindowsSDK10)
{
if (LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64)
{
LinkEnvironment.Config.LibraryPaths.Add(string.Format("{0}/Lib/um/x64", EnvVars.NetFxSDKExtensionDir));
}
else if (LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win32)
{
LinkEnvironment.Config.LibraryPaths.Add(string.Format("{0}/Lib/um/x86", EnvVars.NetFxSDKExtensionDir));
}
}
if (LinkEnvironment.Config.bIsBuildingDotNetAssembly)
{
return FileItem.GetItemByFileReference(LinkEnvironment.Config.OutputFilePath);
}
bool bIsBuildingLibrary = LinkEnvironment.Config.bIsBuildingLibrary || bBuildImportLibraryOnly;
bool bIncludeDependentLibrariesInLibrary = bIsBuildingLibrary && LinkEnvironment.Config.bIncludeDependentLibrariesInLibrary;
// Get link arguments.
StringBuilder Arguments = new StringBuilder();
if (bIsBuildingLibrary)
{
AppendLibArguments(LinkEnvironment, Arguments);
}
else
{
AppendLinkArguments(LinkEnvironment, Arguments);
}
if (!WindowsPlatform.bCompileWithClang && WindowsPlatform.bLogDetailedCompilerTimingInfo)
{
Arguments.Append(" /time+");
}
// If we're only building an import library, add the '/DEF' option that tells the LIB utility
// to simply create a .LIB file and .EXP file, and don't bother validating imports
if (bBuildImportLibraryOnly)
{
Arguments.Append(" /DEF");
// Ensure that the import library references the correct filename for the linked binary.
Arguments.AppendFormat(" /NAME:\"{0}\"", LinkEnvironment.Config.OutputFilePath.GetFileName());
}
// Add delay loaded DLLs.
if (!bIsBuildingLibrary)
{
// Delay-load these DLLs.
foreach (string DelayLoadDLL in LinkEnvironment.Config.DelayLoadDLLs)
{
Arguments.AppendFormat(" /DELAYLOAD:\"{0}\"", DelayLoadDLL);
}
}
// @todo UE4 DLL: Why do I need LIBPATHs to build only export libraries with /DEF? (tbbmalloc.lib)
if (!LinkEnvironment.Config.bIsBuildingLibrary || (LinkEnvironment.Config.bIsBuildingLibrary && bIncludeDependentLibrariesInLibrary))
{
// Add the library paths to the argument list.
foreach (string LibraryPath in LinkEnvironment.Config.LibraryPaths)
{
Arguments.AppendFormat(" /LIBPATH:\"{0}\"", LibraryPath);
}
// Add the excluded default libraries to the argument list.
foreach (string ExcludedLibrary in LinkEnvironment.Config.ExcludedLibraries)
{
Arguments.AppendFormat(" /NODEFAULTLIB:\"{0}\"", ExcludedLibrary);
}
}
// For targets that are cross-referenced, we don't want to write a LIB file during the link step as that
// file will clobber the import library we went out of our way to generate during an earlier step. This
// file is not needed for our builds, but there is no way to prevent MSVC from generating it when
// linking targets that have exports. We don't want this to clobber our LIB file and invalidate the
// existing timstamp, so instead we simply emit it with a different name
FileReference ImportLibraryFilePath = FileReference.Combine(LinkEnvironment.Config.IntermediateDirectory,
LinkEnvironment.Config.OutputFilePath.GetFileNameWithoutExtension() + ".lib");
if (LinkEnvironment.Config.bIsCrossReferenced && !bBuildImportLibraryOnly)
{
ImportLibraryFilePath += ".suppressed";
}
FileItem OutputFile;
if (bBuildImportLibraryOnly)
{
OutputFile = FileItem.GetItemByFileReference(ImportLibraryFilePath);
}
else
{
OutputFile = FileItem.GetItemByFileReference(LinkEnvironment.Config.OutputFilePath);
OutputFile.bNeedsHotReloadNumbersDLLCleanUp = LinkEnvironment.Config.bIsBuildingDLL;
}
List<FileItem> ProducedItems = new List<FileItem>();
ProducedItems.Add(OutputFile);
List<FileItem> PrerequisiteItems = new List<FileItem>();
// Add the input files to a response file, and pass the response file on the command-line.
List<string> InputFileNames = new List<string>();
foreach (FileItem InputFile in LinkEnvironment.InputFiles)
{
InputFileNames.Add(string.Format("\"{0}\"", InputFile.AbsolutePath));
PrerequisiteItems.Add(InputFile);
}
if (!bBuildImportLibraryOnly)
{
// Add input libraries as prerequisites, too!
foreach (FileItem InputLibrary in LinkEnvironment.InputLibraries)
{
InputFileNames.Add(string.Format("\"{0}\"", InputLibrary.AbsolutePath));
PrerequisiteItems.Add(InputLibrary);
}
}
if (!bIsBuildingLibrary || (LinkEnvironment.Config.bIsBuildingLibrary && bIncludeDependentLibrariesInLibrary))
{
foreach (string AdditionalLibrary in LinkEnvironment.Config.AdditionalLibraries)
{
InputFileNames.Add(string.Format("\"{0}\"", AdditionalLibrary));
// If the library file name has a relative path attached (rather than relying on additional
// lib directories), then we'll add it to our prerequisites list. This will allow UBT to detect
// when the binary needs to be relinked because a dependent external library has changed.
//if( !String.IsNullOrEmpty( Path.GetDirectoryName( AdditionalLibrary ) ) )
{
PrerequisiteItems.Add(FileItem.GetItemByPath(AdditionalLibrary));
}
}
}
// Create a response file for the linker
FileReference ResponseFileName = GetResponseFileName(LinkEnvironment, OutputFile);
// Never create response files when we are only generating IntelliSense data
if (!ProjectFileGenerator.bGenerateProjectFiles)
{
ResponseFile.Create(ResponseFileName, InputFileNames);
}
Arguments.AppendFormat(" @\"{0}\"", ResponseFileName);
// Add the output file to the command-line.
Arguments.AppendFormat(" /OUT:\"{0}\"", OutputFile.AbsolutePath);
if (bBuildImportLibraryOnly || (LinkEnvironment.Config.bHasExports && !bIsBuildingLibrary))
{
// An export file is written to the output directory implicitly; add it to the produced items list.
FileReference ExportFilePath = ImportLibraryFilePath.ChangeExtension(".exp");
FileItem ExportFile = FileItem.GetItemByFileReference(ExportFilePath);
ProducedItems.Add(ExportFile);
}
if (!bIsBuildingLibrary)
{
// There is anything to export
if (LinkEnvironment.Config.bHasExports
// Shipping monolithic builds don't need exports
&& (!((LinkEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Shipping) && (LinkEnvironment.bShouldCompileMonolithic != false))))
{
// Write the import library to the output directory for nFringe support.
FileItem ImportLibraryFile = FileItem.GetItemByFileReference(ImportLibraryFilePath);
Arguments.AppendFormat(" /IMPLIB:\"{0}\"", ImportLibraryFilePath);
ProducedItems.Add(ImportLibraryFile);
}
if (LinkEnvironment.Config.bCreateDebugInfo)
{
// Write the PDB file to the output directory.
{
FileReference PDBFilePath = FileReference.Combine(LinkEnvironment.Config.OutputDirectory, Path.GetFileNameWithoutExtension(OutputFile.AbsolutePath) + ".pdb");
FileItem PDBFile = FileItem.GetItemByFileReference(PDBFilePath);
Arguments.AppendFormat(" /PDB:\"{0}\"", PDBFilePath);
ProducedItems.Add(PDBFile);
}
// Write the MAP file to the output directory.
if (BuildConfiguration.bCreateMapFile)
{
FileReference MAPFilePath = FileReference.Combine(LinkEnvironment.Config.OutputDirectory, Path.GetFileNameWithoutExtension(OutputFile.AbsolutePath) + ".map");
FileItem MAPFile = FileItem.GetItemByFileReference(MAPFilePath);
Arguments.AppendFormat(" /MAP:\"{0}\"", MAPFilePath);
ProducedItems.Add(MAPFile);
// Export a list of object file paths, so we can locate the object files referenced by the map file
ExportObjectFilePaths(LinkEnvironment, Path.ChangeExtension(MAPFilePath.FullName, ".objpaths"));
}
}
// Add the additional arguments specified by the environment.
Arguments.Append(LinkEnvironment.Config.AdditionalArguments);
}
// Create an action that invokes the linker.
Action LinkAction = new Action(ActionType.Link);
LinkAction.CommandDescription = "Link";
LinkAction.WorkingDirectory = UnrealBuildTool.EngineSourceDirectory.FullName;
LinkAction.CommandPath = bIsBuildingLibrary ? EnvVars.LibraryLinkerPath : EnvVars.LinkerPath;
LinkAction.CommandArguments = Arguments.ToString();
LinkAction.ProducedItems.AddRange(ProducedItems);
LinkAction.PrerequisiteItems.AddRange(PrerequisiteItems);
LinkAction.StatusDescription = Path.GetFileName(OutputFile.AbsolutePath);
// ensure compiler timings are captured when we execute the action.
if (!WindowsPlatform.bCompileWithClang && WindowsPlatform.bLogDetailedCompilerTimingInfo)
{
LinkAction.bPrintDebugInfo = true;
}
if (WindowsPlatform.bCompileWithClang)
{
LinkAction.OutputEventHandler = new DataReceivedEventHandler(ClangCompilerOutputFormatter);
}
// Tell the action that we're building an import library here and it should conditionally be
// ignored as a prerequisite for other actions
LinkAction.bProducesImportLibrary = bBuildImportLibraryOnly || LinkEnvironment.Config.bIsBuildingDLL;
// Allow remote linking. Especially in modular builds with many small DLL files, this is almost always very efficient
LinkAction.bCanExecuteRemotely = true;
Log.TraceVerbose(" Linking: " + LinkAction.StatusDescription);
Log.TraceVerbose(" Command: " + LinkAction.CommandArguments);
return OutputFile;
}
private void ExportObjectFilePaths(LinkEnvironment LinkEnvironment, string FileName)
{
// Write the list of object file directories
HashSet<DirectoryReference> ObjectFileDirectories = new HashSet<DirectoryReference>();
foreach(FileItem InputFile in LinkEnvironment.InputFiles)
{
ObjectFileDirectories.Add(InputFile.Reference.Directory);
}
foreach(FileItem InputLibrary in LinkEnvironment.InputLibraries)
{
ObjectFileDirectories.Add(InputLibrary.Reference.Directory);
}
foreach(string AdditionalLibrary in LinkEnvironment.Config.AdditionalLibraries.Where(x => Path.IsPathRooted(x)))
{
ObjectFileDirectories.Add(new FileReference(AdditionalLibrary).Directory);
}
foreach(string LibraryPath in LinkEnvironment.Config.LibraryPaths)
{
ObjectFileDirectories.Add(new DirectoryReference(LibraryPath));
}
foreach(string LibraryPath in (Environment.GetEnvironmentVariable("LIB") ?? "").Split(new char[]{ ';' }, StringSplitOptions.RemoveEmptyEntries))
{
ObjectFileDirectories.Add(new DirectoryReference(LibraryPath));
}
Directory.CreateDirectory(Path.GetDirectoryName(FileName));
File.WriteAllLines(FileName, ObjectFileDirectories.Select(x => x.FullName).OrderBy(x => x).ToArray());
}
public override void CompileCSharpProject(CSharpEnvironment CompileEnvironment, FileReference ProjectFileName, FileReference DestinationFile)
{
// Initialize environment variables required for spawned tools.
VCEnvironment EnvVars = VCEnvironment.SetEnvironment(CompileEnvironment.EnvironmentTargetPlatform, bSupportWindowsXP);
Action BuildProjectAction = new Action(ActionType.BuildProject);
// Specify the source file (prerequisite) for the action
FileItem ProjectFileItem = FileItem.GetExistingItemByFileReference(ProjectFileName);
if (ProjectFileItem == null)
{
throw new BuildException("Expected C# project file {0} to exist.", ProjectFileName);
}
// Add the project and the files contained to the prerequisites.
BuildProjectAction.PrerequisiteItems.Add(ProjectFileItem);
VCSharpProjectFile ProjectFile = new VCSharpProjectFile(ProjectFileName);
List<string> ProjectPreReqs = ProjectFile.GetCSharpDependencies();
DirectoryReference ProjectFolder = ProjectFileName.Directory;
foreach (string ProjectPreReqRelativePath in ProjectPreReqs)
{
FileReference ProjectPreReqAbsolutePath = FileReference.Combine(ProjectFolder, ProjectPreReqRelativePath);
FileItem ProjectPreReqFileItem = FileItem.GetExistingItemByFileReference(ProjectPreReqAbsolutePath);
if (ProjectPreReqFileItem == null)
{
throw new BuildException("Expected C# dependency {0} to exist.", ProjectPreReqAbsolutePath);
}
BuildProjectAction.PrerequisiteItems.Add(ProjectPreReqFileItem);
}
// We might be able to distribute this safely, but it doesn't take any time.
BuildProjectAction.bCanExecuteRemotely = false;
// Setup execution via MSBuild.
BuildProjectAction.WorkingDirectory = UnrealBuildTool.EngineSourceDirectory.FullName;
BuildProjectAction.StatusDescription = ProjectFileName.GetFileName();
BuildProjectAction.CommandPath = EnvVars.MSBuildPath;
if (CompileEnvironment.TargetConfiguration == CSharpTargetConfiguration.Debug)
{
BuildProjectAction.CommandArguments = " /target:rebuild /property:Configuration=Debug";
}
else
{
BuildProjectAction.CommandArguments = " /target:rebuild /property:Configuration=Development";
}
// Be less verbose
BuildProjectAction.CommandArguments += " /nologo /verbosity:minimal";
// Add project
BuildProjectAction.CommandArguments += String.Format(" \"{0}\"", ProjectFileItem.AbsolutePath);
// Specify the output files.
FileReference PDBFilePath = FileReference.Combine(DestinationFile.Directory, DestinationFile.GetFileNameWithoutExtension() + ".pdb");
FileItem PDBFile = FileItem.GetItemByFileReference(PDBFilePath);
BuildProjectAction.ProducedItems.Add(FileItem.GetItemByFileReference(DestinationFile));
BuildProjectAction.ProducedItems.Add(PDBFile);
}
/// <summary>
/// Gets the default include paths for the given platform.
/// </summary>
public static string GetVCIncludePaths(CPPTargetPlatform Platform, bool bSupportWindowsXP)
{
Debug.Assert(Platform == CPPTargetPlatform.Win32 || Platform == CPPTargetPlatform.Win64);
// Make sure we've got the environment variables set up for this target
VCEnvironment.SetEnvironment(Platform, bSupportWindowsXP);
// Also add any include paths from the INCLUDE environment variable. MSVC is not necessarily running with an environment that
// matches what UBT extracted from the vcvars*.bat using SetEnvironmentVariablesFromBatchFile(). We'll use the variables we
// extracted to populate the project file's list of include paths
// @todo projectfiles: Should we only do this for VC++ platforms?
string IncludePaths = Environment.GetEnvironmentVariable("INCLUDE");
if (!String.IsNullOrEmpty(IncludePaths) && !IncludePaths.EndsWith(";"))
{
IncludePaths += ";";
}
return IncludePaths;
}
public override void ModifyBuildProducts(UEBuildBinary Binary, Dictionary<FileReference, BuildProductType> BuildProducts)
{
if (Binary.Config.Type == UEBuildBinaryType.DynamicLinkLibrary)
{
BuildProducts.Add(FileReference.Combine(Binary.Config.IntermediateDirectory, Binary.Config.OutputFilePath.GetFileNameWithoutExtension() + ".lib"), BuildProductType.ImportLibrary);
}
}
/// <summary>
/// Formats compiler output from Clang so that it is clickable in Visual Studio
/// </summary>
protected static void ClangCompilerOutputFormatter(object sender, DataReceivedEventArgs e)
{
string Output = e.Data;
if (string.IsNullOrEmpty(Output))
{
return;
}
// Simplify some assumptions
Output = Output.Replace("\r\n", "\n");
Output = Output.Replace("/", "\\");
// Clang has two main types of line number formats for diagnostic messages:
const string SourceExtensions = "cpp|c|cc|cxx|mm|m|hpp|h|inl";
const string RegexLineAndColumnNumber = @"\.(?:" + SourceExtensions + @")\(\d+(,\d+)\)"; // 'foo.cpp(30,10): warning : [...]'
const string RegexEndOfLineNumber = @"\.(?:" + SourceExtensions + @")\:(\d+)\:"; // 'In file included from foo.cpp:30:'
// We need to replace both of them with MSVC's format ('foo.cpp(30): warning') to make them colourized and clickable.
// Note: MSVC does support clickable column numbers, but adding them makes the colours disappear which is much worse
foreach (var Group in Regex.Matches(Output, RegexLineAndColumnNumber)
.Cast<Match>()
.Where(m => m.Groups.Count == 2)
.Select(m => m.Groups[1])
.OrderByDescending(g => g.Index)) // Work backwards so the match indices remain valid
{
string Left = Output.Substring(0, Group.Index) + "):";
string Remainder = (Group.Index + Group.Length < Output.Length)
? Output.Substring(Group.Index + Group.Length + 2, Output.Length - (Group.Index + Group.Length + 2))
: "";
Output = Left + Remainder;
}
foreach (var Group in Regex.Matches(Output, RegexEndOfLineNumber)
.Cast<Match>()
.Where(m => m.Groups.Count == 2)
.Select(m => m.Groups[1])
.OrderByDescending(g => g.Index))
{
string Left = Output.Substring(0, Group.Index - 1);
string LineNumber = "(" + Group.Value + "):";
string Remainder = (Group.Index + Group.Length < Output.Length)
? Output.Substring(Group.Index + Group.Length + 1, Output.Length - (Group.Index + Group.Length + 1))
: "";
Output = Left + LineNumber + Remainder;
}
// Move the weird misplaced space in clang's 'note' diagnostic to make it consistent with warnings/errors, it bugs my OCD
Output = Output.Replace(": note", " : note ");
LogEventType Verbosity = LogEventType.Console;
const string RegexCompilerDiagnostic = @"([\s\:]error[\s\:]|[\s\:]warning[\s\:]|[\s\:]note[\s\:]).*";
const string RegexFilePath = @"([A-Za-z]:?)*([\\\/][A-Za-z0-9_\-\.\ ]*)+\.(" + SourceExtensions + ")";
const string RegexAbsoluteFilePath = @"^(?:[A-Za-z]:)?\\";
// An output string usually consists of multiple lines, each of which may contain a path
string[] Lines = Output.Split(new[] {'\n'});
for (int LineNum = 0; LineNum < Lines.Length; ++LineNum)
{
string Line = Lines[LineNum];
if (Line.Length > 2 && Line[0] == '\\' && Line[1] != '\\')
{
Line = Line.Substring(1);
}
// Only match notices, warnings and errors. It is sometimes convenient to have everything clickable, but it gets spammy
// with long paths and there are also cases where paths get incorrectly converted (e.g. include paths in errors)
string DiagnosticLevel = Regex.Match(Line, RegexCompilerDiagnostic).Value.TrimStart().ToLowerInvariant();
if (DiagnosticLevel.Length == 0)
{
continue;
}
// Set the verbosity level so that UBT console output is colourized
if (DiagnosticLevel.Contains("error"))
{
Verbosity = (LogEventType)Math.Min((int)Verbosity, (int)LogEventType.Error);
}
else if (DiagnosticLevel.Contains("warning"))
{
Verbosity = (LogEventType)Math.Min((int)Verbosity, (int)LogEventType.Warning);
}
string FilePath = Regex.Match(Line, RegexFilePath).Value;
bool bAbsoluteFilePath = Regex.IsMatch(FilePath, RegexAbsoluteFilePath); // If match, no need to convert
if (FilePath.Length > 0 && !bAbsoluteFilePath)
{
// This assumes all relative source file paths to come from Engine/Source, which is true as far as I know.
// Errors in e.g. Engine/Plugins seem to always be reported with their absolute paths.
string EngineSourcePath = Path.Combine(BuildConfiguration.RelativeEnginePath, "Source");
string AbsoluteFilePath = Path.GetFullPath(Path.Combine(EngineSourcePath, FilePath));
Lines[LineNum] = Line.Replace(FilePath, AbsoluteFilePath);
}
}
Output = string.Join("\n", Lines);
Log.WriteLine(Verbosity, Output);
}
public override void StripSymbols(string SourceFileName, string TargetFileName)
{
ProcessStartInfo StartInfo = new ProcessStartInfo();
string PDBCopyPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "MSBuild", "Microsoft", "VisualStudio", "v14.0", "AppxPackage", "PDBCopy.exe");
if (!File.Exists(PDBCopyPath))
{
// Fall back on VS2013 version
PDBCopyPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "MSBuild", "Microsoft", "VisualStudio", "v12.0", "AppxPackage", "PDBCopy.exe");
}
StartInfo.FileName = PDBCopyPath;
StartInfo.Arguments = String.Format("\"{0}\" \"{1}\" -p", SourceFileName, TargetFileName);
StartInfo.UseShellExecute = false;
StartInfo.CreateNoWindow = true;
Utils.RunLocalProcessAndLogOutput(StartInfo);
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment