コード例 #1
0
        /// <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);
                }
            }
        }
コード例 #2
0
        /// <summary>
        /// Execute the command, having obtained the appropriate mutex
        /// </summary>
        /// <param name="Arguments">Command line arguments</param>
        /// <returns>Exit code</returns>
        private int ExecuteInternal(CommandLineArguments Arguments)
        {
            // Read the target info
            WriteMetadataTargetInfo TargetInfo = BinaryFormatterUtils.Load <WriteMetadataTargetInfo>(Arguments.GetFileReference("-Input="));
            bool bNoManifestChanges            = Arguments.HasOption("-NoManifestChanges");
            int  VersionNumber = Arguments.GetInteger("-Version=");

            Arguments.CheckAllArgumentsUsed();

            // Make sure the version number is correct
            if (VersionNumber != CurrentVersionNumber)
            {
                throw new BuildException("Version number to WriteMetadataMode is incorrect (expected {0}, got {1})", CurrentVersionNumber, VersionNumber);
            }

            // Check if we need to set a build id
            TargetReceipt Receipt = TargetInfo.Receipt;

            if (String.IsNullOrEmpty(Receipt.Version.BuildId))
            {
                // Check if there's an existing version file. If it exists, try to merge in any manifests that are valid (and reuse the existing build id)
                BuildVersion PreviousVersion;
                if (TargetInfo.VersionFile != null && BuildVersion.TryRead(TargetInfo.VersionFile, out PreviousVersion))
                {
                    // Check if we can reuse the existing manifests. This prevents unnecessary builds when switching between projects.
                    Dictionary <FileReference, ModuleManifest> PreviousFileToManifest = new Dictionary <FileReference, ModuleManifest>();
                    if (TryRecyclingManifests(PreviousVersion.BuildId, TargetInfo.FileToManifest.Keys, PreviousFileToManifest))
                    {
                        // Merge files from the existing manifests with the new ones
                        foreach (KeyValuePair <FileReference, ModuleManifest> Pair in PreviousFileToManifest)
                        {
                            ModuleManifest TargetManifest = TargetInfo.FileToManifest[Pair.Key];
                            MergeManifests(Pair.Value, TargetManifest);
                        }

                        // Update the build id to use the current one
                        Receipt.Version.BuildId = PreviousVersion.BuildId;
                    }
                }

                // If the build id is still not set, generate a new one from a GUID
                if (String.IsNullOrEmpty(Receipt.Version.BuildId))
                {
                    Receipt.Version.BuildId = Guid.NewGuid().ToString();
                }
            }
            else
            {
                // Read all the manifests and merge them into the new ones, if they have the same build id
                foreach (KeyValuePair <FileReference, ModuleManifest> Pair in TargetInfo.FileToManifest)
                {
                    ModuleManifest SourceManifest;
                    if (TryReadManifest(Pair.Key, out SourceManifest) && SourceManifest.BuildId == Receipt.Version.BuildId)
                    {
                        MergeManifests(SourceManifest, Pair.Value);
                    }
                }
            }

            // Update the build id in all the manifests, and write them out
            foreach (KeyValuePair <FileReference, ModuleManifest> Pair in TargetInfo.FileToManifest)
            {
                FileReference ManifestFile = Pair.Key;
                if (!UnrealBuildTool.IsFileInstalled(ManifestFile))
                {
                    ModuleManifest Manifest = Pair.Value;
                    Manifest.BuildId = Receipt.Version.BuildId;

                    if (!FileReference.Exists(ManifestFile))
                    {
                        // If the file doesn't already exist, just write it out
                        DirectoryReference.CreateDirectory(ManifestFile.Directory);
                        Manifest.Write(ManifestFile);
                    }
                    else
                    {
                        // Otherwise write it to a buffer first
                        string OutputText;
                        using (StringWriter Writer = new StringWriter())
                        {
                            Manifest.Write(Writer);
                            OutputText = Writer.ToString();
                        }

                        // And only write it to disk if it's been modified. Note that if a manifest is out of date, we should have generated a new build id causing the contents to differ.
                        string CurrentText = FileReference.ReadAllText(ManifestFile);
                        if (CurrentText != OutputText)
                        {
                            if (bNoManifestChanges)
                            {
                                Log.TraceError("Build modifies {0}. This is not permitted. Before:\n    {1}\nAfter:\n    {2}", ManifestFile, CurrentText.Replace("\n", "\n    "), OutputText.Replace("\n", "\n    "));
                            }
                            else
                            {
                                FileReference.WriteAllText(ManifestFile, OutputText);
                            }
                        }
                    }
                }
            }

            // Write out the version file, if it's changed. Since this file is next to the executable, it may be used by multiple targets, and we should avoid modifying it unless necessary.
            if (TargetInfo.VersionFile != null && !UnrealBuildTool.IsFileInstalled(TargetInfo.VersionFile))
            {
                DirectoryReference.CreateDirectory(TargetInfo.VersionFile.Directory);

                StringWriter Writer = new StringWriter();
                Receipt.Version.Write(Writer);

                string Text = Writer.ToString();
                if (!FileReference.Exists(TargetInfo.VersionFile) || File.ReadAllText(TargetInfo.VersionFile.FullName) != Text)
                {
                    File.WriteAllText(TargetInfo.VersionFile.FullName, Text);
                }
            }

            // Write out the receipt
            if (!UnrealBuildTool.IsFileInstalled(TargetInfo.ReceiptFile))
            {
                DirectoryReference.CreateDirectory(TargetInfo.ReceiptFile.Directory);
                Receipt.Write(TargetInfo.ReceiptFile);
            }

            return(0);
        }