// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
namespace UnrealBuildTool
{
///
/// Information about a target, passed along when creating a module descriptor
///
[Serializable]
public class TargetInfo
{
/// Target platform
public readonly UnrealTargetPlatform Platform;
/// Target architecture (or empty string if not needed)
public readonly string Architecture;
/// Target build configuration
public readonly UnrealTargetConfiguration Configuration;
/// Target type (if known)
public readonly TargetRules.TargetType? Type;
/// Whether the target is monolithic (if known)
public readonly bool? bIsMonolithic;
///
/// Constructs a TargetInfo
///
/// Target platform
/// Target build configuration
public TargetInfo( UnrealTargetPlatform InitPlatform, UnrealTargetConfiguration InitConfiguration )
{
Platform = InitPlatform;
Configuration = InitConfiguration;
// get the platform's architecture
var BuildPlatform = UEBuildPlatform.GetBuildPlatform(Platform);
Architecture = BuildPlatform.GetActiveArchitecture();
}
///
/// Constructs a TargetInfo
///
/// Target platform
/// Target build configuration
/// Target type
/// Whether the target is monolithic
public TargetInfo( UnrealTargetPlatform InitPlatform, UnrealTargetConfiguration InitConfiguration, TargetRules.TargetType InitType, bool bInitIsMonolithic )
: this(InitPlatform, InitConfiguration)
{
Type = InitType;
bIsMonolithic = bInitIsMonolithic;
}
///
/// True if the target type is a cooked game.
///
public bool IsCooked
{
get
{
if (!Type.HasValue)
{
throw new BuildException("Trying to access TargetInfo.IsCooked when TargetInfo.Type is not set. Make sure IsCooked is used only in ModuleRules.");
}
return Type == TargetRules.TargetType.Client ||
Type == TargetRules.TargetType.Game ||
Type == TargetRules.TargetType.Server;
}
}
///
/// True if the target type is a monolithic binary
///
public bool IsMonolithic
{
get
{
if (!bIsMonolithic.HasValue)
{
throw new BuildException("Trying to access TargetInfo.IsMonolithic when bIsMonolithic is not set. Make sure IsMonolithic is used only in ModuleRules.");
}
return bIsMonolithic.Value;
}
}
}
///
/// ModuleRules is a data structure that contains the rules for defining a module
///
public abstract class ModuleRules
{
/// Type of module
public enum ModuleType
{
/// C++
CPlusPlus,
/// CLR module (mixed C++ and C++/CLI)
CPlusPlusCLR,
/// External (third-party)
External,
}
/// Code optimization settings
public enum CodeOptimization
{
/// Code should never be optimized if possible.
Never,
/// Code should only be optimized in non-debug builds (not in Debug).
InNonDebugBuilds,
/// Code should only be optimized in shipping builds (not in Debug, DebugGame, Development)
InShippingBuildsOnly,
/// Code should always be optimized if possible.
Always,
/// Default: 'InNonDebugBuilds' for game modules, 'Always' otherwise.
Default,
}
/// Type of module
public ModuleType Type = ModuleType.CPlusPlus;
/// Subfolder of Binaries/PLATFORM folder to put this module in when building DLLs
/// This should only be used by modules that are found via searching like the
/// TargetPlatform or ShaderFormat modules.
/// If FindModules is not used to track them down, the modules will not be found.
public string BinariesSubFolder = "";
/// When this module's code should be optimized.
public CodeOptimization OptimizeCode = CodeOptimization.Default;
/// Header file name for a shared PCH provided by this module. Must be a valid relative path to a public C++ header file.
/// This should only be set for header files that are included by a significant number of other C++ modules.
public string SharedPCHHeaderFile = String.Empty;
public enum PCHUsageMode
{
/// Default: Engine modules use shared PCHs, game modules do not
Default,
/// Never use shared PCHs. Always generate a unique PCH for this module if appropriate
NoSharedPCHs,
/// Shared PCHs are OK!
UseSharedPCHs
}
/// Precompiled header usage for this module
public PCHUsageMode PCHUsage = PCHUsageMode.Default;
/** Use run time type information */
public bool bUseRTTI = false;
/** Enable buffer security checks. This should usually be enabled as it prevents severe security risks. */
public bool bEnableBufferSecurityChecks = true;
/** Enable exception handling */
public bool bEnableExceptions = false;
/** If true and unity builds are enabled, this module will build without unity. */
public bool bFasterWithoutUnity = false;
/** Overrides BuildConfiguration.MinFilesUsingPrecompiledHeader if non-zero. */
public int MinFilesUsingPrecompiledHeaderOverride = 0;
/// List of modules with header files that our module's public headers needs access to, but we don't need to "import" or link against.
public List PublicIncludePathModuleNames = new List();
/// List of public dependency module names. These are modules that are required by our public source files.
public List PublicDependencyModuleNames = new List();
/// List of modules with header files that our module's private code files needs access to, but we don't need to "import" or link against.
public List PrivateIncludePathModuleNames = new List();
/// List of private dependency module names. These are modules that our private code depends on but nothing in our public
/// include files depend on.
public List PrivateDependencyModuleNames = new List();
/// List of module dependencies that should be treated as circular references. This modules must have already been added to
/// either the public or private dependent module list.
public List CircularlyReferencedDependentModules = new List();
/// System include paths. These are public stable header file directories that are not checked when resolving header dependencies.
public List PublicSystemIncludePaths = new List();
/// List of all paths to include files that are exposed to other modules
public List PublicIncludePaths = new List();
/// List of all paths to this module's internal include files, not exposed to other modules
public List PrivateIncludePaths = new List();
/// List of library paths - typically used for External (third party) modules
public List PublicLibraryPaths = new List();
/// List of addition libraries - typically used for External (third party) modules
public List PublicAdditionalLibraries = new List();
// List of frameworks
public List PublicFrameworks = new List();
// List of weak frameworks (for OS version transitions)
public List PublicWeakFrameworks = new List();
/// List of addition frameworks - typically used for External (third party) modules on Mac and iOS
public List PublicAdditionalFrameworks = new List();
/// List of addition resources that should be copied to the app bundle for Mac or iOS
public List AdditionalBundleResources = new List();
/// For builds that execute on a remote machine (e.g. iOS), this list contains additional files that
/// need to be copied over in order for the app to link successfully. Source/header files and PCHs are
/// automatically copied. Usually this is simply a list of precompiled third party library dependencies.
public List PublicAdditionalShadowFiles = new List();
/// List of delay load DLLs - typically used for External (third party) modules
public List PublicDelayLoadDLLs = new List();
/// Additional compiler definitions for this module
public List Definitions = new List();
/** CLR modules only: The assemblies referenced by the module's private implementation. */
public List PrivateAssemblyReferences = new List();
/// Addition modules this module may require at run-time
public List DynamicallyLoadedModuleNames = new List();
/// Extra modules this module may require at run time, that are on behalf of another platform (i.e. shader formats and the like)
public List PlatformSpecificDynamicallyLoadedModuleNames = new List();
/// List of files which this module depends on at runtime. These files will be staged along with the target.
public List RuntimeDependencies = new List();
///
/// Property for the directory containing this module. Useful for adding paths to third party dependencies.
///
public string ModuleDirectory
{
get
{
return Path.GetDirectoryName(RulesCompiler.GetModuleFilename(GetType().Name));
}
}
///
/// Add the given ThirdParty modules as static private dependencies
/// Statically linked to this module, meaning they utilize exports from the other module
/// Private, meaning the include paths for the included modules will not be exposed when giving this modules include paths
/// NOTE: There is no AddThirdPartyPublicStaticDependencies function.
///
/// The names of the modules to add
public void AddThirdPartyPrivateStaticDependencies(TargetInfo Target, params string[] InModuleNames)
{
if (UnrealBuildTool.RunningRocket() == false || Target.Type == TargetRules.TargetType.Game)
{
PrivateDependencyModuleNames.AddRange(InModuleNames);
}
}
///
/// Add the given ThirdParty modules as dynamic private dependencies
/// Dynamically linked to this module, meaning they do not utilize exports from the other module
/// Private, meaning the include paths for the included modules will not be exposed when giving this modules include paths
/// NOTE: There is no AddThirdPartyPublicDynamicDependencies function.
///
/// The names of the modules to add
public void AddThirdPartyPrivateDynamicDependencies(TargetInfo Target, params string[] InModuleNames)
{
if (UnrealBuildTool.RunningRocket() == false || Target.Type == TargetRules.TargetType.Game)
{
PrivateIncludePathModuleNames.AddRange(InModuleNames);
DynamicallyLoadedModuleNames.AddRange(InModuleNames);
}
}
///
/// Setup this module for PhysX/APEX support (based on the settings in UEBuildConfiguration)
///
public void SetupModulePhysXAPEXSupport(TargetInfo Target)
{
if (UEBuildConfiguration.bCompilePhysX == true)
{
AddThirdPartyPrivateStaticDependencies(Target, "PhysX");
Definitions.Add("WITH_PHYSX=1");
if (UEBuildConfiguration.bCompileAPEX == true)
{
AddThirdPartyPrivateStaticDependencies(Target, "APEX");
Definitions.Add("WITH_APEX=1");
}
else
{
Definitions.Add("WITH_APEX=0");
}
}
else
{
Definitions.Add("WITH_PHYSX=0");
Definitions.Add("WITH_APEX=0");
}
if(UEBuildConfiguration.bRuntimePhysicsCooking == true)
{
Definitions.Add("WITH_RUNTIME_PHYSICS_COOKING");
}
}
///
/// Setup this module for Box2D support (based on the settings in UEBuildConfiguration)
///
public void SetupModuleBox2DSupport(TargetInfo Target)
{
//@TODO: This need to be kept in sync with RulesCompiler.cs for now
bool bSupported = false;
if ((Target.Platform == UnrealTargetPlatform.Win64) || (Target.Platform == UnrealTargetPlatform.Win32))
{
bSupported = true;
}
bSupported = bSupported && UEBuildConfiguration.bCompileBox2D;
if (bSupported)
{
AddThirdPartyPrivateStaticDependencies(Target, "Box2D");
}
// Box2D included define (required because pointer types may be in public exported structures)
Definitions.Add(string.Format("WITH_BOX2D={0}", bSupported ? 1 : 0));
}
/** Redistribution override flag for this module. */
public bool? IsRedistributableOverride { get; set; }
/**
* Reads additional dependencies array for project module from project file and fills PrivateDependencyModuleNames.
*
* @param ProjectFile A path to the .uproject file.
* @param ModuleName Name of the module.
*/
public void ReadAdditionalDependencies(string ProjectFile, string ModuleName)
{
// Create a case-insensitive dictionary of the contents
Dictionary Descriptor = fastJSON.JSON.Instance.ToObject>(File.ReadAllText(ProjectFile));
Descriptor = new Dictionary(Descriptor, StringComparer.InvariantCultureIgnoreCase);
// Get the list of plugins
object ModulesObject;
if (Descriptor.TryGetValue("Modules", out ModulesObject))
{
foreach(var ModuleObject in (ModulesObject as object[]).Cast>())
{
object NameObject;
object AdditionalDependenciesObject;
if(!ModuleObject.TryGetValue("Name", out NameObject)
|| !(NameObject as string).Equals(ModuleName)
|| !ModuleObject.TryGetValue("AdditionalDependencies", out AdditionalDependenciesObject))
{
continue;
}
foreach (var AdditionalDependency in (AdditionalDependenciesObject as object[]).Cast())
{
if(!PrivateDependencyModuleNames.Contains(AdditionalDependency))
{
PrivateDependencyModuleNames.Add(AdditionalDependency);
}
}
break;
}
}
}
}
///
/// TargetRules is a data structure that contains the rules for defining a target (application/executable)
///
public abstract class TargetRules
{
/// Type of target
[Serializable]
public enum TargetType
{
/// Cooked monolithic game executable (GameName.exe). Also used for a game-agnostic engine executable (UE4Game.exe or RocketGame.exe)
Game,
/// Uncooked modular editor executable and DLLs (UE4Editor.exe, UE4Editor*.dll, GameName*.dll)
Editor,
/// Cooked monolithic game client executable (GameNameClient.exe, but no server code)
Client,
/// Cooked monolithic game server executable (GameNameServer.exe, but no client code)
Server,
/// Program (standalone program, e.g. ShaderCompileWorker.exe, can be modular or monolithic depending on the program)
Program,
}
///
/// The name of the game, this is set up by the rules compiler after it compiles and constructs this
///
public string TargetName = null;
///
/// Whether the target uses Steam (todo: substitute with more generic functionality)
///
public bool bUsesSteam;
///
/// Whether the target uses CEF3
///
public bool bUsesCEF3;
///
/// Whether the project uses visual Slate UI (as opposed to the low level windowing/messaging which is always used)
///
public bool bUsesSlate = true;
///
/// Hack for legacy game styling isses. No new project should ever set this to true
/// Whether the project uses the Slate editor style in game.
///
public bool bUsesSlateEditorStyle = false;
///
/// Forces linking against the static CRT. This is not supported across the engine due to the need for allocator implementations to be shared (for example), and TPS
/// libraries to be consistent with each other, but can be used for utility programs.
///
public bool bUseStaticCRT = false;
///
/// By default we use the Release C++ Runtime (CRT), even when compiling Debug builds. This is because the Debug C++
/// Runtime isn't very useful when debugging Unreal Engine projects, and linking against the Debug CRT libraries forces
/// our third party library dependencies to also be compiled using the Debug CRT (and often perform more slowly.) Often
/// it can be inconvenient to require a separate copy of the debug versions of third party static libraries simply
/// so that you can debug your program's code.
///
public bool bDebugBuildsActuallyUseDebugCRT = false;
///
/// Whether the output from this target can be publicly distributed, even if it has
/// dependencies on modules that are not (i.e. CarefullyRedist, NotForLicensees, NoRedist).
/// This should be used when you plan to release binaries but not source.
///
public bool bOutputPubliclyDistributable = false;
///
/// Specifies the configuration whose binaries do not require a "-Platform-Configuration" suffix.
///
public UnrealTargetConfiguration UndecoratedConfiguration = UnrealTargetConfiguration.Development;
///
/// A list of additional plugins which need to be included in this target. This allows referencing non-optional plugin modules
/// which cannot be disabled, and allows building against specific modules in program targets which do not fit the categories
/// in ModuleHostType.
///
public List AdditionalPlugins = new List();
///
/// Is the given type a 'game' type (Game/Editor/Server) wrt building?
///
/// The target type of interest
/// true if it *is* a 'game' type
static public bool IsGameType(TargetType InType)
{
return (
(InType == TargetType.Game) ||
(InType == TargetType.Editor) ||
(InType == TargetType.Client) ||
(InType == TargetType.Server)
);
}
///
/// Is the given type a game?
///
/// The target type of interest
/// true if it *is* a game
static public bool IsAGame(TargetType InType)
{
return (
(InType == TargetType.Game) ||
(InType == TargetType.Client)
);
}
///
/// Is the given type an 'editor' type with regard to building?
///
/// The target type of interest
/// true if it *is* an 'editor' type
static public bool IsEditorType(TargetType InType)
{
return (InType == TargetType.Editor);
}
///
/// Type of target
///
public TargetType Type = TargetType.Game;
///
/// The name of this target's 'configuration' within the development IDE. No project may have more than one target with the same configuration name.
/// If no configuration name is set, then it defaults to the TargetType name
///
public string ConfigurationName
{
get
{
if( String.IsNullOrEmpty( ConfigurationNameVar ) )
{
return Type.ToString();
}
else
{
return ConfigurationNameVar;
}
}
set
{
ConfigurationNameVar = value;
}
}
private string ConfigurationNameVar = String.Empty;
///
/// Allows a Program Target to specify it's own solution folder path
///
public string SolutionDirectory = String.Empty;
///
/// If true, the built target goes into the Engine/Binaries/ folder
///
public bool bOutputToEngineBinaries = false;
///
/// Sub folder where the built target goes: Engine/Binaries//
///
public string ExeBinariesSubFolder = String.Empty;
///
/// Whether this target should be compiled in monolithic mode
///
/// The platform being built
/// The configuration being built
/// true if it should, false if not
public virtual bool ShouldCompileMonolithic(UnrealTargetPlatform InPlatform, UnrealTargetConfiguration InConfiguration)
{
// By default, only Editor types compile non-monolithic
if (IsEditorType(Type) == false)
{
// You can build a modular game/program/server via passing '-modular' to UBT
if (UnrealBuildTool.CommandLineContains("-modular") == false)
{
return true;
}
}
if (UnrealBuildTool.CommandLineContains("-monolithic"))
{
return true;
}
return false;
}
///
/// Get the supported platforms for this target
///
/// The list of platforms supported
/// true if successful, false if not
public virtual bool GetSupportedPlatforms(ref List OutPlatforms)
{
if(Type == TargetType.Program)
{
// By default, all programs are desktop only.
return UnrealBuildTool.GetAllDesktopPlatforms(ref OutPlatforms, false);
}
else if(IsEditorType(Type))
{
return UnrealBuildTool.GetAllEditorPlatforms(ref OutPlatforms, false);
}
else if (TargetRules.IsGameType(Type))
{
// By default all games support all platforms
return UnrealBuildTool.GetAllPlatforms(ref OutPlatforms, false);
}
return false;
}
public bool SupportsPlatform(UnrealTargetPlatform InPlatform)
{
List SupportedPlatforms = new List();
if (GetSupportedPlatforms(ref SupportedPlatforms) == true)
{
return SupportedPlatforms.Contains(InPlatform);
}
return false;
}
///
/// Get the supported configurations for this target
///
/// The list of configurations supported
/// true if successful, false if not
public virtual bool GetSupportedConfigurations(ref List OutConfigurations, bool bIncludeTestAndShippingConfigs)
{
if (Type == TargetType.Program)
{
// By default, programs are Debug and Development only.
OutConfigurations.Add(UnrealTargetConfiguration.Debug);
OutConfigurations.Add(UnrealTargetConfiguration.Development);
}
else
{
// By default all games support all configurations
foreach (UnrealTargetConfiguration Config in Enum.GetValues(typeof(UnrealTargetConfiguration)))
{
if (Config != UnrealTargetConfiguration.Unknown)
{
// Some configurations just don't make sense for the editor
if( IsEditorType( Type ) &&
( Config == UnrealTargetConfiguration.Shipping || Config == UnrealTargetConfiguration.Test ) )
{
// We don't currently support a "shipping" editor config
}
else if( !bIncludeTestAndShippingConfigs &&
( Config == UnrealTargetConfiguration.Shipping || Config == UnrealTargetConfiguration.Test ) )
{
// User doesn't want 'Test' or 'Shipping' configs in their project files
}
else
{
OutConfigurations.Add(Config);
}
}
}
}
return (OutConfigurations.Count > 0) ? true : false;
}
///
/// Setup the binaries associated with this target.
///
/// The target information - such as platform and configuration
/// Output list of binaries to generated
/// Output list of extra modules that this target could utilize
public virtual void SetupBinaries(
TargetInfo Target,
ref List OutBuildBinaryConfigurations,
ref List OutExtraModuleNames
)
{
}
///
/// Setup the global environment for building this target
/// IMPORTANT: Game targets will *not* have this function called if they use the shared build environment.
/// See ShouldUseSharedBuildEnvironment().
///
/// The target information - such as platform and configuration
/// Output link environment settings
/// Output compile environment settings
public virtual void SetupGlobalEnvironment(
TargetInfo Target,
ref LinkEnvironmentConfiguration OutLinkEnvironmentConfiguration,
ref CPPEnvironmentConfiguration OutCPPEnvironmentConfiguration
)
{
}
///
/// Allows a target to choose whether to use the shared build environment for a given configuration. Using
/// the shared build environment allows binaries to be reused between targets, but prevents customizing the
/// compile environment through SetupGlobalEnvironment().
///
/// Information about the target
/// True if the target should use the shared build environment
public virtual bool ShouldUseSharedBuildEnvironment(TargetInfo Target)
{
return UnrealBuildTool.RunningRocket() || (Target.Type != TargetType.Program && !Target.IsMonolithic);
}
///
/// Allows the target to specify modules which can be precompiled with the -Precompile/-UsePrecompiled arguments to UBT.
/// All dependencies of the specified modules will be included.
///
/// The target information, such as platform and configuration
/// List which receives module names to precompile
public virtual void GetModulesToPrecompile(TargetInfo Target, List ModuleNames)
{
}
///
/// Return true if this target should always be built with the base editor. Usually programs like shadercompilerworker.
///
/// true if this target should always be built with the base editor.
public virtual bool GUBP_AlwaysBuildWithBaseEditor()
{
return false;
}
///
/// Return true if this target should always be built with the tools. Usually programs like unrealpak.
/// If this is set to true, the program will get its own node
///
/// true if this target should always be built with the base editor.
[Obsolete]
public virtual bool GUBP_AlwaysBuildWithTools(UnrealTargetPlatform InHostPlatform, out bool bInternalToolOnly, out bool SeparateNode)
{
bInternalToolOnly = false;
SeparateNode = false;
return false;
}
///
/// Return true if this target should always be built with the tools. Usually programs like unrealpak.
/// If this is set to true, the program will get its own node
///
/// true if this target should always be built with the base editor.
public virtual bool GUBP_AlwaysBuildWithTools(UnrealTargetPlatform InHostPlatform, out bool bInternalToolOnly, out bool SeparateNode, out bool CrossCompile)
{
bInternalToolOnly = false;
SeparateNode = false;
CrossCompile = false;
return false;
}
///
/// Return a list of platforms to build a tool for
///
/// a list of platforms to build a tool for
public virtual List GUBP_ToolPlatforms(UnrealTargetPlatform InHostPlatform)
{
return new List { InHostPlatform };
}
///
/// Return a list of configs to build a tool for
///
/// a list of configs to build a tool for
public virtual List GUBP_ToolConfigs(UnrealTargetPlatform InHostPlatform)
{
return new List { UnrealTargetConfiguration.Development };
}
///
/// Return true if target should include a NonUnity test
///
/// true if this target should include a NonUnity test
public virtual bool GUBP_IncludeNonUnityToolTest()
{
return false;
}
///
/// Return true if this target should use a platform specific pass
///
/// true if this target should use a platform specific pass
public virtual bool GUBP_NeedsPlatformSpecificDLLs()
{
return false;
}
///
///Returns true if XP monolithics are required for a game
///
/// true if this target needs to be compiled for Windows XP
public virtual bool GUBP_BuildWindowsXPMonolithics()
{
return false;
}
///
/// Return a list of target platforms for the monolithic
///
/// a list of target platforms for the monolithic
public virtual List GUBP_GetPlatforms_MonolithicOnly(UnrealTargetPlatform HostPlatform)
{
var Result = new List{HostPlatform};
// hack to set up the templates without adding anything to their .targets.cs files
if (!String.IsNullOrEmpty(TargetName) && TargetName.StartsWith("TP_"))
{
if (HostPlatform == UnrealTargetPlatform.Win64)
{
Result.Add(UnrealTargetPlatform.IOS);
Result.Add(UnrealTargetPlatform.Android);
}
else if (HostPlatform == UnrealTargetPlatform.Mac)
{
Result.Add(UnrealTargetPlatform.IOS);
}
}
return Result;
}
///
/// Return a list of target platforms for the monolithic without cook
///
/// a list of target platforms for the monolithic without cook
public virtual List GUBP_GetBuildOnlyPlatforms_MonolithicOnly(UnrealTargetPlatform HostPlatform)
{
var Result = new List {};
return Result;
}
///
/// Return a list of configs for target platforms for the monolithic
///
/// a list of configs for a target platforms for the monolithic
public virtual List GUBP_GetConfigs_MonolithicOnly(UnrealTargetPlatform HostPlatform, UnrealTargetPlatform Platform)
{
return new List{UnrealTargetConfiguration.Development};
}
///
/// Return a list of configs which are precompiled for the given target platform
///
/// a list of configs for a target platforms for the monolithic
public virtual List GUBP_GetConfigsForPrecompiledBuilds_MonolithicOnly(UnrealTargetPlatform HostPlatform, UnrealTargetPlatform Platform)
{
return new List();
}
///
/// Return a list of configs for target platforms for formal builds
///
/// a list of configs for a target platforms for the monolithic
[Obsolete]
public virtual List GUBP_GetConfigsForFormalBuilds_MonolithicOnly(UnrealTargetPlatform HostPlatform, UnrealTargetPlatform Platform)
{
return new List();
}
public class GUBPFormalBuild
{
public UnrealTargetPlatform TargetPlatform = UnrealTargetPlatform.Unknown;
public UnrealTargetConfiguration TargetConfig = UnrealTargetConfiguration.Unknown;
public bool bTest = false;
public bool bBeforeTrigger = false;
public GUBPFormalBuild(UnrealTargetPlatform InTargetPlatform, UnrealTargetConfiguration InTargetConfig, bool bInTest = false, bool bInBeforeTrigger = false)
{
TargetPlatform = InTargetPlatform;
TargetConfig = InTargetConfig;
bTest = bInTest;
bBeforeTrigger = bInBeforeTrigger;
}
}
///
/// Return a list of formal builds
///
/// a list of formal builds
public virtual List GUBP_GetConfigsForFormalBuilds_MonolithicOnly(UnrealTargetPlatform HostPlatform)
{
return new List();
}
///
/// Return true if this target should be included in a promotion and indicate shared or not
///
/// if this target should be included in a promotion.
public class GUBPProjectOptions
{
public bool bIsPromotable = false;
public bool bSeparateGamePromotion = false;
public bool bTestWithShared = false;
public bool bIsMassive = false;
public bool bCustomWorkflowForPromotion = false;
public bool bIsNonCode = false;
public bool bPromoteEditorOnly = true;
public string GroupName = null;
}
public virtual GUBPProjectOptions GUBP_IncludeProjectInPromotedBuild_EditorTypeOnly(UnrealTargetPlatform HostPlatform)
{
var Result = new GUBPProjectOptions();
// hack to set up the templates without adding anything to their .targets.cs files
// tweaked to include FP_ folders too - which are temporary
if (!String.IsNullOrEmpty(TargetName) && ( TargetName.StartsWith("TP_") || TargetName.StartsWith("FP_")) )
{
Result.bTestWithShared = true;
Result.GroupName = "Templates";
}
return Result;
}
///
/// Return a list of the non-code projects to test
///
/// a list of the non-code projects to build cook and test
public virtual Dictionary> GUBP_NonCodeProjects_BaseEditorTypeOnly(UnrealTargetPlatform HostPlatform)
{
return new Dictionary>();
}
///
/// Return a list of the non-code projects to make formal builds for
///
/// a list of the non-code projects to build cook and test
[Obsolete]
public virtual Dictionary>> GUBP_NonCodeFormalBuilds_BaseEditorTypeOnly()
{
return new Dictionary>>();
}
///
/// Return a list of the non-code projects to make formal builds for
///
/// a list of the non-code projects to build cook and test
public virtual Dictionary> GUBP_GetNonCodeFormalBuilds_BaseEditorTypeOnly()
{
return new Dictionary>();
}
///
/// Return a list of "test name", "UAT command" pairs for testing the editor
///
public virtual Dictionary GUBP_GetEditorTests_EditorTypeOnly(UnrealTargetPlatform HostPlatform)
{
var MacOption = HostPlatform == UnrealTargetPlatform.Mac ? " -Mac" : "";
var Result = new Dictionary();
Result.Add("EditorTest", "BuildCookRun -run -editortest -unattended -nullrhi -NoP4" + MacOption);
Result.Add("GameTest", "BuildCookRun -run -unattended -nullrhi -NoP4" + MacOption);
Result.Add("EditorAutomationTest", "BuildCookRun -run -editortest -RunAutomationTests -unattended -nullrhi -NoP4" + MacOption);
Result.Add("GameAutomationTest", "BuildCookRun -run -RunAutomationTests -unattended -nullrhi -NoP4" + MacOption);
return Result;
}
///
/// Allow the platform to setup emails for the GUBP for folks that care about node failures relating to this platform
/// Obsolete. Included to avoid breaking existing projects.
///
/// p4 root of the branch we are running
[Obsolete]
public virtual string GUBP_GetGameFailureEMails_EditorTypeOnly(string Branch)
{
return "";
}
///
/// Allow the Game to set up emails for Promotable and Promotion
/// Obsolete. Included to avoid breaking existing projects.
///
[Obsolete]
public virtual string GUBP_GetPromotionEMails_EditorTypeOnly(string Branch)
{
return "";
}
///
/// Return a list of "test name", "UAT command" pairs for testing a monolithic
///
public virtual Dictionary GUBP_GetGameTests_MonolithicOnly(UnrealTargetPlatform HostPlatform, UnrealTargetPlatform AltHostPlatform, UnrealTargetPlatform Platform)
{
var Result = new Dictionary();
if ((Platform == HostPlatform || Platform == AltHostPlatform) && Type == TargetType.Game) // for now, we will only run these for the dev config of the host platform
{
Result.Add("CookedGameTest", "BuildCookRun -run -skipcook -stage -pak -deploy -unattended -nullrhi -NoP4 -platform=" + Platform.ToString());
Result.Add("CookedGameAutomationTest", "BuildCookRun -run -skipcook -stage -pak -deploy -RunAutomationTests -unattended -nullrhi -NoP4 -platform=" + Platform.ToString());
}
return Result;
}
///
/// Return a list of "test name", "UAT command" pairs for testing a monolithic
///
public virtual Dictionary GUBP_GetClientServerTests_MonolithicOnly(UnrealTargetPlatform HostPlatform, UnrealTargetPlatform AltHostPlatform, UnrealTargetPlatform ServerPlatform, UnrealTargetPlatform ClientPlatform)
{
var Result = new Dictionary();
#if false // needs work
if ((ServerPlatform == HostPlatform || ServerPlatform == AltHostPlatform) &&
(ClientPlatform == HostPlatform || ClientPlatform == AltHostPlatform) &&
Type == TargetType.Game) // for now, we will only run these for the dev config of the host platform and only the game executable, not sure how to deal with a client only executable
{
Result.Add("CookedNetTest", "BuildCookRun -run -skipcook -stage -pak -deploy -unattended -server -nullrhi -NoP4 -addcmdline=\"-nosteam\" -platform=" + ClientPlatform.ToString() + " -serverplatform=" + ServerPlatform.ToString());
}
#endif
return Result;
}
///
/// Return additional parameters to cook commandlet
///
public virtual string GUBP_AdditionalCookParameters(UnrealTargetPlatform HostPlatform, string Platform)
{
return "";
}
///
/// Return additional parameters to package commandlet
///
public virtual string GUBP_AdditionalPackageParameters(UnrealTargetPlatform HostPlatform, UnrealTargetPlatform Platform)
{
return "";
}
///
/// Allow Cook Platform Override from a target file
///
public virtual string GUBP_AlternateCookPlatform(UnrealTargetPlatform HostPlatform, string Platform)
{
return "";
}
///
/// Allow target module to override UHT code generation version.
///
public virtual EGeneratedCodeVersion GetGeneratedCodeVersion()
{
return EGeneratedCodeVersion.None;
}
}
public class RulesCompiler
{
///
/// Helper class to avoid adding extra conditions when getting file extensions and suffixes for
/// rule files in FindAllRulesFilesRecursively.
///
public class RulesTypePropertiesAttribute : Attribute
{
public string Suffix;
public string Extension;
public RulesTypePropertiesAttribute(string Suffix, string Extension)
{
this.Suffix = Suffix;
this.Extension = Extension;
}
}
public enum RulesFileType
{
/// *.Build.cs files
[RulesTypeProperties(Suffix: "Build", Extension: ".cs")]
Module,
/// *.Target.cs files
[RulesTypeProperties(Suffix: "Target", Extension: ".cs")]
Target,
/// *.Automation.cs files
[RulesTypeProperties(Suffix: "Automation", Extension: ".cs")]
Automation,
/// *.Automation.csproj files
[RulesTypeProperties(Suffix: "Automation", Extension: ".csproj")]
AutomationModule
}
class RulesFileCache
{
/// List of rules file paths for each of the known types in RulesFileType
public List[] RulesFilePaths = new List[ typeof( RulesFileType ).GetEnumValues().Length ];
}
private static void FindAllRulesFilesRecursively( DirectoryInfo DirInfo, RulesFileCache RulesFileCache )
{
if( DirInfo.Exists )
{
var RulesFileTypeEnum = typeof(RulesFileType);
bool bFoundModuleRulesFile = false;
var RulesFileTypes = typeof( RulesFileType ).GetEnumValues();
foreach( RulesFileType CurRulesType in RulesFileTypes )
{
// Get the suffix and extension associated with this RulesFileType enum value.
var MemberInfo = RulesFileTypeEnum.GetMember(CurRulesType.ToString());
var Attributes = MemberInfo[0].GetCustomAttributes(typeof(RulesTypePropertiesAttribute), false);
var EnumProperties = (RulesTypePropertiesAttribute)Attributes[0];
var SearchRuleSuffix = "." + EnumProperties.Suffix + EnumProperties.Extension; // match files with the right suffix and extension.
var FilesInDirectory = DirInfo.GetFiles("*" + EnumProperties.Extension);
foreach (var RuleFile in FilesInDirectory)
{
// test if filename has the appropriate suffix.
// this handles filenames such as Foo.build.cs, Foo.Build.cs, foo.bUiLd.cs to fix bug 266743 on platforms where case-sensitivity matters
if (RuleFile.Name.EndsWith(SearchRuleSuffix, StringComparison.InvariantCultureIgnoreCase))
{
// Skip Uncooked targets, as those are no longer valid. This is just for easier backwards compatibility with existing projects.
// @todo: Eventually we can eliminate this conditional and just allow it to be an error when these are compiled
if( CurRulesType != RulesFileType.Target || !RuleFile.Name.EndsWith( "Uncooked" + SearchRuleSuffix, StringComparison.InvariantCultureIgnoreCase ) )
{
if (RulesFileCache.RulesFilePaths[(int)CurRulesType] == null)
{
RulesFileCache.RulesFilePaths[(int)CurRulesType] = new List();
}
// Convert file info to the full file path for this file and update our cache
RulesFileCache.RulesFilePaths[(int)CurRulesType].Add(RuleFile.FullName);
// NOTE: Multiple rules files in the same folder are supported. We'll continue iterating along.
if( CurRulesType == RulesFileType.Module )
{
bFoundModuleRulesFile = true;
}
}
else
{
Log.TraceVerbose("Skipped deprecated Target rules file with Uncooked extension: " + RuleFile.Name );
}
}
}
}
// Only recurse if we didn't find a module rules file. In the interest of performance and organizational sensibility
// we don't want to support folders with Build.cs files containing other folders with Build.cs files. Performance-
// wise, this is really important to avoid scanning every folder in the Source/ThirdParty directory, for example.
if( !bFoundModuleRulesFile )
{
// Add all the files recursively
foreach( DirectoryInfo SubDirInfo in DirInfo.GetDirectories() )
{
if( SubDirInfo.Name.Equals( "Intermediate", StringComparison.InvariantCultureIgnoreCase ) )
{
Console.WriteLine( "WARNING: UnrealBuildTool found an Intermediate folder while looking for rules '{0}'. It should only ever be searching under 'Source' folders -- an Intermediate folder is unexpected and will greatly decrease iteration times!", SubDirInfo.FullName );
}
FindAllRulesFilesRecursively(SubDirInfo, RulesFileCache);
}
}
}
}
/// Map of root folders to a cached list of all UBT-related source files in that folder or any of its sub-folders.
/// We cache these file names so we can avoid searching for them later on.
static Dictionary RootFolderToRulesFileCache = new Dictionary();
/// Name of the assembly file to cache rules data within
static string AssemblyName = String.Empty;
/// List of all game folders that we will be able to search for rules files within. This must be primed at startup.
public static List AllGameFolders
{
get;
private set;
}
///
/// Sets which game folders to look at when harvesting for rules source files. This must be called before
/// other functions in the RulesCompiler. The idea here is that we can actually cache rules files for multiple
/// games in a single assembly, if necessary. In practice, multiple game folder's rules should only be cached together
/// when generating project files.
///
/// List of all game folders that rules files will ever be requested for
public static void SetAssemblyNameAndGameFolders( string AssemblyName, List GameFolders )
{
RulesCompiler.AssemblyName = AssemblyName + "ModuleRules";
AllGameFolders = new List();
AllGameFolders.AddRange( GameFolders );
}
public static List FindAllRulesSourceFiles( RulesFileType RulesFileType, List AdditionalSearchPaths )
{
List Folders = new List();
// Add all engine source (including third party source)
Folders.Add( Path.Combine( ProjectFileGenerator.EngineRelativePath, "Source" ) );
// @todo plugin: Disallow modules from including plugin modules as dependency modules? (except when the module is part of that plugin)
// Get all the root folders for plugins
List RootFolders = new List();
RootFolders.Add(ProjectFileGenerator.EngineRelativePath);
RootFolders.AddRange(AllGameFolders);
// Find all the plugin source directories
foreach(string RootFolder in RootFolders)
{
string PluginsFolder = Path.Combine(RootFolder, "Plugins");
foreach(string PluginFile in Plugins.EnumeratePlugins(PluginsFolder))
{
string PluginDirectory = Path.GetDirectoryName(PluginFile);
Folders.Add(Path.Combine(PluginDirectory, "Source"));
}
}
// Add in the game folders to search
if( AllGameFolders != null )
{
foreach( var GameFolder in AllGameFolders )
{
var GameSourceFolder = Path.GetFullPath(Path.Combine( GameFolder, "Source" ));
Folders.Add( GameSourceFolder );
var GameIntermediateSourceFolder = Path.GetFullPath(Path.Combine(GameFolder, "Intermediate", "Source"));
Folders.Add(GameIntermediateSourceFolder);
}
}
// Process the additional search path, if sent in
if( AdditionalSearchPaths != null )
{
foreach( var AdditionalSearchPath in AdditionalSearchPaths )
{
if (!string.IsNullOrEmpty(AdditionalSearchPath))
{
if (Directory.Exists(AdditionalSearchPath))
{
Folders.Add(AdditionalSearchPath);
}
else
{
throw new BuildException( "Couldn't find AdditionalSearchPath for rules source files '{0}'", AdditionalSearchPath );
}
}
}
}
var SourceFiles = new List();
// Iterate over all the folders to check
foreach( string Folder in Folders )
{
// Check to see if we've already cached source files for this folder
RulesFileCache FolderRulesFileCache;
if (!RootFolderToRulesFileCache.TryGetValue(Folder, out FolderRulesFileCache))
{
FolderRulesFileCache = new RulesFileCache();
FindAllRulesFilesRecursively(new DirectoryInfo(Folder), FolderRulesFileCache);
RootFolderToRulesFileCache[Folder] = FolderRulesFileCache;
if (BuildConfiguration.bPrintDebugInfo)
{
foreach (var CurType in Enum.GetValues(typeof(RulesFileType)))
{
var RulesFiles = FolderRulesFileCache.RulesFilePaths[(int)CurType];
if (RulesFiles != null)
{
Log.TraceVerbose("Found {0} rules files for folder {1} of type {2}", RulesFiles.Count, Folder, CurType.ToString());
}
}
}
}
var RulesFilePathsForType = FolderRulesFileCache.RulesFilePaths[(int)RulesFileType];
if (RulesFilePathsForType != null)
{
foreach (string RulesFilePath in RulesFilePathsForType)
{
if (!SourceFiles.Contains(RulesFilePath))
{
SourceFiles.Add(RulesFilePath);
}
}
}
}
return SourceFiles;
}
/// Assembly that contains object types for module rules definitions, loaded (or compiled) on demand */
private static Assembly RulesAssembly = null;
/// Maps module names to their actual xxx.Module.cs file on disk
private static Dictionary ModuleNameToModuleFileMap = new Dictionary( StringComparer.InvariantCultureIgnoreCase );
/// Maps target names to their actual xxx.Target.cs file on disk
private static Dictionary TargetNameToTargetFileMap = new Dictionary( StringComparer.InvariantCultureIgnoreCase );
/// Map of assembly names we've already compiled and loaded to their list of game folders. This is used to prevent
/// trying to recompile the same assembly when ping-ponging between different types of targets
private static Dictionary> LoadedAssemblyMap = new Dictionary>();
private static void ConditionallyCompileAndLoadRulesAssembly()
{
if( String.IsNullOrEmpty( AssemblyName ) )
{
throw new BuildException( "Module or target rules data was requested, but not rules assembly name was set yet!" );
}
RulesAssembly = null;
// Did we already have a RulesAssembly and cached data about rules files and modules? If so, then we'll
// check to see if we need to flush everything and start over. This can happen if UBT wants to built
// different targets in a single invocation, or when generating project files before or after building
// a target
foreach( var ExistingAssembly in LoadedAssemblyMap.Keys )
{
var ExistingAssemblyName = Path.GetFileNameWithoutExtension( ExistingAssembly.Location );
if( ExistingAssemblyName.Equals( AssemblyName, StringComparison.InvariantCultureIgnoreCase ) )
{
// We already have this assembly!
RulesAssembly = ExistingAssembly;
var ExistingGameFolders = LoadedAssemblyMap[ ExistingAssembly ];
// Make sure the game folder list wasn't changed since we last compiled this assembly
if( ExistingGameFolders != AllGameFolders ) // Quick-check pointers first to avoid iterating
{
var AnyGameFoldersDifferent = false;
if( ExistingGameFolders.Count != AllGameFolders.Count )
{
AnyGameFoldersDifferent = true;
}
else
{
foreach( var NewGameFolder in AllGameFolders )
{
if( !ExistingGameFolders.Contains( NewGameFolder ) )
{
AnyGameFoldersDifferent = true;
break;
}
}
foreach( var OldGameFolder in ExistingGameFolders )
{
if( !AllGameFolders.Contains( OldGameFolder ) )
{
AnyGameFoldersDifferent = true;
break;
}
}
}
if( AnyGameFoldersDifferent )
{
throw new BuildException( "SetAssemblyNameAndGameFolders() was called with an assembly name that had already been compiled, but with DIFFERENT game folders. This is not allowed." );
}
}
break;
}
}
// Does anything need to be compiled?
if( RulesAssembly == null )
{
var AdditionalSearchPaths = new List();
if (UnrealBuildTool.HasUProjectFile())
{
// Add the game project's source folder
var ProjectSourceDirectory = Path.Combine( UnrealBuildTool.GetUProjectPath(), "Source" );
if( Directory.Exists( ProjectSourceDirectory ) )
{
AdditionalSearchPaths.Add( ProjectSourceDirectory );
}
// Add the games project's intermediate source folder
var ProjectIntermediateSourceDirectory = Path.Combine(UnrealBuildTool.GetUProjectPath(), "Intermediate", "Source");
if (Directory.Exists(ProjectIntermediateSourceDirectory))
{
AdditionalSearchPaths.Add(ProjectIntermediateSourceDirectory);
}
}
var ModuleFileNames = FindAllRulesSourceFiles(RulesFileType.Module, AdditionalSearchPaths);
var AssemblySourceFiles = new List();
AssemblySourceFiles.AddRange( ModuleFileNames );
if( AssemblySourceFiles.Count == 0 )
{
throw new BuildException("No module rules source files were found in any of the module base directories!");
}
var TargetFileNames = FindAllRulesSourceFiles(RulesFileType.Target, AdditionalSearchPaths);
AssemblySourceFiles.AddRange( TargetFileNames );
// Create a path to the assembly that we'll either load or compile
string BaseIntermediatePath = UnrealBuildTool.HasUProjectFile() ?
Path.Combine(UnrealBuildTool.GetUProjectPath(), BuildConfiguration.BaseIntermediateFolder) : BuildConfiguration.BaseIntermediatePath;
string OutputAssemblyPath = Path.GetFullPath(Path.Combine(BaseIntermediatePath, "BuildRules", AssemblyName + ".dll"));
RulesAssembly = DynamicCompilation.CompileAndLoadAssembly( OutputAssemblyPath, AssemblySourceFiles );
{
// Setup the module map
foreach( var CurModuleFileName in ModuleFileNames )
{
var CleanFileName = Utils.CleanDirectorySeparators( CurModuleFileName );
var ModuleName = Path.GetFileNameWithoutExtension( Path.GetFileNameWithoutExtension( CleanFileName ) ); // Strip both extensions
if( !ModuleNameToModuleFileMap.ContainsKey( ModuleName ) )
{
ModuleNameToModuleFileMap.Add( ModuleName, CurModuleFileName );
}
}
// Setup the target map
foreach( var CurTargetFileName in TargetFileNames )
{
var CleanFileName = Utils.CleanDirectorySeparators( CurTargetFileName );
var TargetName = Path.GetFileNameWithoutExtension( Path.GetFileNameWithoutExtension( CleanFileName ) ); // Strip both extensions
if( !TargetNameToTargetFileMap.ContainsKey( TargetName ) )
{
TargetNameToTargetFileMap.Add( TargetName, CurTargetFileName );
}
}
}
// Remember that we loaded this assembly
LoadedAssemblyMap[ RulesAssembly ] = AllGameFolders;
}
}
///
///
///
///
///
public static string GetModuleFilename(string InModuleName)
{
// Make sure the module file is known to us
if (!ModuleNameToModuleFileMap.ContainsKey(InModuleName))
{
return "";
}
// Return the module file name to the caller
return ModuleNameToModuleFileMap[InModuleName];
}
public static string GetTargetFilename(string InTargetName)
{
// Make sure the target file is known to us
if (!TargetNameToTargetFileMap.ContainsKey(InTargetName))
{
return "";
}
// Return the target file name to the caller
return TargetNameToTargetFileMap[InTargetName];
}
///
///
///
///
///
public static bool IsRocketProjectModule(string InModuleName)
{
if (UnrealBuildTool.HasUProjectFile() == false)
{
return false;
}
string Filename = GetModuleFilename(InModuleName);
if (string.IsNullOrEmpty(Filename))
{
return false;
}
return (Utils.IsFileUnderDirectory( Filename, UnrealBuildTool.GetUProjectPath() ));
}
///
/// Creates an instance of a module rules descriptor object for the specified module name
///
/// Name of the module
/// Information about the target associated with this module
/// Output
/// Compiled module rule info
public static bool TryCreateModuleRules( string ModuleName, TargetInfo Target, out ModuleRules Rules )
{
if(GetModuleFilename( ModuleName ) == null)
{
Rules = null;
return false;
}
else
{
Rules = CreateModuleRules( ModuleName, Target );
return true;
}
}
///
/// Creates an instance of a module rules descriptor object for the specified module name
///
/// Name of the module
/// Information about the target associated with this module
/// Compiled module rule info
public static ModuleRules CreateModuleRules( string ModuleName, TargetInfo Target )
{
string ModuleFileName;
return CreateModuleRules(ModuleName, Target, out ModuleFileName);
}
///
/// Creates an instance of a module rules descriptor object for the specified module name
///
/// Name of the module
/// Information about the target associated with this module
/// The original source file name for the Module.cs file for this module
/// Compiled module rule info
public static ModuleRules CreateModuleRules( string ModuleName, TargetInfo Target, out string ModuleFileName )
{
ConditionallyCompileAndLoadRulesAssembly();
var AssemblyFileName = Path.GetFileNameWithoutExtension( RulesAssembly.Location );
// Currently, we expect the user's rules object type name to be the same as the module name
var ModuleTypeName = ModuleName;
// Make sure the module file is known to us
if( !ModuleNameToModuleFileMap.ContainsKey( ModuleName ) )
{
throw new MissingModuleException( ModuleName );
}
// Return the module file name to the caller
ModuleFileName = ModuleNameToModuleFileMap[ ModuleName ];
UnrealTargetPlatform LocalPlatform = Target.Platform;
UnrealTargetConfiguration LocalConfiguration = Target.Configuration;
TargetInfo LocalTarget = new TargetInfo(LocalPlatform, LocalConfiguration, Target.Type.Value, Target.bIsMonolithic.Value);
// The build module must define a type named 'Rules' that derives from our 'ModuleRules' type.
var RulesObjectType = RulesAssembly.GetType( ModuleName );
if (RulesObjectType == null)
{
// Temporary hack to avoid System namespace collisions
// @todo projectfiles: Make rules assemblies require namespaces.
RulesObjectType = RulesAssembly.GetType("UnrealBuildTool.Rules." + ModuleName);
}
if( RulesObjectType == null )
{
throw new BuildException( "Expecting to find a type to be declared in a module rules named '{0}' in {1}. This type must derive from the 'ModuleRules' type defined by Unreal Build Tool.", ModuleTypeName, RulesAssembly.FullName );
}
// Create an instance of the module's rules object
ModuleRules RulesObject;
try
{
RulesObject = (ModuleRules)Activator.CreateInstance(RulesObjectType, LocalTarget);
}
catch( Exception Ex )
{
throw new BuildException( Ex, "Unable to instantiate instance of '{0}' object type from compiled assembly '{1}'. Unreal Build Tool creates an instance of your module's 'Rules' object in order to find out about your module's requirements. The CLR exception details may provide more information: {2}", ModuleTypeName, AssemblyFileName, Ex.ToString() );
}
// Have to do absolute here as this could be a project that is under the root
var FullUProjectPath = string.IsNullOrWhiteSpace(UnrealBuildTool.GetUProjectPath())
? ""
: Path.GetFullPath(UnrealBuildTool.GetUProjectPath());
var bProjectModule = string.IsNullOrWhiteSpace(FullUProjectPath)
? false
: Utils.IsFileUnderDirectory(ModuleFileName, FullUProjectPath);
if (bProjectModule)
{
RulesObject.ReadAdditionalDependencies(UnrealBuildTool.GetUProjectFile(), ModuleName);
}
// Validate rules object
{
if( RulesObject.Type == ModuleRules.ModuleType.CPlusPlus )
{
if (RulesObject.PrivateAssemblyReferences.Count > 0)
{
throw new BuildException("Module rules for '{0}' may not specify PrivateAssemblyReferences unless it is a CPlusPlusCLR module type.", AssemblyFileName);
}
// Choose code optimization options based on module type (game/engine) if
// default optimization method is selected.
bool bIsEngineModule = Utils.IsFileUnderDirectory( ModuleFileName, ProjectFileGenerator.EngineRelativePath );
if (RulesObject.OptimizeCode == ModuleRules.CodeOptimization.Default)
{
// Engine/Source and Engine/Plugins are considered 'Engine' code...
if (bIsEngineModule)
{
// Engine module - always optimize (except Debug).
RulesObject.OptimizeCode = ModuleRules.CodeOptimization.Always;
}
else
{
// Game module - do not optimize in Debug and DebugGame builds.
RulesObject.OptimizeCode = ModuleRules.CodeOptimization.InNonDebugBuilds;
}
}
// Disable shared PCHs for game modules by default
if (RulesObject.PCHUsage == ModuleRules.PCHUsageMode.Default)
{
// Note that bIsEngineModule includes Engine/Plugins, so Engine/Plugins will use shared PCHs.
var IsProgramTarget = Target.Type != null && Target.Type == TargetRules.TargetType.Program;
if (bIsEngineModule || IsProgramTarget)
{
// Engine module or plugin module -- allow shared PCHs
RulesObject.PCHUsage = ModuleRules.PCHUsageMode.UseSharedPCHs;
}
else
{
// Game module. Do not enable shared PCHs by default, because games usually have a large precompiled header of their own and compile times would suffer.
RulesObject.PCHUsage = ModuleRules.PCHUsageMode.NoSharedPCHs;
}
}
}
}
return RulesObject;
}
///
/// Determines whether the given module name is a game module (as opposed to an engine module)
///
public static bool IsGameModule(string InModuleName)
{
string ModuleFileName = GetModuleFilename(InModuleName);
return ModuleFileName.Length > 0 && !Utils.IsFileUnderDirectory(ModuleFileName, BuildConfiguration.RelativeEnginePath);
}
///
/// Add the standard default include paths to the given modulerules object
///
/// The name of the module
/// The filename to the module rules file (Build.cs)
/// The module file relative to the engine directory
/// true if it is a game module, false if not
/// The module rules object itself
public static void AddDefaultIncludePathsToModuleRules(string InModuleName, string InModuleFilename, string InModuleFileRelativeToEngineDirectory, bool IsGameModule, ModuleRules RulesObject)
{
// Grab the absolute path of the Engine/Source folder for use later
string AbsEngineSourceDirectory = Path.Combine(ProjectFileGenerator.RootRelativePath, "Engine/Source");
AbsEngineSourceDirectory = Path.GetFullPath(AbsEngineSourceDirectory);
AbsEngineSourceDirectory = AbsEngineSourceDirectory.Replace("\\", "/");
// Find the module path relative to the Engine/Source folder
string ModuleDirectoryRelativeToEngineSourceDirectory = Utils.MakePathRelativeTo(InModuleFilename, Path.Combine(ProjectFileGenerator.RootRelativePath, "Engine/Source"));
ModuleDirectoryRelativeToEngineSourceDirectory = ModuleDirectoryRelativeToEngineSourceDirectory.Replace("\\", "/");
// Remove the build.cs file from the directory if present
if (ModuleDirectoryRelativeToEngineSourceDirectory.EndsWith("Build.cs", StringComparison.InvariantCultureIgnoreCase))
{
Int32 LastSlashIdx = ModuleDirectoryRelativeToEngineSourceDirectory.LastIndexOf("/");
if (LastSlashIdx != -1)
{
ModuleDirectoryRelativeToEngineSourceDirectory = ModuleDirectoryRelativeToEngineSourceDirectory.Substring(0, LastSlashIdx + 1);
}
}
else
{
throw new BuildException("Invalid module filename '{0}'.", InModuleFilename);
}
// Determine the 'Game/Source' folder
//@todo.Rocket: This currently requires following our standard format for folder layout.
// Also, it assumes the module itself is not named Source...
string GameSourceIncludePath = (IsGameModule == true) ? ModuleDirectoryRelativeToEngineSourceDirectory : "";
if (string.IsNullOrEmpty(GameSourceIncludePath) == false)
{
Int32 SourceIdx = GameSourceIncludePath.IndexOf("/Source/");
if (SourceIdx != -1)
{
GameSourceIncludePath = GameSourceIncludePath.Substring(0, SourceIdx + 8);
RulesObject.PublicIncludePaths.Add(GameSourceIncludePath);
}
}
// Setup the directories for Classes, Public, and Intermediate
string ClassesDirectory = Path.Combine(ModuleDirectoryRelativeToEngineSourceDirectory, "Classes/"); // @todo uht: Deprecate eventually. Or force it to be manually specified...
string PublicDirectory = Path.Combine(ModuleDirectoryRelativeToEngineSourceDirectory, "Public/");
if(!Utils.IsFileUnderDirectory(InModuleFilename, AbsEngineSourceDirectory))
{
// This will be either the format
// ..//Source/
// or
// c:/PATH//Source/
string SourceDirName = "Source";
Int32 SourceSlashIdx = ModuleDirectoryRelativeToEngineSourceDirectory.IndexOf(SourceDirName);
string SourceDirectoryPath = null;
if(SourceSlashIdx != -1)
{
try
{
SourceDirectoryPath = Path.GetFullPath(ModuleDirectoryRelativeToEngineSourceDirectory.Substring(0, SourceSlashIdx + SourceDirName.Length));
}
catch(Exception Exc)
{
throw new BuildException(Exc, "Failed to resolve module source directory for private include paths for module {0}.", InModuleName);
}
}
else
{
//@todo. throw a build exception here?
}
// Resolve private include paths against the module source root so they are simpler and don't have to be engine root relative.
if(SourceDirectoryPath != null)
{
List ResolvedPrivatePaths = new List();
foreach(var PrivatePath in RulesObject.PrivateIncludePaths)
{
try
{
if(!Path.IsPathRooted(PrivatePath))
{
ResolvedPrivatePaths.Add(Path.Combine(SourceDirectoryPath, PrivatePath));
}
else
{
ResolvedPrivatePaths.Add(PrivatePath);
}
}
catch(Exception Exc)
{
throw new BuildException(Exc, "Failed to resolve private include path {0}.", PrivatePath);
}
}
RulesObject.PrivateIncludePaths = ResolvedPrivatePaths;
}
}
string IncludePath_Classes = "";
string IncludePath_Public = "";
bool bModulePathIsRooted = Path.IsPathRooted(ModuleDirectoryRelativeToEngineSourceDirectory);
string ClassesFolderName = (bModulePathIsRooted == false) ? Path.Combine(AbsEngineSourceDirectory, ClassesDirectory) : ClassesDirectory;
if (Directory.Exists(ClassesFolderName) == true)
{
IncludePath_Classes = ClassesDirectory;
}
string PublicFolderName = (bModulePathIsRooted == false) ? Path.Combine(AbsEngineSourceDirectory, PublicDirectory) : PublicDirectory;
if (Directory.Exists(PublicFolderName) == true)
{
IncludePath_Public = PublicDirectory;
}
// Add them if they are required...
if (IncludePath_Classes.Length > 0)
{
RulesObject.PublicIncludePaths.Add(IncludePath_Classes);
}
if (IncludePath_Public.Length > 0)
{
RulesObject.PublicIncludePaths.Add(IncludePath_Public);
// Add subdirectories of Public if present
DirectoryInfo PublicInfo = new DirectoryInfo(IncludePath_Public);
DirectoryInfo[] PublicSubDirs = PublicInfo.GetDirectories("*", SearchOption.AllDirectories);
if (PublicSubDirs.Length > 0)
{
foreach (DirectoryInfo SubDir in PublicSubDirs)
{
string PartialDir = SubDir.FullName.Replace(PublicInfo.FullName, "");
string NewDir = IncludePath_Public + PartialDir;
NewDir = Utils.CleanDirectorySeparators(NewDir, '/');
RulesObject.PublicIncludePaths.Add(NewDir);
}
}
}
}
protected static bool GetTargetTypeAndRulesInstance(string InTargetName, TargetInfo InTarget, out System.Type OutRulesObjectType, out TargetRules OutRulesObject)
{
// The build module must define a type named 'Target' that derives from our 'TargetRules' type.
OutRulesObjectType = RulesAssembly.GetType(InTargetName);
if (OutRulesObjectType == null)
{
throw new BuildException(
"Expecting to find a type to be declared in a target rules named '{0}'. This type must derive from the 'TargetRules' type defined by Unreal Build Tool.",
InTargetName);
}
// Create an instance of the module's rules object
try
{
OutRulesObject = (TargetRules)Activator.CreateInstance(OutRulesObjectType, InTarget);
}
catch (Exception Ex)
{
var AssemblyFileName = Path.GetFileNameWithoutExtension(RulesAssembly.Location);
throw new BuildException(Ex,
"Unable to instantiate instance of '{0}' object type from compiled assembly '{1}'. Unreal Build Tool creates an instance of your module's 'Rules' object in order to find out about your module's requirements. The CLR exception details may provide more information: {2}",
InTargetName, AssemblyFileName, Ex.ToString());
}
OutRulesObject.TargetName = InTargetName;
return true;
}
///
/// Creates a target rules object for the specified target name.
///
/// Name of the target
/// Information about the target associated with this target
/// The original source file name of the Target.cs file for this target
/// The build target rules for the specified target
public static TargetRules CreateTargetRules(string TargetName, TargetInfo Target, bool bInEditorRecompile, out string TargetFileName)
{
ConditionallyCompileAndLoadRulesAssembly();
// Make sure the target file is known to us
bool bFoundTargetName = TargetNameToTargetFileMap.ContainsKey(TargetName);
if (bFoundTargetName == false)
{
if (UnrealBuildTool.RunningRocket())
{
//@todo Rocket: Remove this when full game support is implemented
// If we are Rocket, they will currently only have an editor target.
// See if that exists
bFoundTargetName = TargetNameToTargetFileMap.ContainsKey(TargetName + "Editor");
if (bFoundTargetName)
{
TargetName += "Editor";
}
}
}
if (bFoundTargetName == false)
{
// throw new BuildException("Couldn't find target rules file for target '{0}' in rules assembly '{1}'.", TargetName, RulesAssembly.FullName);
string ExceptionMessage = "Couldn't find target rules file for target '";
ExceptionMessage += TargetName;
ExceptionMessage += "' in rules assembly '";
ExceptionMessage += RulesAssembly.FullName;
ExceptionMessage += "'." + Environment.NewLine;
ExceptionMessage += "Location: " + RulesAssembly.Location + Environment.NewLine;
ExceptionMessage += "Target rules found:" + Environment.NewLine;
foreach (KeyValuePair entry in TargetNameToTargetFileMap)
{
ExceptionMessage += "\t" + entry.Key + " - " + entry.Value + Environment.NewLine;
}
throw new BuildException(ExceptionMessage);
}
// Return the target file name to the caller
TargetFileName = TargetNameToTargetFileMap[ TargetName ];
// Currently, we expect the user's rules object type name to be the same as the module name + 'Target'
string TargetTypeName = TargetName + "Target";
// The build module must define a type named 'Target' that derives from our 'TargetRules' type.
System.Type RulesObjectType;
TargetRules RulesObject;
GetTargetTypeAndRulesInstance(TargetTypeName, Target, out RulesObjectType, out RulesObject);
if (bInEditorRecompile)
{
// Make sure this is an editor module.
if (RulesObject != null)
{
if (RulesObject.Type != TargetRules.TargetType.Editor)
{
// Not the editor... determine the editor project
string TargetSourceFolder = TargetFileName;
Int32 SourceFolderIndex = -1;
if (Utils.IsRunningOnMono)
{
TargetSourceFolder = TargetSourceFolder.Replace("\\", "/");
SourceFolderIndex = TargetSourceFolder.LastIndexOf("/Source/", StringComparison.InvariantCultureIgnoreCase);
}
else
{
TargetSourceFolder = TargetSourceFolder.Replace("/", "\\");
SourceFolderIndex = TargetSourceFolder.LastIndexOf("\\Source\\", StringComparison.InvariantCultureIgnoreCase);
}
if (SourceFolderIndex != -1)
{
TargetSourceFolder = TargetSourceFolder.Substring(0, SourceFolderIndex + 8);
foreach (KeyValuePair CheckEntry in TargetNameToTargetFileMap)
{
if (CheckEntry.Value.StartsWith(TargetSourceFolder, StringComparison.InvariantCultureIgnoreCase))
{
if (CheckEntry.Key.Equals(TargetName, StringComparison.InvariantCultureIgnoreCase) == false)
{
// We have found a target in the same source folder that is not the original target found.
// See if it is the editor project
string CheckTargetTypeName = CheckEntry.Key + "Target";
System.Type CheckRulesObjectType;
TargetRules CheckRulesObject;
GetTargetTypeAndRulesInstance(CheckTargetTypeName, Target, out CheckRulesObjectType, out CheckRulesObject);
if (CheckRulesObject != null)
{
if (CheckRulesObject.Type == TargetRules.TargetType.Editor)
{
// Found it
// NOTE: This prevents multiple Editor targets from co-existing...
RulesObject = CheckRulesObject;
break;
}
}
}
}
}
}
}
}
}
return RulesObject;
}
///
/// Creates a target object for the specified target name.
///
/// Root folder for the target's game, if this is a game target
/// Name of the target
/// Information about the target associated with this target
/// The build target object for the specified build rules source file
public static UEBuildTarget CreateTarget(TargetDescriptor Desc)
{
var CreateTargetStartTime = DateTime.UtcNow;
string TargetFileName;
TargetRules RulesObject = CreateTargetRules(Desc.TargetName, new TargetInfo(Desc.Platform, Desc.Configuration), Desc.bIsEditorRecompile, out TargetFileName);
if (Desc.bIsEditorRecompile)
{
// Now that we found the actual Editor target, make sure we're no longer using the old TargetName (which is the Game target)
var TargetSuffixIndex = RulesObject.TargetName.LastIndexOf("Target");
Desc.TargetName = (TargetSuffixIndex > 0) ? RulesObject.TargetName.Substring(0, TargetSuffixIndex) : RulesObject.TargetName;
}
if ((ProjectFileGenerator.bGenerateProjectFiles == false) && (RulesObject.SupportsPlatform(Desc.Platform) == false))
{
if (UEBuildConfiguration.bCleanProject)
{
return null;
}
throw new BuildException("{0} does not support the {1} platform.", Desc.TargetName, Desc.Platform.ToString());
}
// Generate a build target from this rules module
UEBuildTarget BuildTarget = null;
switch (RulesObject.Type)
{
case TargetRules.TargetType.Game:
BuildTarget = new UEBuildGame(Desc, RulesObject);
break;
case TargetRules.TargetType.Editor:
BuildTarget = new UEBuildEditor(Desc, RulesObject);
break;
case TargetRules.TargetType.Client:
BuildTarget = new UEBuildClient(Desc, RulesObject);
break;
case TargetRules.TargetType.Server:
BuildTarget = new UEBuildServer(Desc, RulesObject);
break;
case TargetRules.TargetType.Program:
BuildTarget = new UEBuildTarget(Desc, RulesObject, null);
break;
}
if( BuildConfiguration.bPrintPerformanceInfo )
{
var CreateTargetTime = (DateTime.UtcNow - CreateTargetStartTime).TotalSeconds;
Log.TraceInformation( "CreateTarget for " + Desc.TargetName + " took " + CreateTargetTime + "s" );
}
if (BuildTarget == null)
{
throw new BuildException("Failed to create build target for '{0}'.", Desc.TargetName);
}
return BuildTarget;
}
}
}