/// <summary> /// Creates a text file with the given contents. If the contents of the text file aren't changed, it won't write the new contents to /// the file to avoid causing an action to be considered outdated. /// </summary> /// <param name="AbsolutePath">Path to the intermediate file to create</param> /// <param name="Contents">Contents of the new file</param> /// <returns>File item for the newly created file</returns> public static FileItem CreateIntermediateTextFile(FileReference AbsolutePath, string Contents) { // Create the directory if it doesn't exist. Directory.CreateDirectory(Path.GetDirectoryName(AbsolutePath.FullName)); // Only write the file if its contents have changed. if (!FileReference.Exists(AbsolutePath)) { File.WriteAllText(AbsolutePath.FullName, Contents, GetEncodingForString(Contents)); } else { string CurrentContents = Utils.ReadAllText(AbsolutePath.FullName); if (!String.Equals(CurrentContents, Contents, StringComparison.InvariantCultureIgnoreCase)) { try { FileReference PrevAbsolutePath = new FileReference(AbsolutePath.FullName + ".prev"); FileReference.Delete(PrevAbsolutePath); FileReference.Move(AbsolutePath, PrevAbsolutePath); Log.TraceLog("Updating {0} - contents have changed (previous version renamed to {1}).", AbsolutePath.FullName, PrevAbsolutePath); } catch { Log.TraceLog("Updating {0} - contents have changed (unable to rename). Previous:\n {1}\nNew:\n {2}", AbsolutePath.FullName, CurrentContents.Replace("\n", "\n "), Contents.Replace("\n", "\n ")); } File.WriteAllText(AbsolutePath.FullName, Contents, GetEncodingForString(Contents)); } } return(GetItemByFileReference(AbsolutePath)); }
/// <summary> /// Creates a text file with the given contents. If the contents of the text file aren't changed, it won't write the new contents to /// the file to avoid causing an action to be considered outdated. /// </summary> /// <param name="Location">Path to the intermediate file to create</param> /// <param name="Contents">Contents of the new file</param> /// <returns>File item for the newly created file</returns> public static FileItem CreateIntermediateTextFile(FileReference Location, string Contents) { // Only write the file if its contents have changed. if (!FileReference.Exists(Location)) { DirectoryReference.CreateDirectory(Location.Directory); FileReference.WriteAllText(Location, Contents, GetEncodingForString(Contents)); } else { string CurrentContents = Utils.ReadAllText(Location.FullName); if (!String.Equals(CurrentContents, Contents, StringComparison.InvariantCultureIgnoreCase)) { FileReference BackupFile = new FileReference(Location.FullName + ".old"); try { Log.TraceLog("Updating {0}: contents have changed. Saving previous version to {1}.", Location, BackupFile); FileReference.Delete(BackupFile); FileReference.Move(Location, BackupFile); } catch (Exception Ex) { Log.TraceWarning("Unable to rename {0} to {1}", Location, BackupFile); Log.TraceLog("{0}", ExceptionUtils.FormatExceptionDetails(Ex)); } FileReference.WriteAllText(Location, Contents, GetEncodingForString(Contents)); } } // Reset the file info, in case it already knows about the old file FileItem Item = GetItemByFileReference(Location); Item.ResetCachedInfo(); return(Item); }
/** * Creates a text file with the given contents. If the contents of the text file aren't changed, it won't write the new contents to * the file to avoid causing an action to be considered outdated. */ public static FileItem CreateIntermediateTextFile(string AbsolutePath, string Contents) { // Create the directory if it doesn't exist. Directory.CreateDirectory(Path.GetDirectoryName(AbsolutePath)); // Only write the file if its contents have changed. if (!File.Exists(AbsolutePath) || !String.Equals(Utils.ReadAllText(AbsolutePath), Contents, StringComparison.InvariantCultureIgnoreCase)) { File.WriteAllText(AbsolutePath, Contents, GetEncodingForString(Contents)); } return(GetItemByPath(AbsolutePath)); }
/// <summary> /// Patch the action graph for hot reloading, mapping files according to the given dictionary. /// </summary> public static void PatchActionGraph(IEnumerable <Action> Actions, Dictionary <FileReference, FileReference> OriginalFileToHotReloadFile) { // Gather all of the response files for link actions. We're going to need to patch 'em up after we figure out new // names for all of the output files and import libraries List <string> ResponseFilePaths = new List <string>(); // Same as Response files but for all of the link.sh files for link actions. // Only used on BuildHostPlatform Linux List <string> LinkScriptFilePaths = new List <string>(); // Keep a map of the original file names and their new file names, so we can fix up response files after Dictionary <string, string> OriginalFileNameAndNewFileNameList_NoExtensions = new Dictionary <string, string>(); // Finally, we'll keep track of any file items that we had to create counterparts for change file names, so we can fix those up too Dictionary <FileItem, FileItem> AffectedOriginalFileItemAndNewFileItemMap = new Dictionary <FileItem, FileItem>(); foreach (Action Action in Actions.Where((Action) => Action.ActionType == ActionType.Link)) { // Assume that the first produced item (with no extension) is our output file name FileReference HotReloadFile; if (!OriginalFileToHotReloadFile.TryGetValue(Action.ProducedItems[0].Location, out HotReloadFile)) { continue; } string OriginalFileNameWithoutExtension = Utils.GetFilenameWithoutAnyExtensions(Action.ProducedItems[0].AbsolutePath); string NewFileNameWithoutExtension = Utils.GetFilenameWithoutAnyExtensions(HotReloadFile.FullName); // Find the response file in the command line. We'll need to make a copy of it with our new file name. string ResponseFileExtension = ".response"; int ResponseExtensionIndex = Action.CommandArguments.IndexOf(ResponseFileExtension, StringComparison.InvariantCultureIgnoreCase); if (ResponseExtensionIndex != -1) { int ResponseFilePathIndex = Action.CommandArguments.LastIndexOf("@\"", ResponseExtensionIndex); if (ResponseFilePathIndex == -1) { throw new BuildException("Couldn't find response file path in action's command arguments when hot reloading"); } string OriginalResponseFilePathWithoutExtension = Action.CommandArguments.Substring(ResponseFilePathIndex + 2, (ResponseExtensionIndex - ResponseFilePathIndex) - 2); string OriginalResponseFilePath = OriginalResponseFilePathWithoutExtension + ResponseFileExtension; string NewResponseFilePath = ReplaceBaseFileName(OriginalResponseFilePath, OriginalFileNameWithoutExtension, NewFileNameWithoutExtension); // Copy the old response file to the new path if (String.Compare(OriginalResponseFilePath, NewResponseFilePath, StringComparison.OrdinalIgnoreCase) != 0) { File.Copy(OriginalResponseFilePath, NewResponseFilePath, overwrite: true); } // Keep track of the new response file name. We'll have to do some edits afterwards. ResponseFilePaths.Add(NewResponseFilePath); } // Find the *.link.sh file in the command line. We'll need to make a copy of it with our new file name. // Only currently used on Linux if (UEBuildPlatform.IsPlatformInGroup(BuildHostPlatform.Current.Platform, UnrealPlatformGroup.Unix)) { string LinkScriptFileExtension = ".link.sh"; int LinkScriptExtensionIndex = Action.CommandArguments.IndexOf(LinkScriptFileExtension, StringComparison.InvariantCultureIgnoreCase); if (LinkScriptExtensionIndex != -1) { // We expect the script invocation to be quoted int LinkScriptFilePathIndex = Action.CommandArguments.LastIndexOf("\"", LinkScriptExtensionIndex); if (LinkScriptFilePathIndex == -1) { throw new BuildException("Couldn't find link script file path in action's command arguments when hot reloading. Is the path quoted?"); } string OriginalLinkScriptFilePathWithoutExtension = Action.CommandArguments.Substring(LinkScriptFilePathIndex + 1, (LinkScriptExtensionIndex - LinkScriptFilePathIndex) - 1); string OriginalLinkScriptFilePath = OriginalLinkScriptFilePathWithoutExtension + LinkScriptFileExtension; string NewLinkScriptFilePath = ReplaceBaseFileName(OriginalLinkScriptFilePath, OriginalFileNameWithoutExtension, NewFileNameWithoutExtension); // Copy the old response file to the new path File.Copy(OriginalLinkScriptFilePath, NewLinkScriptFilePath, overwrite: true); // Keep track of the new response file name. We'll have to do some edits afterwards. LinkScriptFilePaths.Add(NewLinkScriptFilePath); } // Update this action's list of prerequisite items too for (int ItemIndex = 0; ItemIndex < Action.PrerequisiteItems.Count; ++ItemIndex) { FileItem OriginalPrerequisiteItem = Action.PrerequisiteItems[ItemIndex]; string NewPrerequisiteItemFilePath = ReplaceBaseFileName(OriginalPrerequisiteItem.AbsolutePath, OriginalFileNameWithoutExtension, NewFileNameWithoutExtension); if (OriginalPrerequisiteItem.AbsolutePath != NewPrerequisiteItemFilePath) { // OK, the prerequisite item's file name changed so we'll update it to point to our new file FileItem NewPrerequisiteItem = FileItem.GetItemByPath(NewPrerequisiteItemFilePath); Action.PrerequisiteItems[ItemIndex] = NewPrerequisiteItem; // Keep track of it so we can fix up dependencies in a second pass afterwards AffectedOriginalFileItemAndNewFileItemMap.Add(OriginalPrerequisiteItem, NewPrerequisiteItem); ResponseExtensionIndex = OriginalPrerequisiteItem.AbsolutePath.IndexOf(ResponseFileExtension, StringComparison.InvariantCultureIgnoreCase); if (ResponseExtensionIndex != -1) { string OriginalResponseFilePathWithoutExtension = OriginalPrerequisiteItem.AbsolutePath.Substring(0, ResponseExtensionIndex); string OriginalResponseFilePath = OriginalResponseFilePathWithoutExtension + ResponseFileExtension; string NewResponseFilePath = ReplaceBaseFileName(OriginalResponseFilePath, OriginalFileNameWithoutExtension, NewFileNameWithoutExtension); // Copy the old response file to the new path File.Copy(OriginalResponseFilePath, NewResponseFilePath, overwrite: true); // Keep track of the new response file name. We'll have to do some edits afterwards. ResponseFilePaths.Add(NewResponseFilePath); } } } } // Update this action's list of produced items too for (int ItemIndex = 0; ItemIndex < Action.ProducedItems.Count; ++ItemIndex) { FileItem OriginalProducedItem = Action.ProducedItems[ItemIndex]; string NewProducedItemFilePath = ReplaceBaseFileName(OriginalProducedItem.AbsolutePath, OriginalFileNameWithoutExtension, NewFileNameWithoutExtension); if (OriginalProducedItem.AbsolutePath != NewProducedItemFilePath) { // OK, the produced item's file name changed so we'll update it to point to our new file FileItem NewProducedItem = FileItem.GetItemByPath(NewProducedItemFilePath); Action.ProducedItems[ItemIndex] = NewProducedItem; // Keep track of it so we can fix up dependencies in a second pass afterwards AffectedOriginalFileItemAndNewFileItemMap.Add(OriginalProducedItem, NewProducedItem); } } // Fix up the list of items to delete too for (int Idx = 0; Idx < Action.DeleteItems.Count; Idx++) { FileItem NewItem; if (AffectedOriginalFileItemAndNewFileItemMap.TryGetValue(Action.DeleteItems[Idx], out NewItem)) { Action.DeleteItems[Idx] = NewItem; } } // The status description of the item has the file name, so we'll update it too Action.StatusDescription = ReplaceBaseFileName(Action.StatusDescription, OriginalFileNameWithoutExtension, NewFileNameWithoutExtension); // Keep track of the file names, so we can fix up response files afterwards. if (!OriginalFileNameAndNewFileNameList_NoExtensions.ContainsKey(OriginalFileNameWithoutExtension)) { OriginalFileNameAndNewFileNameList_NoExtensions[OriginalFileNameWithoutExtension] = NewFileNameWithoutExtension; } else if (OriginalFileNameAndNewFileNameList_NoExtensions[OriginalFileNameWithoutExtension] != NewFileNameWithoutExtension) { throw new BuildException("Unexpected conflict in renaming files; {0} maps to {1} and {2}", OriginalFileNameWithoutExtension, OriginalFileNameAndNewFileNameList_NoExtensions[OriginalFileNameWithoutExtension], NewFileNameWithoutExtension); } } // Do another pass and update any actions that depended on the original file names that we changed foreach (Action Action in Actions) { for (int ItemIndex = 0; ItemIndex < Action.PrerequisiteItems.Count; ++ItemIndex) { FileItem OriginalFileItem = Action.PrerequisiteItems[ItemIndex]; FileItem NewFileItem; if (AffectedOriginalFileItemAndNewFileItemMap.TryGetValue(OriginalFileItem, out NewFileItem)) { // OK, looks like we need to replace this file item because we've renamed the file Action.PrerequisiteItems[ItemIndex] = NewFileItem; } } } if (OriginalFileNameAndNewFileNameList_NoExtensions.Count > 0) { // Update all the paths in link actions foreach (Action Action in Actions.Where((Action) => Action.ActionType == ActionType.Link)) { foreach (KeyValuePair <string, string> FileNameTuple in OriginalFileNameAndNewFileNameList_NoExtensions) { string OriginalFileNameWithoutExtension = FileNameTuple.Key; string NewFileNameWithoutExtension = FileNameTuple.Value; Action.CommandArguments = ReplaceBaseFileName(Action.CommandArguments, OriginalFileNameWithoutExtension, NewFileNameWithoutExtension); } } foreach (string ResponseFilePath in ResponseFilePaths) { // Load the file up string FileContents = Utils.ReadAllText(ResponseFilePath); // Replace all of the old file names with new ones foreach (KeyValuePair <string, string> FileNameTuple in OriginalFileNameAndNewFileNameList_NoExtensions) { string OriginalFileNameWithoutExtension = FileNameTuple.Key; string NewFileNameWithoutExtension = FileNameTuple.Value; FileContents = ReplaceBaseFileName(FileContents, OriginalFileNameWithoutExtension, NewFileNameWithoutExtension); } // Overwrite the original file File.WriteAllText(ResponseFilePath, FileContents, new System.Text.UTF8Encoding(false)); } if (UEBuildPlatform.IsPlatformInGroup(BuildHostPlatform.Current.Platform, UnrealPlatformGroup.Unix)) { foreach (string LinkScriptFilePath in LinkScriptFilePaths) { // Load the file up string FileContents = Utils.ReadAllText(LinkScriptFilePath); // Replace all of the old file names with new ones foreach (KeyValuePair <string, string> FileNameTuple in OriginalFileNameAndNewFileNameList_NoExtensions) { string OriginalFileNameWithoutExtension = FileNameTuple.Key; string NewFileNameWithoutExtension = FileNameTuple.Value; FileContents = ReplaceBaseFileName(FileContents, OriginalFileNameWithoutExtension, NewFileNameWithoutExtension); } // Overwrite the original file File.WriteAllText(LinkScriptFilePath, FileContents, new System.Text.UTF8Encoding(false)); } } } // Update the action that writes out the module manifests foreach (Action Action in Actions) { if (Action.ActionType == ActionType.WriteMetadata) { string Arguments = Action.CommandArguments; // Find the argument for the metadata file const string InputArgument = "-Input="; int InputIdx = Arguments.IndexOf(InputArgument); if (InputIdx == -1) { throw new Exception("Missing -Input= argument to WriteMetadata command when patching action graph."); } int FileNameIdx = InputIdx + InputArgument.Length; if (Arguments[FileNameIdx] == '\"') { FileNameIdx++; } int FileNameEndIdx = FileNameIdx; while (FileNameEndIdx < Arguments.Length && (Arguments[FileNameEndIdx] != ' ' || Arguments[FileNameIdx - 1] == '\"') && Arguments[FileNameEndIdx] != '\"') { FileNameEndIdx++; } // Read the metadata file FileReference TargetInfoFile = new FileReference(Arguments.Substring(FileNameIdx, FileNameEndIdx - FileNameIdx)); if (!FileReference.Exists(TargetInfoFile)) { throw new Exception(String.Format("Unable to find metadata file to patch action graph ({0})", TargetInfoFile)); } // Update the module names WriteMetadataTargetInfo TargetInfo = BinaryFormatterUtils.Load <WriteMetadataTargetInfo>(TargetInfoFile); foreach (KeyValuePair <FileReference, ModuleManifest> FileNameToVersionManifest in TargetInfo.FileToManifest) { KeyValuePair <string, string>[] ManifestEntries = FileNameToVersionManifest.Value.ModuleNameToFileName.ToArray(); foreach (KeyValuePair <string, string> Manifest in ManifestEntries) { FileReference OriginalFile = FileReference.Combine(FileNameToVersionManifest.Key.Directory, Manifest.Value); FileReference HotReloadFile; if (OriginalFileToHotReloadFile.TryGetValue(OriginalFile, out HotReloadFile)) { FileNameToVersionManifest.Value.ModuleNameToFileName[Manifest.Key] = HotReloadFile.GetFileName(); } } } // Write the hot-reload metadata file and update the argument list FileReference HotReloadTargetInfoFile = FileReference.Combine(TargetInfoFile.Directory, "Metadata-HotReload.dat"); BinaryFormatterUtils.SaveIfDifferent(HotReloadTargetInfoFile, TargetInfo); Action.PrerequisiteItems.RemoveAll(x => x.Location == TargetInfoFile); Action.PrerequisiteItems.Add(FileItem.GetItemByFileReference(HotReloadTargetInfoFile)); Action.CommandArguments = Arguments.Substring(0, FileNameIdx) + HotReloadTargetInfoFile + Arguments.Substring(FileNameEndIdx); } } }
/** Finds the names of files directly included by the given C++ file. */ public static List <DependencyInclude> GetDirectIncludeDependencies(UEBuildTarget Target, FileItem CPPFile, IUEBuildPlatform BuildPlatform, bool bOnlyCachedDependencies, out bool HasUObjects) { // Try to fulfill request from cache first. List <DependencyInclude> Result; if (IncludeDependencyCache[Target].GetCachedDirectDependencies(CPPFile, out Result, out HasUObjects)) { return(Result); } else if (bOnlyCachedDependencies) { return(new List <DependencyInclude>()); } var TimerStartTime = DateTime.UtcNow; ++CPPEnvironment.TotalDirectIncludeCacheMisses; // Get the adjusted filename string FileToRead = CPPFile.AbsolutePath; if (BuildPlatform.RequiresExtraUnityCPPWriter() == true && Path.GetFileName(FileToRead).StartsWith("Module.")) { FileToRead += ".ex"; } // Read lines from the C++ file. string FileContents = Utils.ReadAllText(FileToRead); if (string.IsNullOrEmpty(FileContents)) { return(new List <DependencyInclude>()); } HasUObjects = CPPEnvironment.UObjectRegex.IsMatch(FileContents); // Note: This depends on UBT executing w/ a working directory of the Engine/Source folder! string EngineSourceFolder = Directory.GetCurrentDirectory(); string InstalledFolder = EngineSourceFolder; Int32 EngineSourceIdx = EngineSourceFolder.IndexOf("\\Engine\\Source"); if (EngineSourceIdx != -1) { InstalledFolder = EngineSourceFolder.Substring(0, EngineSourceIdx); } Result = new List <DependencyInclude>(); if (Utils.IsRunningOnMono) { // Mono crashes when running a regex on a string longer than about 5000 characters, so we parse the file in chunks int StartIndex = 0; const int SafeTextLength = 4000; while (StartIndex < FileContents.Length) { int EndIndex = StartIndex + SafeTextLength < FileContents.Length ? FileContents.IndexOf("\n", StartIndex + SafeTextLength) : FileContents.Length; if (EndIndex == -1) { EndIndex = FileContents.Length; } CollectHeaders(CPPFile, Result, FileToRead, FileContents, InstalledFolder, StartIndex, EndIndex); StartIndex = EndIndex + 1; } } else { CollectHeaders(CPPFile, Result, FileToRead, FileContents, InstalledFolder, 0, FileContents.Length); } // Populate cache with results. IncludeDependencyCache[Target].SetDependencyInfo(CPPFile, Result, HasUObjects); CPPEnvironment.DirectIncludeCacheMissesTotalTime += (DateTime.UtcNow - TimerStartTime).TotalSeconds; return(Result); }