// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using AutomationTool; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml; using Tools.DotNETCommon; using UnrealBuildTool; namespace BuildGraph.Tasks { /// /// Parameters for a task that runs the cooker /// public class PakFileTaskParameters { /// /// List of files, wildcards and tag sets to add to the pak file, separated by ';' characters. /// [TaskParameter(ValidationType = TaskParameterValidationType.FileSpec)] public string Files; /// /// PAK file to output /// [TaskParameter] public FileReference Output; /// /// Path to a Response File that contains a list of files to add to the pak file, instead of specifying them individually /// [TaskParameter(Optional = true)] public FileReference ResponseFile; /// /// Directories to rebase the files relative to. If specified, the shortest path under a listed directory will be used for each file. /// [TaskParameter(Optional = true)] public HashSet RebaseDir; /// /// Script which gives the order of files /// [TaskParameter(Optional = true)] public FileReference Order; /// /// Encryption keys for this pak file /// [TaskParameter(Optional = true)] public FileReference Sign; /// /// Whether to compress files /// [TaskParameter(Optional = true)] public bool Compress = true; /// /// Additional arguments to be passed to UnrealPak /// [TaskParameter(Optional = true)] public string Arguments = ""; /// /// Tag to be applied to build products of this task /// [TaskParameter(Optional = true, ValidationType = TaskParameterValidationType.TagList)] public string Tag; } /// /// Creates a PAK file from a given set of files. /// [TaskElement("PakFile", typeof(PakFileTaskParameters))] public class PakFileTask : CustomTask { /// /// Parameters for the task /// PakFileTaskParameters Parameters; /// /// Constructor. /// /// Parameters for this task public PakFileTask(PakFileTaskParameters InParameters) { Parameters = InParameters; } /// /// Execute the task. /// /// Information about the current job /// Set of build products produced by this node. /// Mapping from tag names to the set of files they include public override void Execute(JobContext Job, HashSet BuildProducts, Dictionary> TagNameToFileSet) { // Find the directories we're going to rebase relative to HashSet RebaseDirs = new HashSet{ CommandUtils.RootDirectory }; if(Parameters.RebaseDir != null) { RebaseDirs.UnionWith(Parameters.RebaseDir); } // Get the output parameter FileReference OutputFile = Parameters.Output; // Check for a ResponseFile parameter FileReference ResponseFile = Parameters.ResponseFile; if (ResponseFile == null) { // Get a unique filename for the response file ResponseFile = FileReference.Combine(new DirectoryReference(CommandUtils.CmdEnv.LogFolder), String.Format("PakList_{0}.txt", OutputFile.GetFileNameWithoutExtension())); for (int Idx = 2; FileReference.Exists(ResponseFile); Idx++) { ResponseFile = FileReference.Combine(ResponseFile.Directory, String.Format("PakList_{0}_{1}.txt", OutputFile.GetFileNameWithoutExtension(), Idx)); } // Write out the response file HashSet Files = ResolveFilespec(CommandUtils.RootDirectory, Parameters.Files, TagNameToFileSet); using (StreamWriter Writer = new StreamWriter(ResponseFile.FullName, false, new System.Text.UTF8Encoding(true))) { foreach (FileReference File in Files) { string RelativePath = FindShortestRelativePath(File, RebaseDirs); if (RelativePath == null) { throw new AutomationException("Couldn't find relative path for '{0}' - not under any rebase directories", File.FullName); } Writer.WriteLine("\"{0}\" \"{1}\"{2}", File.FullName, RelativePath, Parameters.Compress ? " -compress" : ""); } } } // Format the command line StringBuilder CommandLine = new StringBuilder(); CommandLine.AppendFormat("{0} -create={1}", CommandUtils.MakePathSafeToUseWithCommandLine(OutputFile.FullName), CommandUtils.MakePathSafeToUseWithCommandLine(ResponseFile.FullName)); if(Parameters.Sign != null) { CommandLine.AppendFormat(" -sign={0}", CommandUtils.MakePathSafeToUseWithCommandLine(Parameters.Sign.FullName)); } if(Parameters.Order != null) { CommandLine.AppendFormat(" -order={0}", CommandUtils.MakePathSafeToUseWithCommandLine(Parameters.Order.FullName)); } if (CommandUtils.IsEngineInstalled()) { CommandLine.Append(" -installed"); } if (GlobalCommandLine.UTF8Output) { CommandLine.AppendFormat(" -UTF8Output"); } // Get the executable path FileReference UnrealPakExe; if(HostPlatform.Current.HostEditorPlatform == UnrealTargetPlatform.Win64) { UnrealPakExe = ResolveFile("Engine/Binaries/Win64/UnrealPak.exe"); } else { UnrealPakExe = ResolveFile(String.Format("Engine/Binaries/{0}/UnrealPak", HostPlatform.Current.HostEditorPlatform.ToString())); } // Run it CommandUtils.LogInformation("Running '{0} {1}'", CommandUtils.MakePathSafeToUseWithCommandLine(UnrealPakExe.FullName), CommandLine.ToString()); CommandUtils.RunAndLog(CommandUtils.CmdEnv, UnrealPakExe.FullName, CommandLine.ToString(), Options: CommandUtils.ERunOptions.Default | CommandUtils.ERunOptions.UTF8Output); BuildProducts.Add(OutputFile); // Apply the optional tag to the output file foreach(string TagName in FindTagNamesFromList(Parameters.Tag)) { FindOrAddTagSet(TagNameToFileSet, TagName).Add(OutputFile); } } /// /// Find the shortest relative path of the given file from a set of base directories. /// /// Full path to a file /// Possible base directories /// The shortest relative path, or null if the file is not under any of them public static string FindShortestRelativePath(FileReference File, IEnumerable RebaseDirs) { string RelativePath = null; foreach(DirectoryReference RebaseDir in RebaseDirs) { if(File.IsUnderDirectory(RebaseDir)) { string NewRelativePath = File.MakeRelativeTo(RebaseDir); if(RelativePath == null || NewRelativePath.Length < RelativePath.Length) { RelativePath = NewRelativePath; } } } return RelativePath; } /// /// Output this task out to an XML writer. /// public override void Write(XmlWriter Writer) { Write(Writer, Parameters); } /// /// Find all the tags which are used as inputs to this task /// /// The tag names which are read by this task public override IEnumerable FindConsumedTagNames() { return FindTagNamesFromFilespec(Parameters.Files); } /// /// Find all the tags which are modified by this task /// /// The tag names which are modified by this task public override IEnumerable FindProducedTagNames() { return FindTagNamesFromList(Parameters.Tag); } } }