// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; using System.Text; using EpicGames.Core; using Microsoft.Extensions.Logging; using OpenTracing.Util; using static UnrealBuildTool.DataDrivenPlatformInfo; namespace UnrealBuildTool { /// /// Enum for the different ways a platform can support multiple architectures within UnrealBuildTool /// public enum UnrealArchitectureMode { /// /// This platform only supports a single architecture (consoles, etc) /// SingleArchitecture, /// /// This platform needs separate targets per architecture (compiling for multiple will compile two entirely separate set of source/exectuable/intermediates) /// OneTargetPerArchitecture, /// /// This will create a single target, but compile each source file separately /// SingleTargetCompileSeparately, /// /// This will create a single target, but compile each source file and link the executable separately - but then packaged together into one final output /// SingleTargetLinkSeparately, } /// /// Full architecture configuation information for a platform. Can be found by platform with UnrealArchitectureConfig.ForPlatform(), or UEBuildPlatform.ArchitectureConfig /// public class UnrealArchitectureConfig { /// /// Get the architecture configuration object for a given platform /// /// /// public static UnrealArchitectureConfig ForPlatform(UnrealTargetPlatform Platform) { return UEBuildPlatform.GetBuildPlatform(Platform).ArchitectureConfig; } /// /// Get all known ArchitectureConfig objects /// /// public static IEnumerable AllConfigs() { // return ArchConfigs for platforms that have a BuildPlatform (which is where the Configs are stored) return UnrealTargetPlatform.GetValidPlatforms().Where(x => UEBuildPlatform.TryGetBuildPlatform(x, out _)).Select(x => UEBuildPlatform.GetBuildPlatform(x).ArchitectureConfig); } /// /// The multi-architecture mode for this platform (potentially single-architecture) /// public UnrealArchitectureMode Mode { get; } /// /// The set of all architecture this platform supports. Any platform specified on the UBT commandline not in this list will be an error /// public UnrealArchitectures AllSupportedArchitectures { get; } /// /// This determines what architecture(s) to compile/package when no architeecture is specified on the commandline /// /// /// /// public virtual UnrealArchitectures ActiveArchitectures(FileReference? ProjectFile, string? TargetName) { if (Mode != UnrealArchitectureMode.SingleArchitecture || AllSupportedArchitectures.bIsMultiArch) { throw new BuildException("Platforms that support multiple platforms are expected to override ActiveArchitectures in a platform-specifiec UnraelArchitectureConfig subclass"); } return AllSupportedArchitectures; } /// /// Like ProjectSupportedArchitectures, except when building in distribution mode. Defaults to ActiveArchitectures /// /// /// /// public virtual UnrealArchitectures DistributionArchitectures(FileReference? ProjectFile, string? TargetName) { return ActiveArchitectures(ProjectFile, TargetName); } /// /// Returns the set all architectures potentially supported by this project. Can be used by project file gnenerators to restrict IDE architecture options /// Defaults to AllSupportedArchitectures /// /// /// /// public virtual UnrealArchitectures ProjectSupportedArchitectures(FileReference? ProjectFile, string? TargetName) { return AllSupportedArchitectures; } /// /// Returns if architecture name should be used when making intermediate directories, per-architecture filenames, etc /// It is virtual, so a platform can choose to skip architecture name for one platform, but not another /// /// public virtual bool RequiresArchitectureFilenames(UnrealArchitectures Architectures) { // @todo: this needs to also have directory vs file (we may need directory names but not filenames in the case of Single-target multi-arch) return Mode == UnrealArchitectureMode.OneTargetPerArchitecture; } /// /// Convert user specified architecture strings to what the platform wants /// /// /// public virtual string ConvertToReadableArchitecture(UnrealArch Architecture) { return Architecture.ToString(); } /// /// Get name for architecture-specific directories (can be shorter than architecture name itself) /// public virtual string GetFolderNameForArchitecture(UnrealArch Architecture) { // by default, use the architecture name return Architecture.ToString(); } /// /// Get name for architecture-specific directories (can be shorter than architecture name itself) /// public virtual string GetFolderNameForArchitectures(UnrealArchitectures Architectures) { // by default, use the architecture names combined with + return Architectures.GetFolderNameForPlatform(this); } /// /// Returns the architecture of the currently running OS (only used for desktop platforms, so will throw an exception in the general case) /// /// public virtual UnrealArch GetHostArchitecture() { if (AllSupportedArchitectures.bIsMultiArch) { throw new BuildException($"Asking for Host architecture from {GetType()} which supports multiple architectures, but did not override GetHostArchitecture()"); } return AllSupportedArchitectures.SingleArchitecture; } /// /// Simple constructor for platforms with a single architecture /// /// public UnrealArchitectureConfig(UnrealArch SingleArchitecture) { Mode = UnrealArchitectureMode.SingleArchitecture; AllSupportedArchitectures = new UnrealArchitectures(SingleArchitecture); } /// /// Full constructor for platforms that support multiple architectures /// /// /// protected UnrealArchitectureConfig(UnrealArchitectureMode Mode, IEnumerable SupportedArchitectures) { this.Mode = Mode; AllSupportedArchitectures = new UnrealArchitectures(SupportedArchitectures); } } abstract class UEBuildPlatform { private static Dictionary BuildPlatformDictionary = new Dictionary(); // a mapping of a group to the platforms in the group (ie, Microsoft contains Win32 and Win64) static Dictionary> PlatformGroupDictionary = new Dictionary>(); /// /// The corresponding target platform enum /// public readonly UnrealTargetPlatform Platform; /// /// The configuration about the architecture(s) this platform supports /// public readonly UnrealArchitectureConfig ArchitectureConfig; /// /// Logger for this platform /// protected readonly ILogger Logger; /// /// All the platform folder names /// private static string[]? CachedPlatformFolderNames; /// /// Cached copy of the list of folders to include for this platform /// private IReadOnlySet? CachedIncludedFolderNames; /// /// Cached copy of the list of folders to exclude for this platform /// private IReadOnlySet? CachedExcludedFolderNames; /// /// Constructor. /// /// The enum value for this platform /// The SDK management object for this platform /// THe architecture configuraton for this platform. This is returned by UnrealArchitectureConfig.ForPlatform() /// Logger for output public UEBuildPlatform(UnrealTargetPlatform InPlatform, UEBuildPlatformSDK SDK, UnrealArchitectureConfig ArchitectureConfig, ILogger InLogger) { Platform = InPlatform; Logger = InLogger; this.ArchitectureConfig = ArchitectureConfig; // check DDPI to see if the platform is enabled on this host platform string IniPlatformName = ConfigHierarchy.GetIniPlatformName(Platform); bool bIsEnabled = false; ConfigDataDrivenPlatformInfo? DDPI = DataDrivenPlatformInfo.GetDataDrivenInfoForPlatform(IniPlatformName); if (DDPI != null) { bIsEnabled = DDPI.bIsEnabled; } // set up the SDK if the platform is enabled UEBuildPlatformSDK.RegisterSDKForPlatform(SDK, Platform.ToString(), bIsEnabled); if (bIsEnabled) { SDK.ManageAndValidateSDK(); } } private static string[] UATProjectParams = { "-project=", "-scriptsforproject=" }; private static void InitializePerPlatformSDKs(string[] Args, bool bArgumentsAreForUBT, ILogger Logger) { // Before we setup AutoSDK, we check to see if any projects need to override the Main version so that AutoSDK // will set up an alternate SDK Dictionary PlatformToVersionMap = new(); IEnumerable ProjectFiles; if (bArgumentsAreForUBT == false) { ProjectFiles = Args // find arguments that start with one of the hard-coded parameters .Where(x => UATProjectParams.Any(y => x.StartsWith(y, StringComparison.OrdinalIgnoreCase))) // treat the part after the = as a path to a uproject, and ask the NativeProjects class to find a .uproject .Select(x => NativeProjects.FindProjectFile(x.Substring(x.IndexOf('=') + 1), Logger)) // only use existant projects .Where(x => x != null && FileReference.Exists(x)); } else { CommandLineArguments CommandLine = new(Args); BuildConfiguration BuildConfiguration = new(); XmlConfig.ApplyTo(BuildConfiguration); CommandLine.ApplyTo(BuildConfiguration); // get the project files for all targets - we allow null uproject files which means to use the defaults // (same as a uproject that has no override SDK versions set) ProjectFiles = TargetDescriptor.ParseCommandLineForProjects(CommandLine, Logger); } UEBuildPlatformSDK.InitializePerProjectSDKVersions(ProjectFiles.OfType()); // clear the cache used for auto-switching to the best manually-installed SDK UEBuildPlatformSDK.ClearManualSDKEnvVarCache(); } /// /// Finds all the UEBuildPlatformFactory types in this assembly and uses them to register all the available platforms, and uses a UBT commandline to check for per-project SDKs /// /// Whether to register platforms that are not installed /// Only register the host platform /// Commandline args to look through for finding uprojects, to look up per-project SDK versions /// The UBT mode (usually Build, but /// Logger for output internal static void RegisterPlatforms(bool bIncludeNonInstalledPlatforms, bool bHostPlatformOnly, Type UBTModeType, string[] ArgumentsForPerPlatform, ILogger Logger) { bool bUseTargetTripleParams = false; // @todo : add a ToolModeOptions.UseTargetTripleCommandLine or something if (UBTModeType.Name == "BuildMode") { bUseTargetTripleParams = true; } RegisterPlatforms(bIncludeNonInstalledPlatforms, bHostPlatformOnly, ArgumentsForPerPlatform, bArgumentsAreForUBT: bUseTargetTripleParams, Logger); } /// /// Finds all the UEBuildPlatformFactory types in this assembly and uses them to register all the available platforms, and uses a UAT commandline to check for per-project SDKs /// /// Whether to register platforms that are not installed /// Only register the host platform /// Commandline args to look through for finding uprojects, to look up per-project SDK versions /// Logger for output internal static void RegisterPlatforms(bool bIncludeNonInstalledPlatforms, bool bHostPlatformOnly, string[] ArgumentsForPerPlatform, ILogger Logger) { RegisterPlatforms(bIncludeNonInstalledPlatforms, bHostPlatformOnly, ArgumentsForPerPlatform, bArgumentsAreForUBT: false, Logger); } private static void RegisterPlatforms(bool bIncludeNonInstalledPlatforms, bool bHostPlatformOnly, string[] ArgumentsForPerPlatform, bool bArgumentsAreForUBT, ILogger Logger) { // Initialize the installed platform info using (GlobalTracer.Instance.BuildSpan("Initializing InstalledPlatformInfo").StartActive()) { InstalledPlatformInfo.Initialize(); } using (GlobalTracer.Instance.BuildSpan("Initializing PerPlatformSDKs").StartActive()) { InitializePerPlatformSDKs(ArgumentsForPerPlatform, bArgumentsAreForUBT, Logger); } // Find and register all tool chains and build platforms that are present Type[] AllTypes; using (GlobalTracer.Instance.BuildSpan("Querying types").StartActive()) { AllTypes = Assembly.GetExecutingAssembly().GetTypes(); } // register all build platforms first, since they implement SDK-switching logic that can set environment variables foreach (Type CheckType in AllTypes) { if (CheckType.IsClass && !CheckType.IsAbstract) { if (CheckType.IsSubclassOf(typeof(UEBuildPlatformFactory))) { Logger.LogDebug(" Registering build platform: {Platform}", CheckType.ToString()); using (GlobalTracer.Instance.BuildSpan(CheckType.Name).StartActive()) { UEBuildPlatformFactory TempInst = (UEBuildPlatformFactory)Activator.CreateInstance(CheckType)!; if (bHostPlatformOnly && TempInst.TargetPlatform != BuildHostPlatform.Current.Platform) { continue; } // We need all platforms to be registered when we run -validateplatform command to check SDK status of each if (bIncludeNonInstalledPlatforms || InstalledPlatformInfo.IsValidPlatform(TempInst.TargetPlatform)) { TempInst.RegisterBuildPlatforms(Logger); } } } } } } /// /// Gets an array of all platform folder names /// /// Array of platform folders public static string[] GetPlatformFolderNames() { if (CachedPlatformFolderNames == null) { List PlatformFolderNames = new List(); // Find all the platform folders to exclude from the list of precompiled modules PlatformFolderNames.AddRange(UnrealTargetPlatform.GetValidPlatformNames()); // Also exclude all the platform groups that this platform is not a part of PlatformFolderNames.AddRange(UnrealPlatformGroup.GetValidGroupNames()); // Save off the list as an array CachedPlatformFolderNames = PlatformFolderNames.ToArray(); } return CachedPlatformFolderNames; } /// /// Finds a list of folder names to include when building for this platform /// public IReadOnlySet GetIncludedFolderNames() { if (CachedIncludedFolderNames == null) { HashSet Names = new HashSet(DirectoryReference.Comparer); Names.Add(Platform.ToString()); foreach (UnrealPlatformGroup Group in UEBuildPlatform.GetPlatformGroups(Platform)) { Names.Add(Group.ToString()); } CachedIncludedFolderNames = Names; } return CachedIncludedFolderNames; } /// /// Finds a list of folder names to exclude when building for this platform /// public IReadOnlySet GetExcludedFolderNames() { CachedExcludedFolderNames ??= new HashSet(GetPlatformFolderNames().Except(GetIncludedFolderNames()), DirectoryReference.Comparer); return CachedExcludedFolderNames; } /// /// Whether the required external SDKs are installed for this platform. Could be either a manual install or an AutoSDK. /// public SDKStatus HasRequiredSDKsInstalled() { UEBuildPlatformSDK? SDK = UEBuildPlatform.GetSDK(Platform); if (SDK == null || !SDK.bIsSdkAllowedOnHost) { return SDKStatus.Invalid; } return SDK.HasRequiredSDKsInstalled(); } /// /// Whether this platform requires specific Visual Studio version. /// public virtual VCProjectFileFormat GetRequiredVisualStudioVersion() { return VCProjectFileFormat.Default; } /// /// The version required to support Visual Studio /// public virtual Version GetVersionRequiredForVisualStudio(VCProjectFileFormat Format) { return new Version(); } /// /// Gets all the registered platforms /// /// Sequence of registered platforms public static IEnumerable GetRegisteredPlatforms() { return BuildPlatformDictionary.Keys; } /// /// Searches a directory tree for build products to be cleaned. /// /// The directory to search /// Target or application names that may appear at the start of the build product name (eg. "UnrealEditor", "ShooterGameEditor") /// Suffixes which may appear at the end of the build product name /// List to receive a list of files to be cleaned /// List to receive a list of directories to be cleaned public void FindBuildProductsToClean(DirectoryReference BaseDir, string[] NamePrefixes, string[] NameSuffixes, List FilesToClean, List DirectoriesToClean) { foreach (FileReference File in DirectoryReference.EnumerateFiles(BaseDir)) { string FileName = File.GetFileName(); if (IsDefaultBuildProduct(FileName, NamePrefixes, NameSuffixes) || IsBuildProduct(FileName, NamePrefixes, NameSuffixes)) { FilesToClean.Add(File); } } foreach (DirectoryReference SubDir in DirectoryReference.EnumerateDirectories(BaseDir)) { string SubDirName = SubDir.GetDirectoryName(); if (IsBuildProduct(SubDirName, NamePrefixes, NameSuffixes)) { DirectoriesToClean.Add(SubDir); } else { FindBuildProductsToClean(SubDir, NamePrefixes, NameSuffixes, FilesToClean, DirectoriesToClean); } } } /// /// Enumerates any additional directories needed to clean this target /// /// The target to clean /// Receives a list of files to be removed /// Receives a list of directories to be removed public virtual void FindAdditionalBuildProductsToClean(ReadOnlyTargetRules Target, List FilesToDelete, List DirectoriesToDelete) { } /// /// Determines if a filename is a default UBT build product /// /// The name to check /// Target or application names that may appear at the start of the build product name (eg. "UnrealEditor", "ShooterGameEditor") /// Suffixes which may appear at the end of the build product name /// True if the substring matches the name of a build product, false otherwise public static bool IsDefaultBuildProduct(string FileName, string[] NamePrefixes, string[] NameSuffixes) { return UEBuildPlatform.IsBuildProductName(FileName, NamePrefixes, NameSuffixes, ".target") || UEBuildPlatform.IsBuildProductName(FileName, NamePrefixes, NameSuffixes, ".modules") || UEBuildPlatform.IsBuildProductName(FileName, NamePrefixes, NameSuffixes, ".version"); } /// /// Determines if the given name is a build product for a target. /// /// The name to check /// Target or application names that may appear at the start of the build product name (eg. "UnrealEditor", "ShooterGameEditor") /// Suffixes which may appear at the end of the build product name /// True if the string matches the name of a build product, false otherwise public abstract bool IsBuildProduct(string FileName, string[] NamePrefixes, string[] NameSuffixes); /// /// Determines if a string is in the canonical name of a UE build product, with a specific extension (eg. "UnrealEditor-Win64-Debug.exe" or "UnrealEditor-ModuleName-Win64-Debug.dll"). /// /// The file name to check /// Target or application names that may appear at the start of the build product name (eg. "UnrealEditor", "ShooterGameEditor") /// Suffixes which may appear at the end of the build product name /// The extension to check for /// True if the string matches the name of a build product, false otherwise public static bool IsBuildProductName(string FileName, string[] NamePrefixes, string[] NameSuffixes, string Extension) { return IsBuildProductName(FileName, 0, FileName.Length, NamePrefixes, NameSuffixes, Extension); } /// /// Determines if a substring is in the canonical name of a UE build product, with a specific extension (eg. "UnrealEditor-Win64-Debug.exe" or "UnrealEditor-ModuleName-Win64-Debug.dll"). /// /// The name to check /// Index of the first character to be checked /// Number of characters of the substring to check /// Target or application names that may appear at the start of the build product name (eg. "UnrealEditor", "ShooterGameEditor") /// Suffixes which may appear at the end of the build product name /// The extension to check for /// True if the substring matches the name of a build product, false otherwise public static bool IsBuildProductName(string FileName, int Index, int Count, string[] NamePrefixes, string[] NameSuffixes, string Extension) { // Check if the extension matches, and forward on to the next IsBuildProductName() overload without it if it does. if (Count > Extension.Length && String.Compare(FileName, Index + Count - Extension.Length, Extension, 0, Extension.Length, StringComparison.InvariantCultureIgnoreCase) == 0) { return IsBuildProductName(FileName, Index, Count - Extension.Length, NamePrefixes, NameSuffixes); } return false; } /// /// Determines if a substring is in the canonical name of a UE build product, excluding extension or other decoration (eg. "UnrealEditor-Win64-Debug" or "UnrealEditor-ModuleName-Win64-Debug"). /// /// The name to check /// Index of the first character to be checked /// Number of characters of the substring to check /// Target or application names that may appear at the start of the build product name (eg. "UnrealEditor", "ShooterGameEditor") /// Suffixes which may appear at the end of the build product name /// True if the substring matches the name of a build product, false otherwise public static bool IsBuildProductName(string FileName, int Index, int Count, string[] NamePrefixes, string[] NameSuffixes) { foreach (string NamePrefix in NamePrefixes) { if (Count >= NamePrefix.Length && String.Compare(FileName, Index, NamePrefix, 0, NamePrefix.Length, StringComparison.InvariantCultureIgnoreCase) == 0) { int MinIdx = Index + NamePrefix.Length; foreach (string NameSuffix in NameSuffixes) { int MaxIdx = Index + Count - NameSuffix.Length; if (MaxIdx >= MinIdx && String.Compare(FileName, MaxIdx, NameSuffix, 0, NameSuffix.Length, StringComparison.InvariantCultureIgnoreCase) == 0) { if (MinIdx < MaxIdx && FileName[MinIdx] == '-') { MinIdx++; while (MinIdx < MaxIdx && FileName[MinIdx] != '-' && FileName[MinIdx] != '.') { MinIdx++; } } if (MinIdx == MaxIdx) { return true; } } } } } return false; } public virtual void PostBuildSync(UEBuildTarget Target) { } /// /// Get the bundle directory for the shared link environment /// /// The target rules /// List of executable output files /// Path to the bundle directory public virtual DirectoryReference? GetBundleDirectory(ReadOnlyTargetRules Rules, List OutputFiles) { return null; } /// /// Determines whether a given platform is available /// /// The platform to check for /// Ignore the sdks presence when checking for platform availablity (many platforms can be cooked as a target platform without requiring sdk) /// True if it's available, false otherwise public static bool IsPlatformAvailable(UnrealTargetPlatform Platform, bool bIgnoreSDKCheck = false) { return BuildPlatformDictionary.ContainsKey(Platform) && (bIgnoreSDKCheck || BuildPlatformDictionary[Platform].HasRequiredSDKsInstalled() == SDKStatus.Valid); } /// /// Determines whether a given platform is available in the context of a particular Taget /// /// The platform to check for /// A Target object that may further restrict available platforms /// Ignore the sdks presence when checking for platform availablity (many platforms can be cooked as a target platform without requiring sdk) /// True if it's available, false otherwise public static bool IsPlatformAvailableForTarget(UnrealTargetPlatform Platform, ReadOnlyTargetRules Target, bool bIgnoreSDKCheck = false) { return IsPlatformAvailable(Platform, bIgnoreSDKCheck) && Target.IsPlatformOptedIn(Platform); } /// /// Register the given platforms UEBuildPlatform instance /// /// The UEBuildPlatform instance to use for the InPlatform /// Logger for output public static void RegisterBuildPlatform(UEBuildPlatform InBuildPlatform, ILogger Logger) { Logger.LogDebug(" Registering build platform: {Platform} - buildable: {Buildable}", InBuildPlatform.Platform, InBuildPlatform.HasRequiredSDKsInstalled() == SDKStatus.Valid); if (BuildPlatformDictionary.ContainsKey(InBuildPlatform.Platform) == true) { Logger.LogWarning("RegisterBuildPlatform Warning: Registering build platform {Platform} for {ForPlatform} when it is already set to {CurPlatform}", InBuildPlatform.ToString(), InBuildPlatform.Platform.ToString(), BuildPlatformDictionary[InBuildPlatform.Platform].ToString()); BuildPlatformDictionary[InBuildPlatform.Platform] = InBuildPlatform; } else { BuildPlatformDictionary.Add(InBuildPlatform.Platform, InBuildPlatform); } } /// /// Assign a platform as a member of the given group /// public static void RegisterPlatformWithGroup(UnrealTargetPlatform InPlatform, UnrealPlatformGroup InGroup) { // find or add the list of groups for this platform List? Platforms; if (!PlatformGroupDictionary.TryGetValue(InGroup, out Platforms)) { Platforms = new List(); PlatformGroupDictionary.Add(InGroup, Platforms); } Platforms.Add(InPlatform); } /// /// Retrieve the list of platforms in this group (if any) /// public static List GetPlatformsInGroup(UnrealPlatformGroup InGroup) { List? PlatformList; if (!PlatformGroupDictionary.TryGetValue(InGroup, out PlatformList)) { PlatformList = new List(); } return PlatformList; } /// /// Enumerates all the platform groups for a given platform /// /// The platform to look for /// List of platform groups that this platform is a member of public static IEnumerable GetPlatformGroups(UnrealTargetPlatform Platform) { return PlatformGroupDictionary.Where(x => x.Value.Contains(Platform)).Select(x => x.Key); } /// /// Retrieve the IUEBuildPlatform instance for the given TargetPlatform /// /// The UnrealTargetPlatform being built /// UEBuildPlatform The instance of the build platform public static UEBuildPlatform GetBuildPlatform(UnrealTargetPlatform InPlatform) { UEBuildPlatform? Platform; if (!TryGetBuildPlatform(InPlatform, out Platform)) { throw new BuildException("GetBuildPlatform: No BuildPlatform found for {0}", InPlatform.ToString()); } return Platform; } /// /// Retrieve the IUEBuildPlatform instance for the given TargetPlatform /// /// The UnrealTargetPlatform being built /// /// UEBuildPlatform The instance of the build platform public static bool TryGetBuildPlatform(UnrealTargetPlatform InPlatform, [NotNullWhen(true)] out UEBuildPlatform? Platform) { return BuildPlatformDictionary.TryGetValue(InPlatform, out Platform); } /// /// Allow all registered build platforms to modify the newly created module /// passed in for the given platform. /// This is not required - but allows for hiding details of a particular platform. /// /// The name of the module /// The module rules /// The target being build public static void PlatformModifyHostModuleRules(string ModuleName, ModuleRules Rules, ReadOnlyTargetRules Target) { foreach (KeyValuePair PlatformEntry in BuildPlatformDictionary) { PlatformEntry.Value.ModifyModuleRulesForOtherPlatform(ModuleName, Rules, Target); } } /// /// Returns the delimiter used to separate paths in the PATH environment variable for the platform we are executing on. /// [Obsolete("Replace with System.IO.Path.PathSeparator")] public static string GetPathVarDelimiter() { return Path.PathSeparator.ToString(); } /// /// Gets the platform name that should be used. /// public virtual string GetPlatformName() { return Platform.ToString(); } /// /// If this platform can be compiled with XGE /// public virtual bool CanUseXGE() { return true; } /// /// If this platform can be compiled with FASTBuild /// public virtual bool CanUseFASTBuild() { return false; } /// /// If this platform can be compiled with SN-DBS /// public virtual bool CanUseSNDBS() { return false; } /// /// Set all the platform-specific defaults for a new target /// public virtual void ResetTarget(TargetRules Target) { } /// /// Validate a target's settings /// public virtual void ValidateTarget(TargetRules Target) { if (Target.bMergeModules) { ValidateMergedTarget(Target); } } /// /// Validate a target's settings for merged modules /// public virtual void ValidateMergedTarget(TargetRules Target) { } /// /// Validate a plugin's settings /// public virtual void ValidatePlugin(UEBuildPlugin Plugin, ReadOnlyTargetRules Target) { } /// /// Validate a UEBuildModule before it's processed /// The UEBuildModule that needs to be validated /// Options for the target being built /// The logger for output /// public virtual void ValidateModule(UEBuildModule Module, ReadOnlyTargetRules Target, ILogger Logger) { } /// /// Validate a UEBuildModule's include paths before it's processed /// /// The UEBuildModule that needs to be validated /// Options for the target being built /// Other modules to validate against, if needed /// The logger for output public virtual bool ValidateModuleIncludePaths(UEBuildModule Module, ReadOnlyTargetRules Target, IEnumerable AllModules, ILogger Logger) { CppCompileWarnings ModuleWarningSettings = Module.Rules.CppCompileWarningSettings; if (ModuleWarningSettings.ModuleIncludePathWarningLevel <= WarningLevel.Off && ModuleWarningSettings.ModuleIncludePrivateWarningLevel <= WarningLevel.Off && ModuleWarningSettings.ModuleIncludeSubdirectoryWarningLevel <= WarningLevel.Off) { return false; } bool AnyErrors = false; void LoggerFunc(string? message, params object?[] args) { if (ModuleWarningSettings.ModuleIncludePathWarningLevel == WarningLevel.Warning) { Logger.LogWarning($"Warning: {message}", args); } else if (ModuleWarningSettings.ModuleIncludePathWarningLevel == WarningLevel.Error) { Logger.LogError($"Error: {message}", args); AnyErrors = true; } } void LoggerFuncPrivate(string? message, params object?[] args) { if (ModuleWarningSettings.ModuleIncludePrivateWarningLevel == WarningLevel.Warning) { Logger.LogWarning($"Warning: {message}", args); } else if (ModuleWarningSettings.ModuleIncludePrivateWarningLevel == WarningLevel.Error) { Logger.LogError($"Error: {message}", args); AnyErrors = true; } } void LoggerFuncSubDir(string? message, params object?[] args) { if (ModuleWarningSettings.ModuleIncludeSubdirectoryWarningLevel == WarningLevel.Warning) { Logger.LogWarning($"Warning: {message}", args); } else if (ModuleWarningSettings.ModuleIncludeSubdirectoryWarningLevel == WarningLevel.Error) { Logger.LogError($"Error: {message}", args); AnyErrors = true; } } IOrderedEnumerable PublicIncludePaths = Module.Rules.PublicIncludePaths.Select(x => DirectoryReference.FromString(x)!).OrderBy(x => x.FullName); IOrderedEnumerable PrivateIncludePaths = Module.Rules.PrivateIncludePaths.Select(x => DirectoryReference.FromString(x)!).OrderBy(x => x.FullName); IOrderedEnumerable InternalIncludePaths = Module.Rules.InternalIncludePaths.Select(x => DirectoryReference.FromString(x)!).OrderBy(x => x.FullName); IOrderedEnumerable PublicSystemIncludePaths = Module.Rules.PublicSystemIncludePaths.Select(x => DirectoryReference.FromString(x)!).OrderBy(x => x.FullName); IOrderedEnumerable OtherModules = AllModules.Where(x => x != Module).OrderBy(x => x.Name); if (Module is UEBuildModuleExternal) { foreach (DirectoryReference Path in PublicIncludePaths) { LoggerFunc("External module '{Name}' is adding '{Path}' to PublicIncludePaths. This path should be added to PublicSystemIncludePaths.", Module.Name, Path.MakeRelativeTo(Module.ModuleDirectory)); } foreach (DirectoryReference Path in PublicSystemIncludePaths.Where(x => !x.IsUnderDirectory(Module.ModuleDirectory))) { UEBuildModule? OtherModule = OtherModules.FirstOrDefault(x => Path.IsUnderDirectory(x.ModuleDirectory)); if (OtherModule is UEBuildModuleExternal) { LoggerFunc("External module '{Name}' is adding '{Path}' from external module '{OtherModule}' to PublicSystemIncludePaths. Did you intend to add a public reference?", Module.Name, Path, OtherModule.Name); } else if (OtherModule is UEBuildModuleCPP) { LoggerFunc("External module '{Name}' is adding '{Path}' from module '{OtherModule}' to PublicSystemIncludePaths. This is not allowed.", Module.Name, Path, OtherModule.Name); } } foreach (DirectoryReference Path in PrivateIncludePaths) { LoggerFunc("External module '{Name}' is adding '{Path}' to PrivateIncludePaths. This path is unused.", Module.Name, Path); } foreach (DirectoryReference Path in InternalIncludePaths) { LoggerFunc("External module '{Name}' is adding '{Path}' to InternalIncludePaths. This path is unused.", Module.Name, Path); } } else if (Module is UEBuildModuleCPP) { foreach (DirectoryReference Path in PublicIncludePaths) { if (Path.IsUnderDirectory(Module.ModuleDirectory)) { if (Path == Module.ModuleDirectory) { LoggerFunc("Module '{Name}' is adding root directory to PublicIncludePaths. This is not allowed.", Module.Name); } else if (Path.IsUnderDirectory(DirectoryReference.Combine(Module.ModuleDirectory, "Private")) || Path.IsUnderDirectory(DirectoryReference.Combine(Module.ModuleDirectory, "Internal"))) { LoggerFuncPrivate("Module '{Name}' is adding subdirectory '{Path}' to PublicIncludePaths. This is not allowed.", Module.Name, Path.MakeRelativeTo(Module.ModuleDirectory)); } else if (Path.IsUnderDirectory(DirectoryReference.Combine(Module.ModuleDirectory, "Public")) || Path.IsUnderDirectory(DirectoryReference.Combine(Module.ModuleDirectory, "Classes"))) { LoggerFuncSubDir("Module '{Name}' is adding subdirectory '{Path}' to PublicIncludePaths. This is not necessary.", Module.Name, Path.MakeRelativeTo(Module.ModuleDirectory)); } } UEBuildModule? OtherModule = OtherModules.FirstOrDefault(x => Path.IsUnderDirectory(x.ModuleDirectory)); if (OtherModule is UEBuildModuleExternal) { LoggerFunc("Module '{Name}' is adding '{Path}' from external module '{OtherModule}' to PublicIncludePaths. Did you intend to add a public reference?", Module.Name, Path, OtherModule.Name); } else if (OtherModule is UEBuildModuleCPP) { if (Path == OtherModule.ModuleDirectory) { LoggerFuncPrivate("Module '{Name}' is adding root directory from '{OtherModule}' to PublicIncludePaths. This is not allowed.", Module.Name, OtherModule.Name); } else if (Path.IsUnderDirectory(DirectoryReference.Combine(OtherModule.ModuleDirectory, "Private")) | Path.IsUnderDirectory(DirectoryReference.Combine(OtherModule.ModuleDirectory, "Internal"))) { LoggerFuncPrivate("Module '{Name}' is adding '{Path}' from module '{OtherModule}' to PublicIncludePaths. This is not allowed.", Module.Name, Path, OtherModule.Name); } else if (Path.IsUnderDirectory(DirectoryReference.Combine(OtherModule.ModuleDirectory, "Public")) || Path.IsUnderDirectory(DirectoryReference.Combine(OtherModule.ModuleDirectory, "Classes"))) { LoggerFunc("Module '{Name}' is adding '{Path}' from module '{OtherModule}' to PublicIncludePaths. Did you intend to add a public reference?", Module.Name, Path, OtherModule.Name); } } } foreach (DirectoryReference Path in PrivateIncludePaths) { if (Path.IsUnderDirectory(Module.ModuleDirectory)) { if (Path == Module.ModuleDirectory) { LoggerFunc("Module '{Name}' is adding root directory to PrivateIncludePaths. This is not recommended.", Module.Name); } else if (Path.IsUnderDirectory(DirectoryReference.Combine(Module.ModuleDirectory, "Private")) || Path.IsUnderDirectory(DirectoryReference.Combine(Module.ModuleDirectory, "Internal")) || Path.IsUnderDirectory(DirectoryReference.Combine(Module.ModuleDirectory, "Public")) || Path.IsUnderDirectory(DirectoryReference.Combine(Module.ModuleDirectory, "Classes"))) { LoggerFuncSubDir("Module '{Name}' is adding subdirectory '{Path}' to PrivateIncludePaths. This is not necessary.", Module.Name, Path.MakeRelativeTo(Module.ModuleDirectory)); } } UEBuildModule? OtherModule = OtherModules.FirstOrDefault(x => Path.IsUnderDirectory(x.ModuleDirectory)); if (OtherModule is UEBuildModuleExternal) { LoggerFunc("Module '{Name}' is adding '{Path}' from external module '{OtherModule}' to PrivateIncludePaths. Did you intend to add a private reference?", Module.Name, Path, OtherModule.Name); } else if (OtherModule is UEBuildModuleCPP) { if (Path == OtherModule.ModuleDirectory) { LoggerFuncPrivate("Module '{Name}' is adding root directory from '{OtherModule}' to PrivateIncludePaths. This is not allowed.", Module.Name, OtherModule.Name); } else if (Path.IsUnderDirectory(DirectoryReference.Combine(OtherModule.ModuleDirectory, "Private")) | Path.IsUnderDirectory(DirectoryReference.Combine(OtherModule.ModuleDirectory, "Internal"))) { LoggerFuncPrivate("Module '{Name}' is adding '{Path}' from module '{OtherModule}' to PrivateIncludePaths. This is not allowed.", Module.Name, Path, OtherModule.Name); } else if (Path.IsUnderDirectory(DirectoryReference.Combine(OtherModule.ModuleDirectory, "Public")) || Path.IsUnderDirectory(DirectoryReference.Combine(OtherModule.ModuleDirectory, "Classes"))) { LoggerFunc("Module '{Name}' is adding '{Path}' from module '{OtherModule}' to PrivateIncludePaths. Did you intend to add a private reference?", Module.Name, Path, OtherModule.Name); } } } foreach (DirectoryReference Path in InternalIncludePaths) { if (Path.IsUnderDirectory(Module.ModuleDirectory)) { if (Path == Module.ModuleDirectory) { LoggerFunc("Module '{Name}' is adding root directory to InternalIncludePaths. This is not allowed.", Module.Name); } else if (Path.IsUnderDirectory(DirectoryReference.Combine(Module.ModuleDirectory, "Private")) || Path.IsUnderDirectory(DirectoryReference.Combine(Module.ModuleDirectory, "Internal"))) { LoggerFunc("Module '{Name}' is adding subdirectory '{Path}' to InternalIncludePaths. This is not allowed.", Module.Name, Path.MakeRelativeTo(Module.ModuleDirectory)); } else if (Path.IsUnderDirectory(DirectoryReference.Combine(Module.ModuleDirectory, "Public")) || Path.IsUnderDirectory(DirectoryReference.Combine(Module.ModuleDirectory, "Classes"))) { LoggerFuncSubDir("Module '{Name}' is adding subdirectory '{Path}' to InternalIncludePaths. This is not necessary.", Module.Name, Path.MakeRelativeTo(Module.ModuleDirectory)); } } UEBuildModule? OtherModule = OtherModules.FirstOrDefault(x => Path.IsUnderDirectory(x.ModuleDirectory)); if (OtherModule is UEBuildModuleExternal) { LoggerFunc("Module '{Name}' is adding '{Path}' from external module '{OtherModule}' to InternalIncludePaths. Did you intend to add a public reference?", Module.Name, Path, OtherModule.Name); } else if (OtherModule is UEBuildModuleCPP) { if (Path == OtherModule.ModuleDirectory) { LoggerFunc("Module '{Name}' is adding root directory from '{OtherModule}' to InternalIncludePaths. This is not allowed.", Module.Name, OtherModule.Name); } else if (Path.IsUnderDirectory(DirectoryReference.Combine(OtherModule.ModuleDirectory, "Private")) | Path.IsUnderDirectory(DirectoryReference.Combine(OtherModule.ModuleDirectory, "Internal"))) { LoggerFunc("Module '{Name}' is adding '{Path}' from module '{OtherModule}' to InternalIncludePaths. This is not allowed.", Module.Name, Path, OtherModule.Name); } else if (Path.IsUnderDirectory(DirectoryReference.Combine(OtherModule.ModuleDirectory, "Public")) || Path.IsUnderDirectory(DirectoryReference.Combine(OtherModule.ModuleDirectory, "Classes"))) { LoggerFunc("Module '{Name}' is adding '{Path}' from module '{OtherModule}' to InternalIncludePaths. Did you intend to add a public reference?", Module.Name, Path, OtherModule.Name); } } } } return AnyErrors; } /// /// Return whether the given platform requires a monolithic build /// /// The platform of interest /// The configuration of interest /// public static bool PlatformRequiresMonolithicBuilds(UnrealTargetPlatform InPlatform, UnrealTargetConfiguration InConfiguration) { // Some platforms require monolithic builds... UEBuildPlatform? BuildPlatform; if (TryGetBuildPlatform(InPlatform, out BuildPlatform)) { return BuildPlatform.ShouldCompileMonolithicBinary(InPlatform); } // We assume it does not return false; } /// /// Get the extension to use for the given binary type /// /// The binary type being built /// string The binary extension (i.e. 'exe' or 'dll') public virtual string GetBinaryExtension(UEBuildBinaryType InBinaryType) { throw new BuildException("GetBinaryExtensiton for {0} not handled in {1}", InBinaryType.ToString(), ToString()); } /// /// Get the extensions to use for debug info for the given binary type /// /// Options for the target being built /// The binary type being built /// string[] The debug info extensions (i.e. 'pdb') public virtual string[] GetDebugInfoExtensions(ReadOnlyTargetRules InTarget, UEBuildBinaryType InBinaryType) { throw new BuildException("GetDebugInfoExtensions for {0} not handled in {1}", InBinaryType.ToString(), ToString()); } /// /// Whether this platform should build a monolithic binary /// public virtual bool ShouldCompileMonolithicBinary(UnrealTargetPlatform InPlatform) { return false; } /// /// Modify the rules for a newly created module, where the target is a different host platform. /// This is not required - but allows for hiding details of a particular platform. /// /// The name of the module /// The module rules /// The target being build public virtual void ModifyModuleRulesForOtherPlatform(string ModuleName, ModuleRules Rules, ReadOnlyTargetRules Target) { } /// /// For platforms that need to output multiple files per binary (ie Android "fat" binaries) /// this will emit multiple paths. By default, it simply makes an array from the input /// public virtual List FinalizeBinaryPaths(FileReference BinaryName, FileReference? ProjectFile, ReadOnlyTargetRules Target) { List TempList = new List() { BinaryName }; return TempList; } /// /// Return all valid configurations for this platform /// Typically, this is always Debug, Development, and Shipping - but Test is a likely future addition for some platforms /// public virtual List GetConfigurations(UnrealTargetPlatform InUnrealTargetPlatform, bool bIncludeDebug) { List Configurations = new List() { UnrealTargetConfiguration.Development, }; if (bIncludeDebug) { Configurations.Insert(0, UnrealTargetConfiguration.Debug); } return Configurations; } protected static bool DoProjectSettingsMatchDefault(UnrealTargetPlatform Platform, DirectoryReference ProjectDirectoryName, string Section, string[]? BoolKeys, string[]? IntKeys, string[]? StringKeys, ILogger Logger) { ConfigHierarchy ProjIni = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, ProjectDirectoryName, Platform); ConfigHierarchy DefaultIni = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, (DirectoryReference?)null, Platform); // look at all bool values if (BoolKeys != null) { foreach (string Key in BoolKeys) { bool Default = false, Project = false; DefaultIni.GetBool(Section, Key, out Default); ProjIni.GetBool(Section, Key, out Project); if (Default != Project) { Log.TraceInformationOnce("{0} is not set to default. (Base: {1} vs. {2}: {3})", Key, Default, Path.GetFileName(ProjectDirectoryName.FullName), Project); return false; } } } // look at all int values if (IntKeys != null) { foreach (string Key in IntKeys) { int Default = 0, Project = 0; DefaultIni.GetInt32(Section, Key, out Default); ProjIni.GetInt32(Section, Key, out Project); if (Default != Project) { Log.TraceInformationOnce("{0} is not set to default. (Base: {1} vs. {2}: {3})", Key, Default, Path.GetFileName(ProjectDirectoryName.FullName), Project); return false; } } } // look for all string values if (StringKeys != null) { foreach (string Key in StringKeys) { string? Default = "", Project = ""; DefaultIni.GetString(Section, Key, out Default); ProjIni.GetString(Section, Key, out Project); if (Default != Project) { Log.TraceInformationOnce("{0} is not set to default. (Base: {1} vs. {2}: {3})", Key, Default, Path.GetFileName(ProjectDirectoryName.FullName), Project); return false; } } } // if we get here, we match all important settings return true; } /// /// Check for the default configuration /// return true if the project uses the default build config /// public virtual bool HasDefaultBuildConfig(UnrealTargetPlatform Platform, DirectoryReference ProjectDirectoryName) { string[] BoolKeys = new string[] { "bCompileApex", "bCompileICU", "bCompileRecast", "bCompileSpeedTree", "bCompileWithPluginSupport", "bCompilePhysXVehicle", "bCompileFreeType", "bCompileForSize", "bCompileCEF3", "bCompileCustomSQLitePlatform" }; return DoProjectSettingsMatchDefault(Platform, ProjectDirectoryName, "/Script/BuildSettings.BuildSettings", BoolKeys, null, null, Logger); } /// /// Check for whether we require a build for platform reasons /// return true if the project requires a build /// public virtual bool RequiresBuild(UnrealTargetPlatform Platform, DirectoryReference ProjectDirectoryName) { return false; } /// /// Get a list of extra modules the platform requires. /// This is to allow undisclosed platforms to add modules they need without exposing information about the platform. /// /// The target being build /// List of extra modules the platform needs to add to the target public virtual void AddExtraModules(ReadOnlyTargetRules Target, List ExtraModuleNames) { } /// /// Modify the rules for a newly created module, in a target that's being built for this platform. /// This is not required - but allows for hiding details of a particular platform. /// /// The name of the module /// The module rules /// The target being build public virtual void ModifyModuleRulesForActivePlatform(string ModuleName, ModuleRules Rules, ReadOnlyTargetRules Target) { } /// /// Setup the target environment for building /// /// Settings for the target being compiled /// The compile environment for this target /// The link environment for this target public abstract void SetUpEnvironment(ReadOnlyTargetRules Target, CppCompileEnvironment CompileEnvironment, LinkEnvironment LinkEnvironment); /// /// Setup the configuration environment for building /// /// The target being built /// The global compile environment /// The global link environment public virtual void SetUpConfigurationEnvironment(ReadOnlyTargetRules Target, CppCompileEnvironment GlobalCompileEnvironment, LinkEnvironment GlobalLinkEnvironment) { if (GlobalCompileEnvironment.bUseDebugCRT) { GlobalCompileEnvironment.Definitions.Add("_DEBUG=1"); // the engine doesn't use this, but lots of 3rd party stuff does } else { GlobalCompileEnvironment.Definitions.Add("NDEBUG=1"); // the engine doesn't use this, but lots of 3rd party stuff does } switch (Target.Configuration) { default: case UnrealTargetConfiguration.Debug: GlobalCompileEnvironment.Definitions.Add("UE_BUILD_DEBUG=1"); break; case UnrealTargetConfiguration.DebugGame: // Individual game modules can be switched to be compiled in debug as necessary. By default, everything is compiled in development. case UnrealTargetConfiguration.Development: GlobalCompileEnvironment.Definitions.Add("UE_BUILD_DEVELOPMENT=1"); break; case UnrealTargetConfiguration.Shipping: GlobalCompileEnvironment.Definitions.Add("UE_BUILD_SHIPPING=1"); break; case UnrealTargetConfiguration.Test: GlobalCompileEnvironment.Definitions.Add("UE_BUILD_TEST=1"); break; } // Create debug info based on the heuristics specified by the user. GlobalCompileEnvironment.bCreateDebugInfo = Target.DebugInfo != DebugInfoMode.None && ShouldCreateDebugInfo(Target); GlobalLinkEnvironment.bCreateDebugInfo = GlobalCompileEnvironment.bCreateDebugInfo; } /// /// Allows the platform to return various build metadata that is not tracked by other means. If the returned string changes, the makefile will be invalidated. /// /// The project file being built /// String describing the current build metadata public string GetExternalBuildMetadata(FileReference? ProjectFile) { StringBuilder Result = new StringBuilder(); GetExternalBuildMetadata(ProjectFile, Result); return Result.ToString(); } /// /// Allows the platform to return various build metadata that is not tracked by other means. If the returned string changes, the makefile will be invalidated. /// /// The project file being built /// String builder to contain build metadata public virtual void GetExternalBuildMetadata(FileReference? ProjectFile, StringBuilder Metadata) { } /// /// Allows the platform to modify the binary link environment before the binary is built /// /// The binary link environment being created /// The binary compile environment being used /// The target rules in use /// The toolchain being used /// Action graph that is used to build the binary public virtual void ModifyBinaryLinkEnvironment(LinkEnvironment BinaryLinkEnvironment, CppCompileEnvironment BinaryCompileEnvironment, ReadOnlyTargetRules Target, UEToolChain ToolChain, IActionGraphBuilder Graph) { } /// /// Indicates whether this platform requires a .loadorder file to be generated for the build. /// .loadorder files contain a list of dynamic modules and the exact order in which they should be loaded /// to ensure that all dependencies are satisfied i.e. we don't attempt to load a module without loading /// all its dependencies first. /// As such, this file is only needed in modular builds on some platforms (depending on the way they implement dynamic modules such as DLLs). /// /// The target rules in use /// True if .loadorder file should be generated public virtual bool RequiresLoadOrderManifest(ReadOnlyTargetRules Target) { return false; } /// /// Checks if platform is part of a given platform group /// /// The platform to check /// The platform group to check /// True if platform is part of a platform group internal static bool IsPlatformInGroup(UnrealTargetPlatform Platform, UnrealPlatformGroup PlatformGroup) { List? Platforms = UEBuildPlatform.GetPlatformsInGroup(PlatformGroup); if (Platforms != null) { return Platforms.Contains(Platform); } else { return false; } } /// /// Gets the SDK object that was passed in to the constructor /// /// The SDK object public UEBuildPlatformSDK? GetSDK() { return UEBuildPlatformSDK.GetSDKForPlatform(Platform.ToString()); } /// /// Gets the SDK object that was passed in to the constructor to the UEBuildPlatform constructor for this platform /// /// The SDK object public static UEBuildPlatformSDK? GetSDK(UnrealTargetPlatform Platform) { return UEBuildPlatformSDK.GetSDKForPlatform(Platform.ToString()); } /// /// Whether this platform should create debug information or not /// /// The target being built /// bool true if debug info should be generated, false if not public abstract bool ShouldCreateDebugInfo(ReadOnlyTargetRules Target); /// /// Creates a toolchain instance for this platform. There should be a single toolchain instance per-target, as their may be /// state data and configuration cached between calls. /// /// The target being built /// New toolchain instance. public abstract UEToolChain CreateToolChain(ReadOnlyTargetRules Target); /// /// Deploys the given target /// /// Receipt for the target being deployed public abstract void Deploy(TargetReceipt Receipt); } }