// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; using System.Xml; using EpicGames.Core; using Microsoft.Extensions.Logging; using UnrealBuildBase; using UnrealBuildTool; namespace AutomationTool.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 { get; set; } /// /// PAK file to output. /// [TaskParameter] public FileReference Output { get; set; } /// /// 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 { get; set; } /// /// 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 { get; set; } /// /// Script that gives the order of files. /// [TaskParameter(Optional = true)] public FileReference Order { get; set; } /// /// Encryption keys for this pak file. /// [TaskParameter(Optional = true)] public FileReference Sign { get; set; } /// /// Whether to compress files. /// [TaskParameter(Optional = true)] public bool Compress { get; set; } = true; /// /// Additional arguments to pass to UnrealPak. /// [TaskParameter(Optional = true)] public string Arguments { get; set; } = ""; /// /// Tag to be applied to build products of this task. /// [TaskParameter(Optional = true, ValidationType = TaskParameterValidationType.TagList)] public string Tag { get; set; } } /// /// Creates a PAK file from a given set of files. /// [TaskElement("PakFile", typeof(PakFileTaskParameters))] public class PakFileTask : BgTaskImpl { readonly PakFileTaskParameters _parameters; /// /// Constructor. /// /// Parameters for this task public PakFileTask(PakFileTaskParameters parameters) { _parameters = parameters; } /// /// ExecuteAsync 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 async Task ExecuteAsync(JobContext job, HashSet buildProducts, Dictionary> tagNameToFileSet) { // Find the directories we're going to rebase relative to HashSet rebaseDirs = new HashSet { Unreal.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(Unreal.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); } string compressArg = _parameters.Compress ? " -compress" : ""; await writer.WriteLineAsync($"\"{file.FullName}\" \"{relativePath}\"{compressArg}"); } } } // 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 (Unreal.IsEngineInstalled()) { commandLine.Append(" -installed"); } // 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 Logger.LogInformation("Running '{Arg0} {Arg1}'", 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); } } }