public bool IsValidPlatformAndConfiguration(UnrealTargetConfiguration Configuration, UnrealTargetPlatform Platform, EProjectType ProjectType = EProjectType.Any) { if (UnrealBuildTool.IsEngineInstalled()) { foreach (InstalledPlatformConfiguration PlatformConfiguration in InstalledPlatformConfigurations) { if (PlatformConfiguration.Configuration == Configuration && PlatformConfiguration.Platform == Platform) { if (ProjectType == EProjectType.Any || PlatformConfiguration.ProjectType == EProjectType.Any || PlatformConfiguration.ProjectType == ProjectType) { if (string.IsNullOrEmpty(PlatformConfiguration.RequiredFile) || File.Exists(PlatformConfiguration.RequiredFile)) { return(true); } } } } return(false); } return(true); }
/// <summary> /// Add the given Engine 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. /// </summary> /// <param name="ModuleNames">The names of the modules to add</param> public void AddEngineThirdPartyPrivateStaticDependencies(TargetInfo Target, params string[] InModuleNames) { if (!UnrealBuildTool.IsEngineInstalled() || Target.IsMonolithic) { PrivateDependencyModuleNames.AddRange(InModuleNames); } }
/// <summary> /// Creates a cache hierarchy for a particular target /// </summary> /// <param name="ProjectFile">Project file for the target being built</param> /// <param name="TargetName">Name of the target</param> /// <param name="Platform">Platform being built</param> /// <param name="Configuration">Configuration being built</param> /// <param name="TargetType">The target type</param> /// <param name="Architecture">The target architecture</param> /// <returns>Dependency cache hierarchy for the given project</returns> public static CppDependencyCache CreateHierarchy(FileReference ProjectFile, string TargetName, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, TargetType TargetType, string Architecture) { CppDependencyCache Cache = null; if (ProjectFile == null || !UnrealBuildTool.IsEngineInstalled()) { string AppName; if (TargetType == TargetType.Program) { AppName = TargetName; } else { AppName = UEBuildTarget.GetAppNameForTargetType(TargetType); } FileReference EngineCacheLocation = FileReference.Combine(UnrealBuildTool.EngineDirectory, UEBuildTarget.GetPlatformIntermediateFolder(Platform, Architecture), AppName, Configuration.ToString(), "DependencyCache.bin"); Cache = FindOrAddCache(EngineCacheLocation, UnrealBuildTool.EngineDirectory, Cache); } if (ProjectFile != null) { FileReference ProjectCacheLocation = FileReference.Combine(ProjectFile.Directory, UEBuildTarget.GetPlatformIntermediateFolder(Platform, Architecture), TargetName, Configuration.ToString(), "DependencyCache.bin"); Cache = FindOrAddCache(ProjectCacheLocation, ProjectFile.Directory, Cache); } return(Cache); }
/// <summary> /// Generates a full path to action history file for the specified target. /// </summary> public static FileReference GeneratePathForTarget(UEBuildTarget Target) { DirectoryReference Folder = null; if (Target.ShouldCompileMonolithic() || Target.TargetType == TargetRules.TargetType.Program) { // Monolithic configs and programs have their Action History stored in their respective project folders // or under engine intermediate folder + program name folder DirectoryReference RootDirectory; if (Target.ProjectFile != null) { RootDirectory = Target.ProjectFile.Directory; } else { RootDirectory = UnrealBuildTool.EngineDirectory; } Folder = DirectoryReference.Combine(RootDirectory, BuildConfiguration.PlatformIntermediateFolder, Target.GetTargetName()); } else { // Shared action history (unless this is an installed build target) Folder = (UnrealBuildTool.IsEngineInstalled() && Target.ProjectFile != null) ? DirectoryReference.Combine(Target.ProjectFile.Directory, BuildConfiguration.BaseIntermediateFolder) : DirectoryReference.Combine(UnrealBuildTool.EngineDirectory, BuildConfiguration.BaseIntermediateFolder); } return(FileReference.Combine(Folder, "ActionHistory.bin")); }
/// <summary> /// Add the given Engine 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. /// </summary> /// <param name="Target">The target this module belongs to</param> /// <param name="ModuleNames">The names of the modules to add</param> public void AddEngineThirdPartyPrivateStaticDependencies(ReadOnlyTargetRules Target, params string[] ModuleNames) { if (!UnrealBuildTool.IsEngineInstalled() || Target.LinkType == TargetLinkType.Monolithic) { PrivateDependencyModuleNames.AddRange(ModuleNames); } }
/// <summary> /// Initialize the list of input files /// </summary> public static List <InputFile> FindInputFiles() { // Find all the input file locations List <InputFile> InputFiles = new List <InputFile>(); // Skip all the config files under the Engine folder if it's an installed build if (!UnrealBuildTool.IsEngineInstalled()) { // Check for the config file under /Engine/Programs/NotForLicensees/UnrealBuildTool FileReference NotForLicenseesConfigLocation = FileReference.Combine(UnrealBuildTool.EngineDirectory, "Programs", "NotForLicensees", "UnrealBuildTool", "BuildConfiguration.xml"); if (FileReference.Exists(NotForLicenseesConfigLocation)) { InputFiles.Add(new InputFile { Location = NotForLicenseesConfigLocation, FolderName = "NotForLicensees" }); } // Check for the user config file under /Engine/Programs/NotForLicensees/UnrealBuildTool FileReference UserConfigLocation = FileReference.Combine(UnrealBuildTool.EngineDirectory, "Saved", "UnrealBuildTool", "BuildConfiguration.xml"); if (!FileReference.Exists(UserConfigLocation)) { CreateDefaultConfigFile(UserConfigLocation); } InputFiles.Add(new InputFile { Location = UserConfigLocation, FolderName = "User" }); } // Check for the global config file under AppData/Unreal Engine/UnrealBuildTool string AppDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); if (!String.IsNullOrEmpty(AppDataFolder)) { FileReference AppDataConfigLocation = FileReference.Combine(new DirectoryReference(AppDataFolder), "Unreal Engine", "UnrealBuildTool", "BuildConfiguration.xml"); if (!FileReference.Exists(AppDataConfigLocation)) { CreateDefaultConfigFile(AppDataConfigLocation); } InputFiles.Add(new InputFile { Location = AppDataConfigLocation, FolderName = "Global (AppData)" }); } // Check for the global config file under My Documents/Unreal Engine/UnrealBuildTool string PersonalFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal); if (!String.IsNullOrEmpty(PersonalFolder)) { FileReference PersonalConfigLocation = FileReference.Combine(new DirectoryReference(PersonalFolder), "Unreal Engine", "UnrealBuildTool", "BuildConfiguration.xml"); if (FileReference.Exists(PersonalConfigLocation)) { InputFiles.Add(new InputFile { Location = PersonalConfigLocation, FolderName = "Global (Documents)" }); } } return(InputFiles); }
/// <summary> /// Add the given Engine 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. /// </summary> /// <param name="ModuleNames">The names of the modules to add</param> public void AddEngineThirdPartyPrivateDynamicDependencies(TargetInfo Target, params string[] InModuleNames) { if (!UnrealBuildTool.IsEngineInstalled() || Target.IsMonolithic) { PrivateIncludePathModuleNames.AddRange(InModuleNames); DynamicallyLoadedModuleNames.AddRange(InModuleNames); } }
/// <summary> /// Enumerates all the locations of metadata caches for the given target /// </summary> /// <param name="ProjectFile">Project file for the target being built</param> /// <returns>Dependency cache hierarchy for the given project</returns> public static IEnumerable <FileReference> GetFilesToClean(FileReference ProjectFile) { if (ProjectFile == null || !UnrealBuildTool.IsEngineInstalled()) { yield return(FileReference.Combine(UnrealBuildTool.EngineDirectory, "Intermediate", "Build", "SourceFileCache.bin")); } if (ProjectFile != null) { yield return(FileReference.Combine(ProjectFile.Directory, "Intermediate", "Build", "SourceFileCache.bin")); } }
/// <summary> /// Enumerates all the locations of action history files for the given target /// </summary> /// <param name="ProjectFile">Project file for the target being built</param> /// <param name="TargetName">Name of the target</param> /// <param name="Platform">Platform being built</param> /// <param name="TargetType">The target type</param> /// <param name="Architecture">The target architecture</param> /// <returns>Dependency cache hierarchy for the given project</returns> public static IEnumerable <FileReference> GetFilesToClean(FileReference ProjectFile, string TargetName, UnrealTargetPlatform Platform, TargetType TargetType, string Architecture) { if (ProjectFile == null || !UnrealBuildTool.IsEngineInstalled()) { yield return(GetEngineLocation(TargetName, Platform, TargetType, Architecture)); } if (ProjectFile != null) { yield return(GetProjectLocation(ProjectFile, TargetName, Platform, Architecture)); } }
private static void SelectXcode(ref string DeveloperDir, bool bVerbose) { string Reason = "hardcoded"; if (DeveloperDir == "xcode-select") { Reason = "xcode-select"; // on the Mac, run xcode-select directly DeveloperDir = Utils.RunLocalProcessAndReturnStdOut("xcode-select", "--print-path"); // make sure we get a full path if (Directory.Exists(DeveloperDir) == false) { throw new BuildException("Selected Xcode ('{0}') doesn't exist, cannot continue.", DeveloperDir); } if (DeveloperDir.EndsWith("/") == false) { // we expect this to end with a slash DeveloperDir += "/"; } } if (bVerbose && !DeveloperDir.StartsWith("/Applications/Xcode.app")) { Log.TraceInformationOnce("Compiling with non-standard Xcode ({0}): {1}", Reason, DeveloperDir); } // Installed engine requires Xcode 11 if (UnrealBuildTool.IsEngineInstalled()) { string XcodeBuilderVersionOutput = Utils.RunLocalProcessAndReturnStdOut("xcodebuild", "-version"); if (XcodeBuilderVersionOutput.Length > 10) { string[] Version = XcodeBuilderVersionOutput.Substring(6, 4).Split('.'); if (Version.Length == 2) { if (int.Parse(Version[0]) < 11) { throw new BuildException("Building for macOS, iOS and tvOS requires Xcode 11 or newer, Xcode " + Version[0] + "." + Version[1] + " detected"); } } else { Log.TraceWarning("Failed to query Xcode version"); } } else { Log.TraceWarning("Failed to query Xcode version"); } } }
/// <summary> /// Determes the path to the game-agnostic saved directory (same as FPaths::GameAgnosticSavedDir()) /// </summary> /// <returns></returns> public static DirectoryReference GetGameAgnosticSavedDir() { if (UnrealBuildTool.IsEngineInstalled()) { return(DirectoryReference.Combine(Utils.GetUserSettingDirectory(), "UnrealEngine", String.Format("{0}.{1}", ReadOnlyBuildVersion.Current.MajorVersion, ReadOnlyBuildVersion.Current.MinorVersion), "Saved")); } else { return(DirectoryReference.Combine(UnrealBuildTool.EngineDirectory, "Saved")); } }
/// <summary> /// Loads JunkManifest.txt file and removes all junk files/folders defined in it. /// </summary> static public void DeleteJunk() { DateTime JunkStartTime = DateTime.UtcNow; if (UnrealBuildTool.IsEngineInstalled() == false) { List <string> JunkManifest = LoadJunkManifest(); DeleteAllJunk(JunkManifest); } if (BuildConfiguration.bPrintPerformanceInfo) { double JunkTime = (DateTime.UtcNow - JunkStartTime).TotalSeconds; Log.TraceInformation("DeleteJunk took " + JunkTime + "s"); } }
/// <summary> /// Updates the intermediate include directory timestamps of all the passed in UObject modules /// </summary> private static void UpdateDirectoryTimestamps(List <UHTModuleInfo> UObjectModules) { foreach (var Module in UObjectModules) { string GeneratedCodeDirectory = Path.GetDirectoryName(Module.GeneratedCPPFilenameBase); var GeneratedCodeDirectoryInfo = new DirectoryInfo(GeneratedCodeDirectory); try { if (GeneratedCodeDirectoryInfo.Exists) { // Don't write anything to the engine directory if we're running an installed build if (UnrealBuildTool.IsEngineInstalled() && new DirectoryReference(Module.ModuleDirectory).IsUnderDirectory(UnrealBuildTool.EngineDirectory)) { continue; } // Touch the include directory since we have technically 'generated' the headers // However, the headers might not be touched at all since that would cause the compiler to recompile everything // We can't alter the directory timestamp directly, because this may throw exceptions when the directory is // open in visual studio or windows explorer, so instead we create a blank file that will change the timestamp for us FileReference TimestampFile = FileReference.Combine(new DirectoryReference(GeneratedCodeDirectoryInfo.FullName), "Timestamp"); if (!GeneratedCodeDirectoryInfo.Exists) { GeneratedCodeDirectoryInfo.Create(); } // Save all of the UObject files to a timestamp file. We'll load these on the next run to see if any new // files with UObject classes were deleted, so that we'll know to run UHT even if the timestamps of all // of the other source files were unchanged { var AllUObjectFiles = new List <string>(); AllUObjectFiles.AddRange(Module.PublicUObjectClassesHeaders.ConvertAll(Item => Item.AbsolutePath)); AllUObjectFiles.AddRange(Module.PublicUObjectHeaders.ConvertAll(Item => Item.AbsolutePath)); AllUObjectFiles.AddRange(Module.PrivateUObjectHeaders.ConvertAll(Item => Item.AbsolutePath)); ResponseFile.Create(TimestampFile, AllUObjectFiles); } } } catch (Exception Exception) { throw new BuildException(Exception, "Couldn't touch header directories: " + Exception.Message); } } }
public override bool GenerateProjectFiles(PlatformProjectGeneratorCollection PlatformProjectGenerators, String[] arguments) { ConfigureProjectFileGeneration(); if (bGeneratingGameProjectFiles || UnrealBuildTool.IsEngineInstalled()) { MasterProjectPath = OnlyGameProject.Directory; MasterProjectName = OnlyGameProject.GetFileNameWithoutExtension(); if (!DirectoryReference.Exists(DirectoryReference.Combine(MasterProjectPath, "Source"))) { if (!DirectoryReference.Exists(DirectoryReference.Combine(MasterProjectPath, "Intermediate", "Source"))) { if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Mac) { MasterProjectPath = UnrealBuildTool.EngineDirectory; GameProjectName = "UE4Game"; } if (!DirectoryReference.Exists(DirectoryReference.Combine(MasterProjectPath, "Source"))) { throw new BuildException("Directory '{0}' is missing 'Source' folder.", MasterProjectPath); } } } IntermediateProjectFilesPath = DirectoryReference.Combine(MasterProjectPath, "Intermediate", "ProjectFiles"); } SetupSupportedPlatformsAndConfigurations(); Log.TraceVerbose("Detected supported platforms: " + SupportedPlatforms); List <FileReference> AllGameProjects = FindGameProjects(); GatherProjects(PlatformProjectGenerators, AllGameProjects); WriteProjectFiles(PlatformProjectGenerators); Log.TraceVerbose("Project generation complete ({0} generated, {1} imported)", GeneratedProjectFiles.Count, OtherProjectFiles.Count); return(true); }
/// <summary> /// Creates a hierarchy of action history stores for a particular target /// </summary> /// <param name="ProjectFile">Project file for the target being built</param> /// <param name="TargetName">Name of the target</param> /// <param name="Platform">Platform being built</param> /// <param name="TargetType">The target type</param> /// <param name="Architecture">The target architecture</param> /// <returns>Dependency cache hierarchy for the given project</returns> public static ActionHistory CreateHierarchy(FileReference ProjectFile, string TargetName, UnrealTargetPlatform Platform, TargetType TargetType, string Architecture) { ActionHistory History = null; if (ProjectFile == null || !UnrealBuildTool.IsEngineInstalled()) { FileReference EngineCacheLocation = GetEngineLocation(TargetName, Platform, TargetType, Architecture); History = FindOrAddHistory(EngineCacheLocation, UnrealBuildTool.EngineDirectory, History); } if (ProjectFile != null) { FileReference ProjectCacheLocation = GetProjectLocation(ProjectFile, TargetName, Platform, Architecture); History = FindOrAddHistory(ProjectCacheLocation, ProjectFile.Directory, History); } return(History); }
/// <summary> /// Creates a cache hierarchy for a particular target /// </summary> /// <param name="ProjectFile">Project file for the target being built</param> /// <returns>Dependency cache hierarchy for the given project</returns> public static SourceFileMetadataCache CreateHierarchy(FileReference ProjectFile) { SourceFileMetadataCache Cache = null; if (ProjectFile == null || !UnrealBuildTool.IsEngineInstalled()) { FileReference EngineCacheLocation = FileReference.Combine(UnrealBuildTool.EngineDirectory, "Intermediate", "Build", "SourceFileCache.bin"); Cache = FindOrAddCache(EngineCacheLocation, UnrealBuildTool.EngineDirectory, Cache); } if (ProjectFile != null) { FileReference ProjectCacheLocation = FileReference.Combine(ProjectFile.Directory, "Intermediate", "Build", "SourceFileCache.bin"); Cache = FindOrAddCache(ProjectCacheLocation, ProjectFile.Directory, Cache); } return(Cache); }
/** Updates the intermediate include directory timestamps of all the passed in UObject modules */ private static void UpdateDirectoryTimestamps(List <UHTModuleInfo> UObjectModules) { foreach (var Module in UObjectModules) { string GeneratedCodeDirectory = Path.GetDirectoryName(Module.GeneratedCPPFilenameBase); var GeneratedCodeDirectoryInfo = new DirectoryInfo(GeneratedCodeDirectory); try { if (GeneratedCodeDirectoryInfo.Exists) { // Don't write anything to the engine directory if we're running an installed build if (UnrealBuildTool.IsEngineInstalled() && Utils.IsFileUnderDirectory(Module.ModuleDirectory, BuildConfiguration.RelativeEnginePath)) { continue; } // Touch the include directory since we have technically 'generated' the headers // However, the headers might not be touched at all since that would cause the compiler to recompile everything // We can't alter the directory timestamp directly, because this may throw exceptions when the directory is // open in visual studio or windows explorer, so instead we create a blank file that will change the timestamp for us string TimestampFile = GeneratedCodeDirectoryInfo.FullName + Path.DirectorySeparatorChar + @"Timestamp"; if (!GeneratedCodeDirectoryInfo.Exists) { GeneratedCodeDirectoryInfo.Create(); } if (File.Exists(TimestampFile)) { File.Delete(TimestampFile); } using (File.Create(TimestampFile)) { } } } catch (Exception Exception) { throw new BuildException(Exception, "Couldn't touch header directories: " + Exception.Message); } } }
private bool ContainsValidConfiguration(Predicate <InstalledPlatformConfiguration> ConfigFilter) { if (UnrealBuildTool.IsEngineInstalled()) { foreach (InstalledPlatformConfiguration PlatformConfiguration in InstalledPlatformConfigurations) { // Check whether filter accepts this configuration and it has required file if (ConfigFilter(PlatformConfiguration) && (string.IsNullOrEmpty(PlatformConfiguration.RequiredFile) || File.Exists(PlatformConfiguration.RequiredFile))) { return(true); } } return(false); } return(true); }
public UHTManifest(UEBuildTarget Target, string InRootLocalPath, string InRootBuildPath, IEnumerable <UHTModuleInfo> ModuleInfo) { IsGameTarget = TargetRules.IsGameType(Target.TargetType); RootLocalPath = InRootLocalPath; RootBuildPath = InRootBuildPath; TargetName = Target.GetTargetName(); Modules = ModuleInfo.Select(Info => new Module { Name = Info.ModuleName, ModuleType = Info.ModuleType, BaseDirectory = Info.ModuleDirectory, IncludeBase = Info.ModuleDirectory, OutputDirectory = Path.GetDirectoryName(Info.GeneratedCPPFilenameBase), ClassesHeaders = Info.PublicUObjectClassesHeaders.Select((Header) => Header.AbsolutePath).ToList(), PublicHeaders = Info.PublicUObjectHeaders.Select((Header) => Header.AbsolutePath).ToList(), PrivateHeaders = Info.PrivateUObjectHeaders.Select((Header) => Header.AbsolutePath).ToList(), PCH = Info.PCH, GeneratedCPPFilenameBase = Info.GeneratedCPPFilenameBase, SaveExportedHeaders = !UnrealBuildTool.IsEngineInstalled() || !new DirectoryReference(Info.ModuleDirectory).IsUnderDirectory(UnrealBuildTool.EngineDirectory), UHTGeneratedCodeVersion = Info.GeneratedCodeVersion, }).ToList(); }
/// <summary> /// Determines whether the given target type is supported /// </summary> /// <param name="TargetType">The target type being built</param> /// <param name="Platform">The platform being built</param> /// <param name="Configuration">The configuration being built</param> /// <param name="ProjectType">The project type required</param> /// <param name="State">State of the given platform support</param> /// <returns>True if the target can be built</returns> public static bool IsValid(TargetType?TargetType, UnrealTargetPlatform?Platform, UnrealTargetConfiguration?Configuration, EProjectType ProjectType, InstalledPlatformState State) { if (!UnrealBuildTool.IsEngineInstalled() || InstalledPlatformConfigurations == null) { return(true); } foreach (InstalledPlatformConfiguration Config in InstalledPlatformConfigurations) { // Check whether this configuration matches all the criteria if (TargetType.HasValue && Config.PlatformType != TargetType.Value) { continue; } if (Platform.HasValue && Config.Platform != Platform.Value) { continue; } if (Configuration.HasValue && Config.Configuration != Configuration.Value) { continue; } if (ProjectType != EProjectType.Any && Config.ProjectType != EProjectType.Any && Config.ProjectType != ProjectType) { continue; } if (State == InstalledPlatformState.Downloaded && !String.IsNullOrEmpty(Config.RequiredFile) && !File.Exists(Config.RequiredFile)) { continue; } // Success! return(true); } return(false); }
/// <summary> /// Loads a makefile from disk /// </summary> /// <param name="MakefilePath">Path to the makefile to load</param> /// <param name="ProjectFile">Path to the project file</param> /// <param name="Platform">Platform for this makefile</param> /// <param name="Arguments">Command line arguments for this target</param> /// <param name="ReasonNotLoaded">If the function returns null, this string will contain the reason why</param> /// <returns>The loaded makefile, or null if it failed for some reason. On failure, the 'ReasonNotLoaded' variable will contain information about why</returns> public static TargetMakefile Load(FileReference MakefilePath, FileReference ProjectFile, UnrealTargetPlatform Platform, string[] Arguments, out string ReasonNotLoaded) { using (Timeline.ScopeEvent("Checking dependent timestamps")) { // Check the directory timestamp on the project files directory. If the user has generated project files more recently than the makefile, then we need to consider the file to be out of date FileInfo MakefileInfo = new FileInfo(MakefilePath.FullName); if (!MakefileInfo.Exists) { // Makefile doesn't even exist, so we won't bother loading it ReasonNotLoaded = "no existing makefile"; return(null); } // Check the build version FileInfo BuildVersionFileInfo = new FileInfo(BuildVersion.GetDefaultFileName().FullName); if (BuildVersionFileInfo.Exists && MakefileInfo.LastWriteTime.CompareTo(BuildVersionFileInfo.LastWriteTime) < 0) { Log.TraceLog("Existing makefile is older than Build.version, ignoring it"); ReasonNotLoaded = "Build.version is newer"; return(null); } // @todo ubtmake: This will only work if the directory timestamp actually changes with every single GPF. Force delete existing files before creating new ones? Eh... really we probably just want to delete + create a file in that folder // -> UPDATE: Seems to work OK right now though on Windows platform, maybe due to GUID changes // @todo ubtmake: Some platforms may not save any files into this folder. We should delete + generate a "touch" file to force the directory timestamp to be updated (or just check the timestamp file itself. We could put it ANYWHERE, actually) // Installed Build doesn't need to check engine projects for outdatedness if (!UnrealBuildTool.IsEngineInstalled()) { if (DirectoryReference.Exists(ProjectFileGenerator.IntermediateProjectFilesPath)) { DateTime EngineProjectFilesLastUpdateTime = new FileInfo(ProjectFileGenerator.ProjectTimestampFile).LastWriteTime; if (MakefileInfo.LastWriteTime.CompareTo(EngineProjectFilesLastUpdateTime) < 0) { // Engine project files are newer than makefile Log.TraceLog("Existing makefile is older than generated engine project files, ignoring it"); ReasonNotLoaded = "project files are newer"; return(null); } } } // Check the game project directory too if (ProjectFile != null) { string ProjectFilename = ProjectFile.FullName; FileInfo ProjectFileInfo = new FileInfo(ProjectFilename); if (!ProjectFileInfo.Exists || MakefileInfo.LastWriteTime.CompareTo(ProjectFileInfo.LastWriteTime) < 0) { // .uproject file is newer than makefile Log.TraceLog("Makefile is older than .uproject file, ignoring it"); ReasonNotLoaded = ".uproject file is newer"; return(null); } DirectoryReference MasterProjectRelativePath = ProjectFile.Directory; string GameIntermediateProjectFilesPath = Path.Combine(MasterProjectRelativePath.FullName, "Intermediate", "ProjectFiles"); if (Directory.Exists(GameIntermediateProjectFilesPath)) { DateTime GameProjectFilesLastUpdateTime = new DirectoryInfo(GameIntermediateProjectFilesPath).LastWriteTime; if (MakefileInfo.LastWriteTime.CompareTo(GameProjectFilesLastUpdateTime) < 0) { // Game project files are newer than makefile Log.TraceLog("Makefile is older than generated game project files, ignoring it"); ReasonNotLoaded = "game project files are newer"; return(null); } } } // Check to see if UnrealBuildTool.exe was compiled more recently than the makefile DateTime UnrealBuildToolTimestamp = new FileInfo(Assembly.GetExecutingAssembly().Location).LastWriteTime; if (MakefileInfo.LastWriteTime.CompareTo(UnrealBuildToolTimestamp) < 0) { // UnrealBuildTool.exe was compiled more recently than the makefile Log.TraceLog("Makefile is older than UnrealBuildTool.exe, ignoring it"); ReasonNotLoaded = "UnrealBuildTool.exe is newer"; return(null); } // Check to see if any BuildConfiguration files have changed since the last build List <XmlConfig.InputFile> InputFiles = XmlConfig.FindInputFiles(); foreach (XmlConfig.InputFile InputFile in InputFiles) { FileInfo InputFileInfo = new FileInfo(InputFile.Location.FullName); if (InputFileInfo.LastWriteTime > MakefileInfo.LastWriteTime) { Log.TraceLog("Makefile is older than BuildConfiguration.xml, ignoring it"); ReasonNotLoaded = "BuildConfiguration.xml is newer"; return(null); } } } TargetMakefile Makefile; using (Timeline.ScopeEvent("Loading makefile")) { try { using (BinaryArchiveReader Reader = new BinaryArchiveReader(MakefilePath)) { int Version = Reader.ReadInt(); if (Version != CurrentVersion) { ReasonNotLoaded = "makefile version does not match"; return(null); } Makefile = new TargetMakefile(Reader); } } catch (Exception Ex) { Log.TraceWarning("Failed to read makefile: {0}", Ex.Message); Log.TraceLog("Exception: {0}", Ex.ToString()); ReasonNotLoaded = "couldn't read existing makefile"; return(null); } } using (Timeline.ScopeEvent("Checking makefile validity")) { // Check if the arguments are different if (!Enumerable.SequenceEqual(Makefile.AdditionalArguments, Arguments)) { ReasonNotLoaded = "command line arguments changed"; return(null); } // Check if ini files are newer. Ini files contain build settings too. DirectoryReference ProjectDirectory = DirectoryReference.FromFile(ProjectFile); foreach (ConfigHierarchyType IniType in (ConfigHierarchyType[])Enum.GetValues(typeof(ConfigHierarchyType))) { foreach (FileReference IniFilename in ConfigHierarchy.EnumerateConfigFileLocations(IniType, ProjectDirectory, Platform)) { FileInfo IniFileInfo = new FileInfo(IniFilename.FullName); if (IniFileInfo.LastWriteTimeUtc > Makefile.CreateTimeUtc) { // Ini files are newer than makefile ReasonNotLoaded = "ini files are newer than makefile"; return(null); } } } // Get the current build metadata from the platform string CurrentExternalMetadata = UEBuildPlatform.GetBuildPlatform(Platform).GetExternalBuildMetadata(ProjectFile); if (String.Compare(CurrentExternalMetadata, Makefile.ExternalMetadata, StringComparison.Ordinal) != 0) { Log.TraceLog("Old metadata:\n", Makefile.ExternalMetadata); Log.TraceLog("New metadata:\n", CurrentExternalMetadata); ReasonNotLoaded = "build metadata has changed"; return(null); } } // The makefile is ok ReasonNotLoaded = null; return(Makefile); }
/// <summary> /// Creates the engine rules assembly /// </summary> /// <param name="bUsePrecompiled">Whether to use a precompiled engine</param> /// <param name="bSkipCompile">Whether to skip compilation for this assembly</param> /// <returns>New rules assembly</returns> public static RulesAssembly CreateEngineRulesAssembly(bool bUsePrecompiled, bool bSkipCompile) { if (EngineRulesAssembly == null) { IReadOnlyList <PluginInfo> IncludedPlugins = Plugins.ReadEnginePlugins(UnrealBuildTool.EngineDirectory); EngineRulesAssembly = CreateEngineOrEnterpriseRulesAssembly(UnrealBuildTool.EngineDirectory, ProjectFileGenerator.EngineProjectFileNameBase, IncludedPlugins, UnrealBuildTool.IsEngineInstalled() || bUsePrecompiled, bSkipCompile, null); } return(EngineRulesAssembly); }
/// <summary> /// 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(). /// </summary> /// <param name="Target">Information about the target</param> /// <returns>True if the target should use the shared build environment</returns> public virtual bool ShouldUseSharedBuildEnvironment(TargetInfo Target) { return(UnrealBuildTool.IsEngineInstalled() || (Target.Type != TargetType.Program && !Target.IsMonolithic)); }
/// <summary> /// Creates the engine rules assembly /// </summary> /// <param name="bUsePrecompiled">Whether to use a precompiled engine</param> /// <param name="bSkipCompile">Whether to skip compilation for this assembly</param> /// <returns>New rules assembly</returns> public static RulesAssembly CreateEngineRulesAssembly(bool bUsePrecompiled, bool bSkipCompile) { if (EngineRulesAssembly == null) { List <PluginInfo> IncludedPlugins = new List <PluginInfo>(); // search for all engine plugins IncludedPlugins.AddRange(Plugins.ReadEnginePlugins(UnrealBuildTool.EngineDirectory)); RulesScope EngineScope = new RulesScope("Engine", null); EngineRulesAssembly = CreateEngineOrEnterpriseRulesAssembly(EngineScope, UnrealBuildTool.GetAllEngineDirectories().ToList(), ProjectFileGenerator.EngineProjectFileNameBase, IncludedPlugins, UnrealBuildTool.IsEngineInstalled() || bUsePrecompiled, bSkipCompile, null); } return(EngineRulesAssembly); }
/// <summary> /// Construct an instance of the given target rules /// </summary> /// <param name="TypeName">Type name of the target rules</param> /// <param name="TargetInfo">Target configuration information to pass to the constructor</param> /// <param name="Arguments">Command line arguments for this target</param> /// <returns>Instance of the corresponding TargetRules</returns> protected TargetRules CreateTargetRulesInstance(string TypeName, TargetInfo TargetInfo, string[] Arguments) { // The build module must define a type named '<TargetName>Target' that derives from our 'TargetRules' type. Type RulesType = CompiledAssembly.GetType(TypeName); if (RulesType == 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.", TypeName); } // Create an instance of the module's rules object, and set some defaults before calling the constructor. TargetRules Rules = (TargetRules)FormatterServices.GetUninitializedObject(RulesType); Rules.bUseBackwardsCompatibleDefaults = bUseBackwardsCompatibleDefaults; // Find the constructor ConstructorInfo Constructor = RulesType.GetConstructor(new Type[] { typeof(TargetInfo) }); if (Constructor == null) { throw new BuildException("No constructor found on {0} which takes an argument of type TargetInfo.", RulesType.Name); } // Invoke the regular constructor try { Constructor.Invoke(Rules, new object[] { TargetInfo }); } 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}", TypeName, Path.GetFileNameWithoutExtension(CompiledAssembly.Location), Ex.ToString()); } // Set the default overriddes for the configured target type Rules.SetOverridesForTargetType(); // Parse any additional command-line arguments. These override default settings specified in config files or the .target.cs files. if (Arguments != null) { foreach (object ConfigurableObject in Rules.GetConfigurableObjects()) { CommandLine.ParseArguments(Arguments, ConfigurableObject); } } // Set the final value for the link type in the target rules if (Rules.LinkType == TargetLinkType.Default) { throw new BuildException("TargetRules.LinkType should be inferred from TargetType"); } // Set the default value for whether to use the shared build environment if (Rules.BuildEnvironment == TargetBuildEnvironment.Default) { if (Rules.Type == TargetType.Program && TargetInfo.ProjectFile != null && TargetNameToTargetFile[Rules.Name].IsUnderDirectory(TargetInfo.ProjectFile.Directory)) { Rules.BuildEnvironment = TargetBuildEnvironment.Unique; } else if (UnrealBuildTool.IsEngineInstalled() || Rules.LinkType != TargetLinkType.Monolithic) { Rules.BuildEnvironment = TargetBuildEnvironment.Shared; } else { Rules.BuildEnvironment = TargetBuildEnvironment.Unique; } } // Lean and mean means no Editor and other frills. if (Rules.bCompileLeanAndMeanUE) { Rules.bBuildEditor = false; Rules.bBuildDeveloperTools = Rules.bBuildDeveloperTools ?? false; Rules.bCompileSimplygon = false; Rules.bCompileSimplygonSSF = false; Rules.bCompileSpeedTree = false; } // if the bBuildDeveloperTools switch hasn't been defined, default it to the bCompileAgainstEngine switch. if (!Rules.bBuildDeveloperTools.HasValue) { Rules.bBuildDeveloperTools = Rules.bCompileAgainstEngine; } // Automatically include CoreUObject if (Rules.bCompileAgainstEngine) { Rules.bCompileAgainstCoreUObject = true; } // Must have editor only data if building the editor. if (Rules.bBuildEditor) { Rules.bBuildWithEditorOnlyData = true; } // Apply the override to force debug info to be enabled if (Rules.bForceDebugInfo) { Rules.bDisableDebugInfo = false; Rules.bOmitPCDebugInfoInDevelopment = false; } // Setup the malloc profiler if (Rules.bUseMallocProfiler) { Rules.bOmitFramePointers = false; Rules.GlobalDefinitions.Add("USE_MALLOC_PROFILER=1"); } // Set a macro if we allow using generated inis if (!Rules.bAllowGeneratedIniWhenCooked) { Rules.GlobalDefinitions.Add("DISABLE_GENERATED_INI_WHEN_COOKED=1"); } return(Rules); }
/// <summary> /// Builds and runs the header tool and touches the header directories. /// Performs any early outs if headers need no changes, given the UObject modules, tool path, game name, and configuration /// </summary> public static bool ExecuteHeaderToolIfNecessary(UEToolChain ToolChain, UEBuildTarget Target, CPPEnvironment GlobalCompileEnvironment, List <UHTModuleInfo> UObjectModules, FileReference ModuleInfoFileName, ref ECompilationResult UHTResult) { if (ProgressWriter.bWriteMarkup) { Log.WriteLine(LogEventType.Console, "@progress push 5%"); } using (ProgressWriter Progress = new ProgressWriter("Generating code...", false)) { // We never want to try to execute the header tool when we're already trying to build it! var bIsBuildingUHT = Target.GetTargetName().Equals("UnrealHeaderTool", StringComparison.InvariantCultureIgnoreCase); var RootLocalPath = Path.GetFullPath(ProjectFileGenerator.RootRelativePath); // check if UHT is out of date DateTime HeaderToolTimestamp = DateTime.MaxValue; bool bHaveHeaderTool = !bIsBuildingUHT && GetHeaderToolTimestamp(out HeaderToolTimestamp); // ensure the headers are up to date bool bUHTNeedsToRun = (UEBuildConfiguration.bForceHeaderGeneration == true || !bHaveHeaderTool || AreGeneratedCodeFilesOutOfDate(UObjectModules, HeaderToolTimestamp)); if (bUHTNeedsToRun || UnrealBuildTool.IsGatheringBuild) { // Since code files are definitely out of date, we'll now finish computing information about the UObject modules for UHT. We // want to save this work until we know that UHT actually needs to be run to speed up best-case iteration times. if (UnrealBuildTool.IsGatheringBuild) // In assembler-only mode, PCH info is loaded from our UBTMakefile! { foreach (var UHTModuleInfo in UObjectModules) { // Only cache the PCH name if we don't already have one. When running in 'gather only' mode, this will have already been cached if (string.IsNullOrEmpty(UHTModuleInfo.PCH)) { UHTModuleInfo.PCH = ""; // We need to figure out which PCH header this module is including, so that UHT can inject an include statement for it into any .cpp files it is synthesizing var DependencyModuleCPP = (UEBuildModuleCPP)Target.GetModuleByName(UHTModuleInfo.ModuleName); var ModuleCompileEnvironment = DependencyModuleCPP.CreateModuleCompileEnvironment(GlobalCompileEnvironment); DependencyModuleCPP.CachePCHUsageForModuleSourceFiles(ModuleCompileEnvironment); if (DependencyModuleCPP.ProcessedDependencies.UniquePCHHeaderFile != null) { UHTModuleInfo.PCH = DependencyModuleCPP.ProcessedDependencies.UniquePCHHeaderFile.AbsolutePath; } } } } } // @todo ubtmake: Optimization: Ideally we could avoid having to generate this data in the case where UHT doesn't even need to run! Can't we use the existing copy? (see below use of Manifest) UHTManifest Manifest = new UHTManifest(Target, RootLocalPath, ToolChain.ConvertPath(RootLocalPath + '\\'), UObjectModules); if (!bIsBuildingUHT && bUHTNeedsToRun) { // Always build UnrealHeaderTool if header regeneration is required, unless we're running within a Rocket ecosystem or hot-reloading if (UnrealBuildTool.RunningRocket() == false && UEBuildConfiguration.bDoNotBuildUHT == false && UEBuildConfiguration.bHotReloadFromIDE == false && !(bHaveHeaderTool && !UnrealBuildTool.IsGatheringBuild && UnrealBuildTool.IsAssemblingBuild)) // If running in "assembler only" mode, we assume UHT is already up to date for much faster iteration! { // If it is out of date or not there it will be built. // If it is there and up to date, it will add 0.8 seconds to the build time. Log.TraceInformation("Building UnrealHeaderTool..."); var UBTArguments = new StringBuilder(); UBTArguments.Append("UnrealHeaderTool"); // Which desktop platform do we need to compile UHT for? UBTArguments.Append(" " + BuildHostPlatform.Current.Platform.ToString()); // NOTE: We force Development configuration for UHT so that it runs quickly, even when compiling debug UBTArguments.Append(" " + UnrealTargetConfiguration.Development.ToString()); // NOTE: We disable mutex when launching UBT from within UBT to compile UHT UBTArguments.Append(" -NoMutex"); if (UnrealBuildTool.CommandLineContains("-noxge")) { UBTArguments.Append(" -noxge"); } // Propagate command-line option if (UnrealBuildTool.CommandLineContains("-2015")) { UBTArguments.Append(" -2015"); } if (UnrealBuildTool.CommandLineContains("-2013")) { UBTArguments.Append(" -2013"); } // Add UHT plugins to UBT command line as external plugins if (Target.UnrealHeaderToolPlugins != null && Target.UnrealHeaderToolPlugins.Count > 0) { foreach (PluginInfo Plugin in Target.UnrealHeaderToolPlugins) { UBTArguments.Append(" -PLUGIN \"" + Plugin.File + "\""); } } if (RunExternalExecutable(UnrealBuildTool.GetUBTPath(), UBTArguments.ToString()) != 0) { return(false); } } Progress.Write(1, 3); var ActualTargetName = String.IsNullOrEmpty(Target.GetTargetName()) ? "UE4" : Target.GetTargetName(); Log.TraceInformation("Parsing headers for {0}", ActualTargetName); string HeaderToolPath = GetHeaderToolPath(); if (!File.Exists(HeaderToolPath)) { throw new BuildException("Unable to generate headers because UnrealHeaderTool binary was not found ({0}).", Path.GetFullPath(HeaderToolPath)); } // Disable extensions when serializing to remove the $type fields Directory.CreateDirectory(ModuleInfoFileName.Directory.FullName); System.IO.File.WriteAllText(ModuleInfoFileName.FullName, fastJSON.JSON.Instance.ToJSON(Manifest, new fastJSON.JSONParameters { UseExtensions = false })); string CmdLine = (Target.ProjectFile != null) ? "\"" + Target.ProjectFile.FullName + "\"" : Target.GetTargetName(); CmdLine += " \"" + ModuleInfoFileName + "\" -LogCmds=\"loginit warning, logexit warning, logdatabase error\" -Unattended -WarningsAsErrors"; if (UnrealBuildTool.IsEngineInstalled()) { CmdLine += " -installed"; } if (UEBuildConfiguration.bFailIfGeneratedCodeChanges) { CmdLine += " -FailIfGeneratedCodeChanges"; } if (!bInvalidateUHTMakefile && BuildConfiguration.bUseUHTMakefiles) { CmdLine += " -UseMakefiles"; } if (!UEBuildConfiguration.bCompileAgainstEngine) { CmdLine += " -NoEnginePlugins"; } Log.TraceInformation(" Running UnrealHeaderTool {0}", CmdLine); Stopwatch s = new Stopwatch(); s.Start(); UHTResult = (ECompilationResult)RunExternalExecutable(ExternalExecution.GetHeaderToolPath(), CmdLine); s.Stop(); if (UHTResult != ECompilationResult.Succeeded) { // On Linux and Mac, the shell will return 128+signal number exit codes if UHT gets a signal (e.g. crashes or is interrupted) if ((BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Linux || BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Mac) && (int)(UHTResult) >= 128 ) { // SIGINT is 2, so 128 + SIGINT is 130 UHTResult = ((int)(UHTResult) == 130) ? ECompilationResult.Canceled : ECompilationResult.CrashOrAssert; } Log.TraceInformation("Error: Failed to generate code for {0} - error code: {2} ({1})", ActualTargetName, (int)UHTResult, UHTResult.ToString()); return(false); } Log.TraceInformation("Reflection code generated for {0} in {1} seconds", ActualTargetName, s.Elapsed.TotalSeconds); if (BuildConfiguration.bPrintPerformanceInfo) { Log.TraceInformation("UnrealHeaderTool took {1}", ActualTargetName, (double)s.ElapsedMilliseconds / 1000.0); } // Now that UHT has successfully finished generating code, we need to update all cached FileItems in case their last write time has changed. // Otherwise UBT might not detect changes UHT made. DateTime StartTime = DateTime.UtcNow; FileItem.ResetInfos(); double ResetDuration = (DateTime.UtcNow - StartTime).TotalSeconds; Log.TraceVerbose("FileItem.ResetInfos() duration: {0}s", ResetDuration); } else { Log.TraceVerbose("Generated code is up to date."); } Progress.Write(2, 3); // There will never be generated code if we're building UHT, so this should never be called. if (!bIsBuildingUHT) { // Allow generated code to be sync'd to remote machines if needed. This needs to be done even if UHT did not run because // generated headers include other generated headers using absolute paths which in case of building remotely are already // the remote machine absolute paths. Because of that parsing headers will not result in finding all includes properly. // @todo ubtmake: Need to figure out what this does in the assembler case, and whether we need to run it ToolChain.PostCodeGeneration(Manifest); } // touch the directories UpdateDirectoryTimestamps(UObjectModules); Progress.Write(3, 3); } if (ProgressWriter.bWriteMarkup) { Log.WriteLine(LogEventType.Console, "@progress pop"); } return(true); }
/// <summary> /// Initialize the config system with the given types /// </summary> /// <param name="OverrideCacheFile">Force use of the cached XML config without checking if it's valid (useful for remote builds)</param> public static void ReadConfigFiles(FileReference OverrideCacheFile) { // Find all the configurable types List <Type> ConfigTypes = FindConfigurableTypes(); // Update the cache if necessary if (OverrideCacheFile != null) { // Set the cache file to the overriden value CacheFile = OverrideCacheFile; // Never rebuild the cache; just try to load it. if (!XmlConfigData.TryRead(CacheFile, ConfigTypes, out Values)) { throw new BuildException("Unable to load XML config cache ({0})", CacheFile); } } else { // Get the default cache file CacheFile = FileReference.Combine(UnrealBuildTool.EngineDirectory, "Intermediate", "Build", "XmlConfigCache.bin"); if (UnrealBuildTool.IsEngineInstalled()) { DirectoryReference UserSettingsDir = Utils.GetUserSettingDirectory(); if (UserSettingsDir != null) { CacheFile = FileReference.Combine(UserSettingsDir, "UnrealEngine", String.Format("XmlConfigCache-{0}.bin", UnrealBuildTool.RootDirectory.FullName.Replace(":", "").Replace(Path.DirectorySeparatorChar, '+'))); } } // Find all the input files FileReference[] InputFiles = FindInputFiles().Select(x => x.Location).ToArray(); // Get the path to the schema FileReference SchemaFile = GetSchemaLocation(); // Try to read the existing cache from disk XmlConfigData CachedValues; if (IsCacheUpToDate(CacheFile, InputFiles) && FileReference.Exists(SchemaFile)) { if (XmlConfigData.TryRead(CacheFile, ConfigTypes, out CachedValues) && Enumerable.SequenceEqual(InputFiles, CachedValues.InputFiles)) { Values = CachedValues; } } // If that failed, regenerate it if (Values == null) { // Find all the configurable fields from the given types Dictionary <string, Dictionary <string, FieldInfo> > CategoryToFields = new Dictionary <string, Dictionary <string, FieldInfo> >(); FindConfigurableFields(ConfigTypes, CategoryToFields); // Create a schema for the config files XmlSchema Schema = CreateSchema(CategoryToFields); if (!UnrealBuildTool.IsEngineInstalled()) { WriteSchema(Schema, SchemaFile); } // Read all the XML files and validate them against the schema Dictionary <Type, Dictionary <FieldInfo, object> > TypeToValues = new Dictionary <Type, Dictionary <FieldInfo, object> >(); foreach (FileReference InputFile in InputFiles) { if (!TryReadFile(InputFile, CategoryToFields, TypeToValues, Schema)) { throw new BuildException("Failed to properly read XML file : {0}", InputFile.FullName); } } // Make sure the cache directory exists DirectoryReference.CreateDirectory(CacheFile.Directory); // Create the new cache Values = new XmlConfigData(InputFiles, TypeToValues.ToDictionary(x => x.Key, x => x.Value.ToArray())); Values.Write(CacheFile); } } // Apply all the static field values foreach (KeyValuePair <Type, KeyValuePair <FieldInfo, object>[]> TypeValuesPair in Values.TypeToValues) { foreach (KeyValuePair <FieldInfo, object> FieldValuePair in TypeValuesPair.Value) { if (FieldValuePair.Key.IsStatic) { object Value = InstanceValue(FieldValuePair.Value, FieldValuePair.Key.FieldType); FieldValuePair.Key.SetValue(null, Value); } } } }
/// <summary> /// Checks the class header files and determines if generated UObject code files are out of date in comparison. /// </summary> /// <param name="UObjectModules">Modules that we generate headers for</param> /// <returns> True if the code files are out of date</returns> private static bool AreGeneratedCodeFilesOutOfDate(List <UHTModuleInfo> UObjectModules, DateTime HeaderToolTimestamp) { // Get CoreUObject.generated.cpp timestamp. If the source files are older than the CoreUObject generated code, we'll // need to regenerate code for the module DateTime?CoreGeneratedTimestamp = null; { // Find the CoreUObject module foreach (var Module in UObjectModules) { if (Module.ModuleName.Equals("CoreUObject", StringComparison.InvariantCultureIgnoreCase)) { CoreGeneratedTimestamp = GetCoreGeneratedTimestamp(Module.ModuleName, Path.GetDirectoryName(Module.GeneratedCPPFilenameBase)); break; } } if (CoreGeneratedTimestamp == null) { throw new BuildException("Could not find CoreUObject in list of all UObjectModules"); } } foreach (var Module in UObjectModules) { // If the engine is installed, skip skip checking timestamps for modules that are under the engine directory if (UnrealBuildTool.IsEngineInstalled() && new DirectoryReference(Module.ModuleDirectory).IsUnderDirectory(UnrealBuildTool.EngineDirectory)) { continue; } // Make sure we have an existing folder for generated code. If not, then we definitely need to generate code! var GeneratedCodeDirectory = Path.GetDirectoryName(Module.GeneratedCPPFilenameBase); var TestDirectory = (FileSystemInfo) new DirectoryInfo(GeneratedCodeDirectory); if (!TestDirectory.Exists) { // Generated code directory is missing entirely! Log.TraceVerbose("UnrealHeaderTool needs to run because no generated code directory was found for module {0}", Module.ModuleName); return(true); } // Grab our special "Timestamp" file that we saved after the last set of headers were generated. This file // actually contains the list of source files which contained UObjects, so that we can compare to see if any // UObject source files were deleted (or no longer contain UObjects), which means we need to run UHT even // if no other source files were outdated string TimestampFile = Path.Combine(GeneratedCodeDirectory, @"Timestamp"); var SavedTimestampFileInfo = (FileSystemInfo) new FileInfo(TimestampFile); if (!SavedTimestampFileInfo.Exists) { // Timestamp file was missing (possibly deleted/cleaned), so headers are out of date Log.TraceVerbose("UnrealHeaderTool needs to run because UHT Timestamp file did not exist for module {0}", Module.ModuleName); return(true); } // Make sure the last UHT run completed after UnrealHeaderTool.exe was compiled last, and after the CoreUObject headers were touched last. var SavedTimestamp = SavedTimestampFileInfo.LastWriteTime; if (HeaderToolTimestamp > SavedTimestamp || CoreGeneratedTimestamp > SavedTimestamp) { // Generated code is older than UnrealHeaderTool.exe or CoreUObject headers. Out of date! Log.TraceVerbose("UnrealHeaderTool needs to run because UnrealHeaderTool.exe or CoreUObject headers are newer than SavedTimestamp for module {0}", Module.ModuleName); return(true); } // Iterate over our UObjects headers and figure out if any of them have changed var AllUObjectHeaders = new List <FileItem>(); AllUObjectHeaders.AddRange(Module.PublicUObjectClassesHeaders); AllUObjectHeaders.AddRange(Module.PublicUObjectHeaders); AllUObjectHeaders.AddRange(Module.PrivateUObjectHeaders); // Load up the old timestamp file and check to see if anything has changed { var UObjectFilesFromPreviousRun = File.ReadAllLines(TimestampFile); if (AllUObjectHeaders.Count != UObjectFilesFromPreviousRun.Length) { Log.TraceVerbose("UnrealHeaderTool needs to run because there are a different number of UObject source files in module {0}", Module.ModuleName); return(true); } for (int FileIndex = 0; FileIndex < AllUObjectHeaders.Count; ++FileIndex) { if (!UObjectFilesFromPreviousRun[FileIndex].Equals(AllUObjectHeaders[FileIndex].AbsolutePath, StringComparison.InvariantCultureIgnoreCase)) { Log.TraceVerbose("UnrealHeaderTool needs to run because the set of UObject source files in module {0} has changed", Module.ModuleName); return(true); } } } foreach (var HeaderFile in AllUObjectHeaders) { var HeaderFileTimestamp = HeaderFile.Info.LastWriteTime; // Has the source header changed since we last generated headers successfully? if (HeaderFileTimestamp > SavedTimestamp) { Log.TraceVerbose("UnrealHeaderTool needs to run because SavedTimestamp is older than HeaderFileTimestamp ({0}) for module {1}", HeaderFile.AbsolutePath, Module.ModuleName); return(true); } // When we're running in assembler mode, outdatedness cannot be inferred by checking the directory timestamp // of the source headers. We don't care if source files were added or removed in this mode, because we're only // able to process the known UObject headers that are in the Makefile. If UObject header files are added/removed, // we expect the user to re-run GenerateProjectFiles which will force UBTMakefile outdatedness. // @todo ubtmake: Possibly, we should never be doing this check these days. if (UnrealBuildTool.IsGatheringBuild || !UnrealBuildTool.IsAssemblingBuild) { // Also check the timestamp on the directory the source file is in. If the directory timestamp has // changed, new source files may have been added or deleted. We don't know whether the new/deleted // files were actually UObject headers, but because we don't know all of the files we processed // in the previous run, we need to assume our generated code is out of date if the directory timestamp // is newer. var HeaderDirectoryTimestamp = new DirectoryInfo(Path.GetDirectoryName(HeaderFile.AbsolutePath)).LastWriteTime; if (HeaderDirectoryTimestamp > SavedTimestamp) { Log.TraceVerbose("UnrealHeaderTool needs to run because the directory containing an existing header ({0}) has changed, and headers may have been added to or deleted from module {1}", HeaderFile.AbsolutePath, Module.ModuleName); return(true); } } } } return(false); }
/// <summary> /// Loads a UBTMakefile from disk /// </summary> /// <param name="MakefilePath">Path to the makefile to load</param> /// <param name="ProjectFile">Path to the project file</param> /// <param name="ReasonNotLoaded">If the function returns null, this string will contain the reason why</param> /// <param name="WorkingSet">Interface to query which source files are in the working set</param> /// <returns>The loaded makefile, or null if it failed for some reason. On failure, the 'ReasonNotLoaded' variable will contain information about why</returns> public static UBTMakefile LoadUBTMakefile(FileReference MakefilePath, FileReference ProjectFile, ISourceFileWorkingSet WorkingSet, out string ReasonNotLoaded) { // Check the directory timestamp on the project files directory. If the user has generated project files more // recently than the UBTMakefile, then we need to consider the file to be out of date FileInfo UBTMakefileInfo = new FileInfo(MakefilePath.FullName); if (!UBTMakefileInfo.Exists) { // UBTMakefile doesn't even exist, so we won't bother loading it ReasonNotLoaded = "no existing makefile"; return(null); } // Check the build version FileInfo BuildVersionFileInfo = new FileInfo(BuildVersion.GetDefaultFileName().FullName); if (BuildVersionFileInfo.Exists && UBTMakefileInfo.LastWriteTime.CompareTo(BuildVersionFileInfo.LastWriteTime) < 0) { Log.TraceVerbose("Existing makefile is older than Build.version, ignoring it"); ReasonNotLoaded = "Build.version is newer"; return(null); } // @todo ubtmake: This will only work if the directory timestamp actually changes with every single GPF. Force delete existing files before creating new ones? Eh... really we probably just want to delete + create a file in that folder // -> UPDATE: Seems to work OK right now though on Windows platform, maybe due to GUID changes // @todo ubtmake: Some platforms may not save any files into this folder. We should delete + generate a "touch" file to force the directory timestamp to be updated (or just check the timestamp file itself. We could put it ANYWHERE, actually) // Installed Build doesn't need to check engine projects for outdatedness if (!UnrealBuildTool.IsEngineInstalled()) { if (DirectoryReference.Exists(ProjectFileGenerator.IntermediateProjectFilesPath)) { DateTime EngineProjectFilesLastUpdateTime = new FileInfo(ProjectFileGenerator.ProjectTimestampFile).LastWriteTime; if (UBTMakefileInfo.LastWriteTime.CompareTo(EngineProjectFilesLastUpdateTime) < 0) { // Engine project files are newer than UBTMakefile Log.TraceVerbose("Existing makefile is older than generated engine project files, ignoring it"); ReasonNotLoaded = "project files are newer"; return(null); } } } // Check the game project directory too if (ProjectFile != null) { string ProjectFilename = ProjectFile.FullName; FileInfo ProjectFileInfo = new FileInfo(ProjectFilename); if (!ProjectFileInfo.Exists || UBTMakefileInfo.LastWriteTime.CompareTo(ProjectFileInfo.LastWriteTime) < 0) { // .uproject file is newer than UBTMakefile Log.TraceVerbose("Makefile is older than .uproject file, ignoring it"); ReasonNotLoaded = ".uproject file is newer"; return(null); } DirectoryReference MasterProjectRelativePath = ProjectFile.Directory; string GameIntermediateProjectFilesPath = Path.Combine(MasterProjectRelativePath.FullName, "Intermediate", "ProjectFiles"); if (Directory.Exists(GameIntermediateProjectFilesPath)) { DateTime GameProjectFilesLastUpdateTime = new DirectoryInfo(GameIntermediateProjectFilesPath).LastWriteTime; if (UBTMakefileInfo.LastWriteTime.CompareTo(GameProjectFilesLastUpdateTime) < 0) { // Game project files are newer than UBTMakefile Log.TraceVerbose("Makefile is older than generated game project files, ignoring it"); ReasonNotLoaded = "game project files are newer"; return(null); } } } // Check to see if UnrealBuildTool.exe was compiled more recently than the UBTMakefile DateTime UnrealBuildToolTimestamp = new FileInfo(Assembly.GetExecutingAssembly().Location).LastWriteTime; if (UBTMakefileInfo.LastWriteTime.CompareTo(UnrealBuildToolTimestamp) < 0) { // UnrealBuildTool.exe was compiled more recently than the UBTMakefile Log.TraceVerbose("Makefile is older than UnrealBuildTool.exe, ignoring it"); ReasonNotLoaded = "UnrealBuildTool.exe is newer"; return(null); } // Check to see if any BuildConfiguration files have changed since the last build List <XmlConfig.InputFile> InputFiles = XmlConfig.FindInputFiles(); foreach (XmlConfig.InputFile InputFile in InputFiles) { FileInfo InputFileInfo = new FileInfo(InputFile.Location.FullName); if (InputFileInfo.LastWriteTime > UBTMakefileInfo.LastWriteTime) { Log.TraceVerbose("Makefile is older than BuildConfiguration.xml, ignoring it"); ReasonNotLoaded = "BuildConfiguration.xml is newer"; return(null); } } UBTMakefile LoadedUBTMakefile = null; try { DateTime LoadUBTMakefileStartTime = DateTime.UtcNow; using (FileStream Stream = new FileStream(UBTMakefileInfo.FullName, FileMode.Open, FileAccess.Read)) { BinaryFormatter Formatter = new BinaryFormatter(); LoadedUBTMakefile = Formatter.Deserialize(Stream) as UBTMakefile; } if (UnrealBuildTool.bPrintPerformanceInfo) { double LoadUBTMakefileTime = (DateTime.UtcNow - LoadUBTMakefileStartTime).TotalSeconds; Log.TraceInformation("LoadUBTMakefile took " + LoadUBTMakefileTime + "s"); } } catch (Exception Ex) { Log.TraceWarning("Failed to read makefile: {0}", Ex.Message); ReasonNotLoaded = "couldn't read existing makefile"; return(null); } if (!LoadedUBTMakefile.IsValidMakefile()) { Log.TraceWarning("Loaded makefile appears to have invalid contents, ignoring it ({0})", UBTMakefileInfo.FullName); ReasonNotLoaded = "existing makefile appears to be invalid"; return(null); } // Check if any of the target's Build.cs files are newer than the makefile foreach (UEBuildTarget Target in LoadedUBTMakefile.Targets) { string TargetCsFilename = Target.TargetRulesFile.FullName; if (TargetCsFilename != null) { FileInfo TargetCsFile = new FileInfo(TargetCsFilename); bool bTargetCsFileExists = TargetCsFile.Exists; if (!bTargetCsFileExists || TargetCsFile.LastWriteTime > UBTMakefileInfo.LastWriteTime) { Log.TraceVerbose("{0} has been {1} since makefile was built, ignoring it ({2})", TargetCsFilename, bTargetCsFileExists ? "changed" : "deleted", UBTMakefileInfo.FullName); ReasonNotLoaded = string.Format("changes to target files"); return(null); } } IEnumerable <string> BuildCsFilenames = Target.GetAllModuleBuildCsFilenames(); foreach (string BuildCsFilename in BuildCsFilenames) { if (BuildCsFilename != null) { FileInfo BuildCsFile = new FileInfo(BuildCsFilename); bool bBuildCsFileExists = BuildCsFile.Exists; if (!bBuildCsFileExists || BuildCsFile.LastWriteTime > UBTMakefileInfo.LastWriteTime) { Log.TraceVerbose("{0} has been {1} since makefile was built, ignoring it ({2})", BuildCsFilename, bBuildCsFileExists ? "changed" : "deleted", UBTMakefileInfo.FullName); ReasonNotLoaded = string.Format("changes to module files"); return(null); } } } foreach (FlatModuleCsDataType FlatCsModuleData in Target.FlatModuleCsData.Values) { if (FlatCsModuleData.BuildCsFilename != null && FlatCsModuleData.ExternalDependencies.Count > 0) { string BaseDir = Path.GetDirectoryName(FlatCsModuleData.BuildCsFilename); foreach (string ExternalDependency in FlatCsModuleData.ExternalDependencies) { FileInfo DependencyFile = new FileInfo(Path.Combine(BaseDir, ExternalDependency)); bool bDependencyFileExists = DependencyFile.Exists; if (!bDependencyFileExists || DependencyFile.LastWriteTime > UBTMakefileInfo.LastWriteTime) { Log.TraceVerbose("{0} has been {1} since makefile was built, ignoring it ({2})", DependencyFile.FullName, bDependencyFileExists ? "changed" : "deleted", UBTMakefileInfo.FullName); ReasonNotLoaded = string.Format("changes to external dependency"); return(null); } } } } } // We do a check to see if any modules' headers have changed which have // acquired or lost UHT types. If so, which should be rare, // we'll just invalidate the entire makefile and force it to be rebuilt. foreach (UEBuildTarget Target in LoadedUBTMakefile.Targets) { // Get all H files in processed modules newer than the makefile itself HashSet <string> HFilesNewerThanMakefile = new HashSet <string>( Target.FlatModuleCsData .SelectMany(x => x.Value.ModuleSourceFolder != null ? Directory.EnumerateFiles(x.Value.ModuleSourceFolder.FullName, "*.h", SearchOption.AllDirectories) : Enumerable.Empty <string>()) .Where(y => Directory.GetLastWriteTimeUtc(y) > UBTMakefileInfo.LastWriteTimeUtc) .OrderBy(z => z).Distinct() ); // Get all H files in all modules processed in the last makefile build HashSet <string> AllUHTHeaders = new HashSet <string>(Target.FlatModuleCsData.Select(x => x.Value).SelectMany(x => x.UHTHeaderNames)); // Check whether any headers have been deleted. If they have, we need to regenerate the makefile since the module might now be empty. If we don't, // and the file has been moved to a different module, we may include stale generated headers. foreach (string FileName in AllUHTHeaders) { if (!File.Exists(FileName)) { Log.TraceVerbose("File processed by UHT was deleted ({0}); invalidating makefile", FileName); ReasonNotLoaded = string.Format("UHT file was deleted"); return(null); } } // Makefile is invalid if: // * There are any newer files which contain no UHT data, but were previously in the makefile // * There are any newer files contain data which needs processing by UHT, but weren't not previously in the makefile foreach (string Filename in HFilesNewerThanMakefile) { bool bContainsUHTData = CPPHeaders.DoesFileContainUObjects(Filename); bool bWasProcessed = AllUHTHeaders.Contains(Filename); if (bContainsUHTData != bWasProcessed) { Log.TraceVerbose("{0} {1} contain UHT types and now {2} , ignoring it ({3})", Filename, bWasProcessed ? "used to" : "didn't", bWasProcessed ? "doesn't" : "does", UBTMakefileInfo.FullName); ReasonNotLoaded = string.Format("new files with reflected types"); return(null); } } } // If adaptive unity build is enabled, do a check to see if there are any source files that became part of the // working set since the Makefile was created (or, source files were removed from the working set.) If anything // changed, then we'll force a new Makefile to be created so that we have fresh unity build blobs. We always // want to make sure that source files in the working set are excluded from those unity blobs (for fastest possible // iteration times.) if (LoadedUBTMakefile.bUseAdaptiveUnityBuild) { // Check if any source files in the working set no longer belong in it foreach (FileItem SourceFile in LoadedUBTMakefile.SourceFileWorkingSet) { if (!WorkingSet.Contains(SourceFile.Location) && File.GetLastWriteTimeUtc(SourceFile.AbsolutePath) > UBTMakefileInfo.LastWriteTimeUtc) { Log.TraceVerbose("{0} was part of source working set and now is not; invalidating makefile ({1})", SourceFile.AbsolutePath, UBTMakefileInfo.FullName); ReasonNotLoaded = string.Format("working set of source files changed"); return(null); } } // Check if any source files that are eligible for being in the working set have been modified foreach (FileItem SourceFile in LoadedUBTMakefile.CandidateSourceFilesForWorkingSet) { if (WorkingSet.Contains(SourceFile.Location) && File.GetLastWriteTimeUtc(SourceFile.AbsolutePath) > UBTMakefileInfo.LastWriteTimeUtc) { Log.TraceVerbose("{0} was part of source working set and now is not; invalidating makefile ({1})", SourceFile.AbsolutePath, UBTMakefileInfo.FullName); ReasonNotLoaded = string.Format("working set of source files changed"); return(null); } } } ReasonNotLoaded = null; return(LoadedUBTMakefile); }
/// <summary> /// Main entry point /// </summary> /// <param name="Arguments">Command-line arguments</param> /// <returns>One of the values of ECompilationResult</returns> public override int Execute(CommandLineArguments Arguments) { Arguments.ApplyTo(this); // Initialize the log system, buffering the output until we can create the log file StartupTraceListener StartupListener = new StartupTraceListener(); Trace.Listeners.Add(StartupListener); // Write the command line Log.TraceLog("Command line: {0}", Environment.CommandLine); // Grab the environment. UnrealBuildTool.InitialEnvironment = Environment.GetEnvironmentVariables(); if (UnrealBuildTool.InitialEnvironment.Count < 1) { throw new BuildException("Environment could not be read"); } // Read the XML configuration files XmlConfig.ApplyTo(this); // Fixup the log path if it wasn't overridden by a config file if (BaseLogFileName == null) { BaseLogFileName = FileReference.Combine(UnrealBuildTool.EngineProgramSavedDirectory, "UnrealBuildTool", "Log.txt").FullName; } // Create the log file, and flush the startup listener to it if (!Arguments.HasOption("-NoLog") && !Log.HasFileWriter()) { FileReference LogFile = new FileReference(BaseLogFileName); foreach (string LogSuffix in Arguments.GetValues("-LogSuffix=")) { LogFile = LogFile.ChangeExtension(null) + "_" + LogSuffix + LogFile.GetExtension(); } TextWriterTraceListener LogTraceListener = Log.AddFileWriter("DefaultLogTraceListener", LogFile); StartupListener.CopyTo(LogTraceListener); } Trace.Listeners.Remove(StartupListener); // Create the build configuration object, and read the settings BuildConfiguration BuildConfiguration = new BuildConfiguration(); XmlConfig.ApplyTo(BuildConfiguration); Arguments.ApplyTo(BuildConfiguration); // Check the root path length isn't too long if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Win64 && UnrealBuildTool.RootDirectory.FullName.Length > BuildConfiguration.MaxRootPathLength) { Log.TraceWarning("Running from a path with a long directory name (\"{0}\" = {1} characters). Root paths shorter than {2} characters are recommended to avoid exceeding maximum path lengths on Windows.", UnrealBuildTool.RootDirectory, UnrealBuildTool.RootDirectory.FullName.Length, BuildConfiguration.MaxRootPathLength); } // now that we know the available platforms, we can delete other platforms' junk. if we're only building specific modules from the editor, don't touch anything else (it may be in use). if (!bIgnoreJunk && !UnrealBuildTool.IsEngineInstalled()) { using (Timeline.ScopeEvent("DeleteJunk()")) { JunkDeleter.DeleteJunk(); } } // Parse and build the targets try { List <TargetDescriptor> TargetDescriptors; // Parse all the target descriptors using (Timeline.ScopeEvent("TargetDescriptor.ParseCommandLine()")) { TargetDescriptors = TargetDescriptor.ParseCommandLine(Arguments, BuildConfiguration.bUsePrecompiled, BuildConfiguration.bSkipRulesCompile); } // Hack for specific files compile; don't build the ShaderCompileWorker target that's added to the command line for generated project files if (TargetDescriptors.Count >= 2) { TargetDescriptors.RemoveAll(x => (x.Name == "ShaderCompileWorker" || x.Name == "LiveCodingConsole") && x.SpecificFilesToCompile.Count > 0); } // Handle remote builds for (int Idx = 0; Idx < TargetDescriptors.Count; ++Idx) { TargetDescriptor TargetDesc = TargetDescriptors[Idx]; if (RemoteMac.HandlesTargetPlatform(TargetDesc.Platform)) { FileReference BaseLogFile = Log.OutputFile ?? new FileReference(BaseLogFileName); FileReference RemoteLogFile = FileReference.Combine(BaseLogFile.Directory, BaseLogFile.GetFileNameWithoutExtension() + "_Remote.txt"); RemoteMac RemoteMac = new RemoteMac(TargetDesc.ProjectFile); if (!RemoteMac.Build(TargetDesc, RemoteLogFile, bSkipPreBuildTargets)) { return((int)CompilationResult.Unknown); } TargetDescriptors.RemoveAt(Idx--); } } // Handle local builds if (TargetDescriptors.Count > 0) { // Get a set of all the project directories HashSet <DirectoryReference> ProjectDirs = new HashSet <DirectoryReference>(); foreach (TargetDescriptor TargetDesc in TargetDescriptors) { if (TargetDesc.ProjectFile != null) { DirectoryReference ProjectDirectory = TargetDesc.ProjectFile.Directory; FileMetadataPrefetch.QueueProjectDirectory(ProjectDirectory); ProjectDirs.Add(ProjectDirectory); } } // Get all the build options BuildOptions Options = BuildOptions.None; if (bSkipBuild) { Options |= BuildOptions.SkipBuild; } if (bXGEExport) { Options |= BuildOptions.XGEExport; } if (bNoEngineChanges) { Options |= BuildOptions.NoEngineChanges; } // Create the working set provider per group. using (ISourceFileWorkingSet WorkingSet = SourceFileWorkingSet.Create(UnrealBuildTool.RootDirectory, ProjectDirs)) { Build(TargetDescriptors, BuildConfiguration, WorkingSet, Options, WriteOutdatedActionsFile, bSkipPreBuildTargets); } } } finally { // Save all the caches SourceFileMetadataCache.SaveAll(); CppDependencyCache.SaveAll(); } return(0); }