1348 lines
56 KiB
C#
1348 lines
56 KiB
C#
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.IO;
|
|
using AutomationTool;
|
|
using UnrealBuildTool;
|
|
using AutomationScripts;
|
|
using EpicGames.Core;
|
|
using UnrealBuildBase;
|
|
using System.Text.Json;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
using static AutomationTool.CommandUtils;
|
|
|
|
public class ConfigHelper
|
|
{
|
|
private string SpecificConfigSection;
|
|
private string SharedConfigSection;
|
|
public ConfigHierarchy GameConfig { get; }
|
|
|
|
public ConfigHelper(UnrealTargetPlatform Platform, FileReference ProjectFile, bool bIsCookedCooker)
|
|
{
|
|
SharedConfigSection = "CookedEditorSettings";
|
|
SpecificConfigSection = SharedConfigSection + (bIsCookedCooker ? "_CookedCooker" : "_CookedEditor");
|
|
|
|
GameConfig = ConfigCache.ReadHierarchy(ConfigHierarchyType.Game, ProjectFile.Directory, Platform);
|
|
}
|
|
|
|
public bool GetBool(string Key)
|
|
{
|
|
bool Value;
|
|
// GetBool will set Value to false if it's not found, which is what we want
|
|
if (!GameConfig.GetBool(SpecificConfigSection, Key, out Value))
|
|
{
|
|
GameConfig.GetBool(SharedConfigSection, Key, out Value);
|
|
}
|
|
return Value;
|
|
}
|
|
|
|
public string GetString(string Key)
|
|
{
|
|
string Value;
|
|
// GetBool will set Value to "" if it's not found, so, set it to null if not found
|
|
if (!GameConfig.GetString(SpecificConfigSection, Key, out Value))
|
|
{
|
|
if (!GameConfig.GetString(SharedConfigSection, Key, out Value))
|
|
{
|
|
Value = null;
|
|
}
|
|
}
|
|
return Value;
|
|
}
|
|
|
|
public List<string> GetArray(string Key)
|
|
{
|
|
List<string> Value = new List<string>();
|
|
List<string> Temp;
|
|
|
|
// merge both sections into one array (probably don't depend on order)
|
|
if (GameConfig.GetArray(SpecificConfigSection, Key, out Temp))
|
|
{
|
|
Value.AddRange(Temp);
|
|
}
|
|
if (GameConfig.GetArray(SharedConfigSection, Key, out Temp))
|
|
{
|
|
Value.AddRange(Temp);
|
|
}
|
|
|
|
return Value;
|
|
}
|
|
}
|
|
|
|
public class ModifyStageContext
|
|
{
|
|
// any assets that end up in this list that are already in the DeploymentContext will be removed during Apply
|
|
public List<FileReference> UFSFilesToStage = new List<FileReference>();
|
|
// files in this list will remove the matching cooked package from the DeploymentContext and these uncooked assets will replace them
|
|
public List<FileReference> FilesToUncook = new List<FileReference>();
|
|
// these files will just be staged
|
|
public List<FileReference> NonUFSFilesToStage = new List<FileReference>();
|
|
|
|
public List<FileReference> DebugNonUFSFilesToStage = new List<FileReference>();
|
|
|
|
public bool bStageShaderDirs = true;
|
|
public bool bStagePlatformBuildDirs = true;
|
|
public bool bStageExtrasDirs = false;
|
|
public bool bStagePlatformDirs = true;
|
|
public bool bStageUAT = false;
|
|
public bool bIsForExternalDistribution = false;
|
|
public bool bStageCollections = false;
|
|
public bool bStagePython = false;
|
|
public bool bStageTargetFiles = false;
|
|
|
|
public ConfigHelper ConfigHelper;
|
|
|
|
public DirectoryReference EngineDirectory;
|
|
public DirectoryReference ProjectDirectory;
|
|
public string ProjectName;
|
|
public string IniPlatformName;
|
|
public bool bIsDLC;
|
|
|
|
// when creating a cooked editor against a premade client, this is the sub-directory in the Releases directory to compare against
|
|
public DirectoryReference ReleaseMetadataLocation = null;
|
|
|
|
// where to find files like CachedEditorThumbnails.bin or EditorClientAssetRegistry.bin
|
|
public DirectoryReference CachedEditorDataLocation = null;
|
|
|
|
// commandline etc helper
|
|
private BuildCommand Command;
|
|
|
|
public ModifyStageContext(ConfigHelper ConfigHelper, DirectoryReference EngineDirectory, ProjectParams Params, DeploymentContext SC, BuildCommand Command)
|
|
{
|
|
this.EngineDirectory = EngineDirectory;
|
|
this.Command = Command;
|
|
this.ConfigHelper = ConfigHelper;
|
|
|
|
bStageShaderDirs = ConfigHelper.GetBool("bStageShaderDirs");
|
|
bStagePlatformBuildDirs = ConfigHelper.GetBool("bStagePlatformBuildDirs");
|
|
bStageExtrasDirs = ConfigHelper.GetBool("bStageExtrasDirs");
|
|
bStagePlatformDirs = ConfigHelper.GetBool("bStagePlatformDirs");
|
|
bStageUAT = ConfigHelper.GetBool("bStageUAT");
|
|
bIsForExternalDistribution = ConfigHelper.GetBool("bIsForExternalDistribution");
|
|
bStageCollections = ConfigHelper.GetBool("bStageCollections");
|
|
bStagePython = ConfigHelper.GetBool("bStagePython");
|
|
|
|
// cache some useful properties
|
|
ProjectDirectory = Params.RawProjectPath.Directory;
|
|
ProjectName = Params.RawProjectPath.GetFileNameWithoutAnyExtensions();
|
|
IniPlatformName = ConfigHierarchy.GetIniPlatformName(SC.StageTargetPlatform.IniPlatformType);
|
|
bIsDLC = Params.DLCFile != null && SC.MetadataDir != null; // MetadataDir needs to be set for DLC
|
|
|
|
Logger.LogInformation("---> ReleaseOverrideDir = {Arg0}, MetadataDir = {Arg1}", Params.BasedOnReleaseVersionPathOverride, SC.MetadataDir);
|
|
|
|
|
|
// cache info for DLC against a release
|
|
if (Params.BasedOnReleaseVersionPathOverride != null)
|
|
{
|
|
// look in Metadata and the override locations
|
|
ReleaseMetadataLocation = DirectoryReference.Combine(new DirectoryReference(Params.BasedOnReleaseVersionPathOverride), "Metadata");
|
|
}
|
|
else
|
|
{
|
|
ReleaseMetadataLocation = SC.MetadataDir;
|
|
}
|
|
|
|
// by default, the files are in the cooked data location (which is SC.Metadatadir)
|
|
CachedEditorDataLocation = SC.MetadataDir;
|
|
}
|
|
|
|
public void Apply(DeploymentContext SC)
|
|
{
|
|
if (bIsDLC)
|
|
{
|
|
// remove files that we are about to stage that were already in the shipped client
|
|
RemoveReleasedFiles(SC);
|
|
}
|
|
|
|
// maps can't be cooked and loaded by the editor, so make sure no cooked ones exist
|
|
UncookMaps(SC);
|
|
UnUFSFiles(SC);
|
|
|
|
// anything we want to be NonUFS make sure is not alreay UFS
|
|
UFSFilesToStage.RemoveAll(x => NonUFSFilesToStage.Contains(x) || DebugNonUFSFilesToStage.Contains(x));
|
|
|
|
Dictionary<StagedFileReference, FileReference> StagedUFSFiles = MimicStageFiles(SC, UFSFilesToStage);
|
|
Dictionary<StagedFileReference, FileReference> StagedNonUFSFiles = MimicStageFiles(SC, NonUFSFilesToStage);
|
|
Dictionary<StagedFileReference, FileReference> StagedDebugNonUFSFiles = MimicStageFiles(SC, DebugNonUFSFilesToStage);
|
|
Dictionary<StagedFileReference, FileReference> StagedUncookFiles = MimicStageFiles(SC, FilesToUncook);
|
|
|
|
// filter out already-cooked assets
|
|
foreach (var CookedFile in SC.FilesToStage.UFSFiles)
|
|
{
|
|
// remove any of the entries in the "staged" UFSFilesToStage that match already staged files
|
|
// we don't check extension here because the UFSFilesToStage should only contain .uasset/.umap files, and not .uexp, etc,
|
|
// and .uasset/.umap files are going to be in SC.FilesToStage
|
|
StagedUFSFiles.Remove(CookedFile.Key);
|
|
}
|
|
|
|
// remove already-cooked assets to be replaced with
|
|
List<StagedFileReference> UncookedFilesThatDoNotExist = new List<StagedFileReference>();
|
|
string[] CookedExtensions = { ".uasset", ".umap", ".ubulk", ".uexp", ".uptnl" };
|
|
foreach (var UncookedFile in StagedUncookFiles)
|
|
{
|
|
string PathWithNoExtension = Path.ChangeExtension(UncookedFile.Key.Name, null);
|
|
// we need to remove cooked files that match the files to Uncook, and there can be several extensions
|
|
// for each source asset, so remove them all
|
|
foreach (string CookedExtension in CookedExtensions)
|
|
{
|
|
StagedFileReference PathWithExtension = new StagedFileReference(PathWithNoExtension + CookedExtension);
|
|
SC.FilesToStage.UFSFiles.Remove(PathWithExtension);
|
|
StagedUFSFiles.Remove(PathWithExtension);
|
|
}
|
|
|
|
// Some uncooked packages are generated at cook time and do not exist in the uncooked depot.
|
|
// We removed the cooked version of these files from staging above, but we should not add an entry for their
|
|
// non-existent uncooked file. Add them to a list for removal after the loop.
|
|
FileReference FullPathToUncooked = UncookedFile.Value;
|
|
if (!FullPathToUncooked.ToFileInfo().Exists)
|
|
{
|
|
UncookedFilesThatDoNotExist.Add(UncookedFile.Key);
|
|
}
|
|
}
|
|
foreach (StagedFileReference UncookedFile in UncookedFilesThatDoNotExist)
|
|
{
|
|
StagedUncookFiles.Remove(UncookedFile);
|
|
}
|
|
|
|
// stage the filtered UFSFiles
|
|
SC.StageFiles(StagedFileType.UFS, StagedUFSFiles.Values);
|
|
|
|
// stage the Uncooked files now that any cooked ones are removed from SC
|
|
SC.StageFiles(StagedFileType.UFS, StagedUncookFiles.Values);
|
|
|
|
// stage the processed NonUFSFiles
|
|
SC.StageFiles(StagedFileType.NonUFS, StagedNonUFSFiles.Values);
|
|
|
|
// stage the processed DebugNonUFSFiles
|
|
SC.StageFiles(StagedFileType.DebugNonUFS, StagedDebugNonUFSFiles.Values);
|
|
|
|
// now remove or allow restricted files
|
|
HandleRestrictedFiles(SC, ref SC.FilesToStage.UFSFiles);
|
|
HandleRestrictedFiles(SC, ref SC.FilesToStage.NonUFSFiles);
|
|
HandleRestrictedFiles(SC, ref SC.FilesToStage.NonUFSDebugFiles);
|
|
|
|
// remove UFS files if they are also in NonUFS - no need to duplicate
|
|
SC.FilesToStage.UFSFiles = SC.FilesToStage.UFSFiles.Where(x => !SC.FilesToStage.NonUFSFiles.ContainsKey(x.Key) && !SC.FilesToStage.NonUFSDebugFiles.ContainsKey(x.Key)).ToDictionary(x => x.Key, x => x.Value);
|
|
|
|
}
|
|
#region Private implementation
|
|
|
|
private void RemoveReleasedFiles(DeploymentContext SC)
|
|
{
|
|
HashSet<StagedFileReference> ShippedFiles = new HashSet<StagedFileReference>();
|
|
Action<string, string> FindShippedFiles = (string ParamName, string FileNamePortion) =>
|
|
{
|
|
FileReference UFSManifestFile = Command.ParseOptionalFileReferenceParam(ParamName);
|
|
if (UFSManifestFile == null)
|
|
{
|
|
UFSManifestFile = FileReference.Combine(ReleaseMetadataLocation, $"Manifest_{FileNamePortion}_{SC.StageTargetPlatform.PlatformType}.txt");
|
|
}
|
|
if (FileReference.Exists(UFSManifestFile))
|
|
{
|
|
foreach (string Line in File.ReadAllLines(UFSManifestFile.FullName))
|
|
{
|
|
string[] Tokens = Line.Split("\t".ToCharArray());
|
|
if (Tokens?.Length > 1)
|
|
{
|
|
ShippedFiles.Add(new StagedFileReference(Tokens[0]));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
FindShippedFiles("ClientUFSManifest", "UFSFiles");
|
|
FindShippedFiles("ClientNonUFSManifest", "NonUFSFiles");
|
|
FindShippedFiles("ClientDebugManifest", "DebugFiles");
|
|
|
|
ShippedFiles.RemoveWhere(x => x.HasExtension(".ttf") && !x.Name.Contains("LastResort"));
|
|
|
|
UFSFilesToStage.RemoveAll(x => ShippedFiles.Contains(DeploymentContext.MakeRelativeStagedReference(SC, x)));
|
|
NonUFSFilesToStage.RemoveAll(x => ShippedFiles.Contains(DeploymentContext.MakeRelativeStagedReference(SC, x)));
|
|
DebugNonUFSFilesToStage.RemoveAll(x => ShippedFiles.Contains(DeploymentContext.MakeRelativeStagedReference(SC, x)));
|
|
}
|
|
private Dictionary<StagedFileReference, FileReference> MimicStageFiles(DeploymentContext SC, List<FileReference> SourceFiles)
|
|
{
|
|
Dictionary<StagedFileReference, FileReference> Mapping = new Dictionary<StagedFileReference, FileReference>();
|
|
|
|
foreach (FileReference FileRef in new HashSet<FileReference>(SourceFiles))
|
|
{
|
|
DirectoryReference RootDir;
|
|
StagedFileReference StagedFile = DeploymentContext.MakeRelativeStagedReference(SC, FileRef, out RootDir);
|
|
|
|
// add the mapping
|
|
Mapping.Add(StagedFile, FileRef);
|
|
}
|
|
|
|
return Mapping;
|
|
}
|
|
|
|
private void HandleRestrictedFiles(DeploymentContext SC, ref Dictionary<StagedFileReference, FileReference> Files)
|
|
{
|
|
// If a directory has been specifically allowed, do not omit the files wihin it from the staging process.
|
|
if (bIsForExternalDistribution)
|
|
{
|
|
Int32 OrigNumFiles = Files.Count();
|
|
// remove entries where any restricted folder names are in the name remapped path (if we remap from NFL to non-NFL, then we don't remove it)
|
|
// If a configuration file has been explicitly allowed, do not omit it either.
|
|
Files = Files.Where(x => SC.ConfigFilesAllowList.Contains(x.Key) || SC.ExtraFilesAllowList.Contains(x.Key) || SC.DirectoriesAllowList.Contains(x.Key.Directory) || !SC.RestrictedFolderNames.Any(y => DeploymentContext.ApplyDirectoryRemap(SC, x.Key).ContainsName(y))).ToDictionary(x => x.Key, x => x.Value);
|
|
if (OrigNumFiles != Files.Count())
|
|
{
|
|
Log.TraceInformationOnce("Some files were not staged since they have restricted folder names in the remapped path.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log.TraceInformationOnce("Allowing restricted directories to be staged...");
|
|
}
|
|
}
|
|
|
|
private void AddCookedUFSFilesToList(List<FileReference> FileList, string Extension, DeploymentContext SC)
|
|
{
|
|
// look in SC and UFSFiles
|
|
FileList.AddRange(SC.FilesToStage.UFSFiles.Keys.Where(x => x.Name.EndsWith(Extension, StringComparison.OrdinalIgnoreCase)).Select(y => DeploymentContext.UnmakeRelativeStagedReference(SC, y)));
|
|
FileList.AddRange(UFSFilesToStage.Where(x => x.FullName.EndsWith(Extension, StringComparison.InvariantCultureIgnoreCase)));
|
|
}
|
|
|
|
private void AddUFSFilesToList(List<FileReference> FileList, string Extension, DeploymentContext SC)
|
|
{
|
|
// look in SC and UFSFiles
|
|
foreach (var Pair in SC.FilesToStage.UFSFiles)
|
|
{
|
|
if (Pair.Key.Name.EndsWith(Extension))
|
|
{
|
|
FileList.Add(Pair.Value);
|
|
}
|
|
}
|
|
FileList.AddRange(UFSFilesToStage.Where(x => x.FullName.EndsWith(Extension, StringComparison.InvariantCultureIgnoreCase)));
|
|
}
|
|
|
|
private void UncookMaps(DeploymentContext SC)
|
|
{
|
|
string CookedMapMode = ConfigHelper.GetString("MapMode").ToLower();
|
|
if (CookedMapMode == "cooked")
|
|
{
|
|
// nothing to do, they are already staged as cooked as normal
|
|
}
|
|
else if (CookedMapMode == "uncooked")
|
|
{
|
|
// remove maps from SC and Context (SC has path to the cooked map, so we have to come back from Staged reference that doesn't have the Cooked dir in it)
|
|
AddCookedUFSFilesToList(FilesToUncook, ".umap", SC);
|
|
}
|
|
else if (CookedMapMode == "none")
|
|
{
|
|
// remove umaps and their sidecar files so they won't be staged (remove extension so that we remove Foo.umap and Foo.uexp)
|
|
// also remove Foo_BuiltData.*
|
|
HashSet<string> Maps = UFSFilesToStage.Where(x => x.HasExtension("umap")).Select(x => Path.ChangeExtension(x.FullName, null)).ToHashSet();
|
|
UFSFilesToStage = UFSFilesToStage.Where(x => !Maps.Contains(Path.ChangeExtension(x.FullName.Replace("_BuiltData", ""), null))).ToList();
|
|
|
|
Maps = FilesToUncook.Where(x => x.HasExtension("umap")).Select(x => Path.ChangeExtension(x.FullName, null)).ToHashSet();
|
|
FilesToUncook = FilesToUncook.Where(x => !Maps.Contains(Path.ChangeExtension(x.FullName.Replace("_BuiltData", ""), null))).ToList();
|
|
|
|
Maps = SC.FilesToStage.UFSFiles.Keys.Where(x => x.HasExtension("umap")).Select(x => Path.ChangeExtension(x.Name, null)).ToHashSet();
|
|
Console.WriteLine($"Found {Maps.Count()} maps");
|
|
SC.FilesToStage.UFSFiles = SC.FilesToStage.UFSFiles.Where(x => !Maps.Contains(Path.ChangeExtension(x.Key.Name.Replace("_BuiltData", ""), null))).ToDictionary(x => x.Key, x => x.Value);
|
|
|
|
}
|
|
}
|
|
|
|
private void UnUFSFiles(DeploymentContext SC)
|
|
{
|
|
if (bStageUAT)
|
|
{
|
|
// UAT needs uplugin and ini files, so make sure they are not in the .pak
|
|
AddUFSFilesToList(NonUFSFilesToStage, ".uplugin", SC);
|
|
AddUFSFilesToList(NonUFSFilesToStage, ".ini", SC);
|
|
AddUFSFilesToList(NonUFSFilesToStage, "SDK.json", SC);
|
|
NonUFSFilesToStage = NonUFSFilesToStage.Where(x => x.GetFileName() != "BinaryConfig.ini").ToList();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
|
|
public class MakeCookedEditor : BuildCommand
|
|
{
|
|
protected bool bIsCookedCooker;
|
|
protected FileReference ProjectFile;
|
|
|
|
protected ConfigHelper ConfigHelper;
|
|
|
|
// used to remember the locations of stating output exactly as calculated by the staging code
|
|
protected DirectoryReference CookedEditorStageDirectory = null;
|
|
protected DirectoryReference ReleaseStageDirectory = null;
|
|
|
|
// with -makerelease, this will have the location of optional editor only files, but a subclass can just set this if the optional files
|
|
// were made and saved off somewhere, it can point to this and the optional files will be automatically staged into Content/Paks
|
|
protected DirectoryReference ReleaseOptionalFileStageDirectory = null;
|
|
|
|
public override void ExecuteBuild()
|
|
{
|
|
Logger.LogInformation("************************* MakeCookedEditor");
|
|
|
|
bIsCookedCooker = ParseParam("cookedcooker");
|
|
ProjectFile = ParseProjectParam();
|
|
|
|
// set up config sections and the like
|
|
ConfigHelper = new ConfigHelper(BuildHostPlatform.Current.Platform, ProjectFile, bIsCookedCooker);
|
|
|
|
ProjectParams BuildParams = GetParams();
|
|
|
|
|
|
Project.Build(this, BuildParams);
|
|
|
|
// after the editor is built, if we are building against a release, and if desired, make that release first to make sure we have it to build against
|
|
ProjectParams ReleaseParams = null;
|
|
|
|
string MakeReleaseOptions = ParseParamValue("makerelease", "");
|
|
if (MakeReleaseOptions != "")
|
|
{
|
|
if (string.IsNullOrEmpty(BuildParams.BasedOnReleaseVersion))
|
|
{
|
|
throw new AutomationException("-makerelease was specified but the project doesn't have bBuildAgainstRelease set to true");
|
|
}
|
|
|
|
ReleaseParams = GetReleaseParams(BuildParams, MakeReleaseOptions.Split(','));
|
|
Project.Build(this, ReleaseParams);
|
|
Project.Cook(ReleaseParams);
|
|
Project.CopyBuildToStagingDirectory(ReleaseParams);
|
|
|
|
FinalizeRelease(ReleaseParams);
|
|
}
|
|
|
|
Project.Cook(BuildParams);
|
|
Project.CopyBuildToStagingDirectory(BuildParams);
|
|
|
|
//this will do packaging if requested, and also symbol upload if requested.
|
|
Project.Package(BuildParams);
|
|
|
|
Project.Archive(BuildParams);
|
|
PrintRunTime();
|
|
// Project.Deploy(BuildParams);
|
|
|
|
if (ReleaseParams != null)
|
|
{
|
|
string CombinedPath = ParseParamValue("CombineBuilds", "");
|
|
if (CombinedPath != "")
|
|
{
|
|
if (CookedEditorStageDirectory == null || ReleaseStageDirectory == null)
|
|
{
|
|
Logger.LogError("Combining Release and CookedEditor together currently requires that both are staged this run (-stage -makerelease=stage)");
|
|
return;
|
|
}
|
|
|
|
Logger.LogInformation("Combinging {ReleaseStageDirectory} + {CookedEditorStageDirectory} -> {CombinedPath}", ReleaseStageDirectory, CookedEditorStageDirectory, CombinedPath);
|
|
DirectoryReference Combined = new DirectoryReference(CombinedPath);
|
|
CopyDirectory_NoExceptions(ReleaseStageDirectory.FullName, Combined.FullName, CopyDirectoryOptions.Default);
|
|
CopyDirectory_NoExceptions(CookedEditorStageDirectory.FullName, Combined.FullName, CopyDirectoryOptions.Merge);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
protected virtual void StageEngineEditorFiles(ProjectParams Params, DeploymentContext SC, ModifyStageContext Context)
|
|
{
|
|
StagePlatformExtensionFiles(Params, SC, Context, Unreal.EngineDirectory);
|
|
StagePluginFiles(Params, SC, Context, true);
|
|
|
|
// engine shaders
|
|
if (Context.bStageShaderDirs)
|
|
{
|
|
IEnumerable<FileReference> ShaderFiles = DirectoryReference.EnumerateFiles(DirectoryReference.Combine(Unreal.EngineDirectory, "Shaders"), "*", SearchOption.AllDirectories)
|
|
.Where(x => !x.GetExtension().Equals(".cs", StringComparison.OrdinalIgnoreCase));
|
|
Context.NonUFSFilesToStage.AddRange(ShaderFiles);
|
|
GatherTargetDependencies(Params, SC, Context, "ShaderCompileWorker");
|
|
}
|
|
if (bIsCookedCooker)
|
|
{
|
|
GatherTargetDependencies(Params, SC, Context, "UnrealPak");
|
|
}
|
|
|
|
// Stage the editor localization targets
|
|
if (!bIsCookedCooker)
|
|
{
|
|
List<string> CulturesToStage = Project.GetCulturesToStage(Params, ConfigHelper.GameConfig);
|
|
|
|
string[] EditorLocalizationTargetsToStage = { "Category", "Engine", "Editor", "EditorTutorials", "Keywords", "PropertyNames", "ToolTips" };
|
|
foreach (string EditorLocalizationTargetToStage in EditorLocalizationTargetsToStage)
|
|
{
|
|
// Note: We skip "ShouldStageLocalizationTarget" below as games may disable certain targets that they don't need at runtime (eg, Engine), but all of these targets are still needed for an editor!
|
|
//if (Project.ShouldStageLocalizationTarget(SC, null, EditorLocalizationTargetToStage))
|
|
{
|
|
Project.StageLocalizationDataForTarget(SC, CulturesToStage, DirectoryReference.Combine(Unreal.EngineDirectory, "Content", "Localization", EditorLocalizationTargetToStage));
|
|
}
|
|
}
|
|
}
|
|
|
|
StageIniPathArray(Params, SC, "EngineExtraStageFiles", Unreal.EngineDirectory, Context);
|
|
|
|
Context.FilesToUncook.Add(FileReference.Combine(Context.EngineDirectory, "Content/EngineMaterials/DefaultMaterial.uasset"));
|
|
Context.FilesToUncook.Add(FileReference.Combine(Context.EngineDirectory, "Content/EditorLandscapeResources/DefaultAlphaTexture.uasset"));
|
|
}
|
|
|
|
protected virtual void StageProjectEditorFiles(ProjectParams Params, DeploymentContext SC, ModifyStageContext Context)
|
|
{
|
|
// always stage the main exe, in case DLC mode is on, then it won't by default
|
|
if (SC.StageExecutables.Count > 0)
|
|
{
|
|
GatherTargetDependencies(Params, SC, Context, SC.StageExecutables[0]);
|
|
}
|
|
|
|
// project shaders
|
|
if (Context.bStageShaderDirs)
|
|
{
|
|
DirectoryReference ProjectShaders = DirectoryReference.Combine(Context.ProjectDirectory, "Shaders");
|
|
if (DirectoryReference.Exists(ProjectShaders))
|
|
{
|
|
Context.NonUFSFilesToStage.AddRange(DirectoryReference.EnumerateFiles(ProjectShaders, "*", SearchOption.AllDirectories));
|
|
}
|
|
}
|
|
|
|
// .collection files
|
|
if (!bIsCookedCooker && Context.bStageCollections)
|
|
{
|
|
DirectoryReference ProjectCollectionsDirectory = DirectoryReference.Combine(Context.ProjectDirectory, "Content", "Collections");
|
|
if (DirectoryReference.Exists(ProjectCollectionsDirectory))
|
|
{
|
|
Context.DebugNonUFSFilesToStage.AddRange(DirectoryReference.EnumerateFiles(ProjectCollectionsDirectory, "*.collection", SearchOption.TopDirectoryOnly));
|
|
}
|
|
|
|
DirectoryReference ProjectDevelopersDirectory = DirectoryReference.Combine(Context.ProjectDirectory, "Content", "Developers");
|
|
if (DirectoryReference.Exists(ProjectDevelopersDirectory))
|
|
{
|
|
foreach (DirectoryReference DeveloperDirectory in DirectoryReference.EnumerateDirectories(ProjectDevelopersDirectory))
|
|
{
|
|
DirectoryReference DeveloperCollectionsDirectory = DirectoryReference.Combine(DeveloperDirectory, "Collections");
|
|
if (DirectoryReference.Exists(DeveloperCollectionsDirectory))
|
|
{
|
|
Context.DebugNonUFSFilesToStage.AddRange(DirectoryReference.EnumerateFiles(DeveloperCollectionsDirectory, "*.collection", SearchOption.TopDirectoryOnly));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Python scripts
|
|
if (Context.bStagePython)
|
|
{
|
|
DirectoryReference ProjectPythonDirectory = DirectoryReference.Combine(Context.ProjectDirectory, "Content", "Python");
|
|
if (DirectoryReference.Exists(ProjectPythonDirectory))
|
|
{
|
|
Context.NonUFSFilesToStage.AddRange(DirectoryReference.EnumerateFiles(ProjectPythonDirectory, "*", SearchOption.AllDirectories));
|
|
}
|
|
}
|
|
|
|
StagePlatformExtensionFiles(Params, SC, Context, Context.ProjectDirectory);
|
|
StagePluginFiles(Params, SC, Context, false);
|
|
|
|
// add stripped out editor .ini files back in
|
|
Context.UFSFilesToStage.AddRange(DirectoryReference.EnumerateFiles(DirectoryReference.Combine(Context.ProjectDirectory, "Config"), "*Editor*", SearchOption.AllDirectories));
|
|
|
|
StageIniPathArray(Params, SC, "ProjectExtraStageFiles", Context.ProjectDirectory, Context);
|
|
|
|
if (!bIsCookedCooker)
|
|
{
|
|
// the editor AR may be named EditorClientAssetRegistry.bin already, but probably is DevelopmentAssetRegistry.bin, so look for both, and name it EditorClientAssetRegistry
|
|
FileReference EditorAR = FileReference.Combine(Context.CachedEditorDataLocation, "EditorClientAssetRegistry.bin");
|
|
if (!FileReference.Exists(EditorAR))
|
|
{
|
|
EditorAR = FileReference.Combine(Context.CachedEditorDataLocation, "DevelopmentAssetRegistry.bin");
|
|
}
|
|
SC.StageFile(StagedFileType.UFS, EditorAR, new StagedFileReference($"{Context.ProjectName}/EditorClientAssetRegistry.bin"));
|
|
|
|
// this file is optional
|
|
FileReference EditorThumbnails = FileReference.Combine(Context.CachedEditorDataLocation, "CachedEditorThumbnails.bin");
|
|
if (FileReference.Exists(EditorThumbnails))
|
|
{
|
|
SC.StageFile(StagedFileType.UFS, EditorThumbnails, new StagedFileReference($"{Context.ProjectName}/CachedEditorThumbnails.bin"));
|
|
}
|
|
}
|
|
|
|
// if a subclass or -makerelease didn't set ReleaseOptionalFileStageDirectory, then look in the Params for the commandline option
|
|
if (ReleaseOptionalFileStageDirectory == null && !string.IsNullOrEmpty(Params.OptionalFileInputDirectory) &&
|
|
Directory.Exists(Params.OptionalFileInputDirectory))
|
|
{
|
|
ReleaseOptionalFileStageDirectory = new DirectoryReference(Params.OptionalFileInputDirectory);
|
|
}
|
|
|
|
if (ReleaseOptionalFileStageDirectory != null)
|
|
{
|
|
// these files were already staged by the client/release build, so we stage them as NonUFS
|
|
// these could be just copied, but StageFiles handles copying easily
|
|
SC.StageFiles(StagedFileType.NonUFS, ReleaseOptionalFileStageDirectory, StageFilesSearch.AllDirectories, new StagedDirectoryReference($"{Context.ProjectName}/Content/Paks"));
|
|
|
|
Logger.LogInformation("Staging optional files from {Arg0}:", ReleaseOptionalFileStageDirectory.FullName);
|
|
foreach (var OptionalFile in DirectoryReference.EnumerateFiles(ReleaseOptionalFileStageDirectory, "*"))
|
|
{
|
|
Logger.LogInformation(" '{Arg0}'", OptionalFile.FullName);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ReadProjectsRecursively(FileReference File, Dictionary<string, string> InitialProperties, Dictionary<FileReference, CsProjectInfo> FileToProjectInfo)
|
|
{
|
|
// Early out if we've already read this project
|
|
if (!FileToProjectInfo.ContainsKey(File))
|
|
{
|
|
// Try to read this project
|
|
CsProjectInfo ProjectInfo;
|
|
if (!CsProjectInfo.TryRead(File, InitialProperties, out ProjectInfo))
|
|
{
|
|
throw new AutomationException("Couldn't read project '{0}'", File.FullName);
|
|
}
|
|
|
|
// Add it to the project lookup, and try to read all the projects it references
|
|
FileToProjectInfo.Add(File, ProjectInfo);
|
|
foreach (FileReference ProjectReference in ProjectInfo.ProjectReferences.Keys)
|
|
{
|
|
if (!FileReference.Exists(ProjectReference))
|
|
{
|
|
throw new AutomationException("Unable to find project '{0}' referenced by '{1}'", ProjectReference, File);
|
|
}
|
|
ReadProjectsRecursively(ProjectReference, InitialProperties, FileToProjectInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual void StageUAT(ProjectParams Params, DeploymentContext SC, ModifyStageContext Context)
|
|
{
|
|
// now look in the .json file that UAT made that lists its script files
|
|
DirectoryReference AutomationToolBinaryDir = DirectoryReference.Combine(Context.EngineDirectory, "Binaries", "DotNET", "AutomationTool");
|
|
DirectoryReference UnrealBuildToolBinaryDir = DirectoryReference.Combine(Context.EngineDirectory, "Binaries", "DotNET", "UnrealBuildTool");
|
|
DirectoryReference ProjectAutomationToolBinaryDir = DirectoryReference.Combine(Context.ProjectDirectory, "Binaries", "DotNET", "AutomationTool");
|
|
|
|
// some netcore dependencies in the tool directories can't be discovered with CsProjectInfo, so just stage the enture UBT and UAT directories
|
|
Context.NonUFSFilesToStage.AddRange(DirectoryReference.EnumerateFiles(UnrealBuildToolBinaryDir, "*", SearchOption.AllDirectories));
|
|
Context.NonUFSFilesToStage.AddRange(DirectoryReference.EnumerateFiles(AutomationToolBinaryDir, "*", SearchOption.AllDirectories));
|
|
|
|
StagedDirectoryReference StagedBinariesDir = new StagedDirectoryReference("Engine/Binaries/DotNET/AutomationTool");
|
|
|
|
// look in Engine/Intermediate/ScriptModules and Project/Intermediate/ScriptModules
|
|
DirectoryReference EngineScriptModulesDir = DirectoryReference.Combine(Context.EngineDirectory, "Intermediate", "ScriptModules");
|
|
DirectoryReference ProjectScriptModulesDir = DirectoryReference.Combine(Context.ProjectDirectory, "Intermediate", "ScriptModules");
|
|
IEnumerable<FileReference> JsonFiles = DirectoryReference.EnumerateFiles(EngineScriptModulesDir);
|
|
if (DirectoryReference.Exists(ProjectScriptModulesDir))
|
|
{
|
|
JsonFiles = JsonFiles.Concat(DirectoryReference.EnumerateFiles(ProjectScriptModulesDir));
|
|
}
|
|
foreach (FileReference JsonFile in JsonFiles)
|
|
{
|
|
try
|
|
{
|
|
// load build info
|
|
CsProjBuildRecord BuildRecord = JsonSerializer.Deserialize<CsProjBuildRecord>(FileReference.ReadAllText(JsonFile));
|
|
|
|
Context.NonUFSFilesToStage.Add(JsonFile);
|
|
|
|
// get location of the project where the other paths are relative to
|
|
DirectoryReference RecordRoot = FileReference.Combine(JsonFile.Directory, BuildRecord.ProjectPath).Directory;
|
|
string FullPath = RecordRoot.FullName;
|
|
|
|
// stage the output and everything it pulled in next to it
|
|
foreach (FileReference TargetDirFile in DirectoryReference.EnumerateFiles(FileReference.Combine(RecordRoot, BuildRecord.TargetPath).Directory, "*", SearchOption.AllDirectories))
|
|
{
|
|
Context.NonUFSFilesToStage.Add(TargetDirFile);
|
|
}
|
|
|
|
// now pull in any dependencies in case something loads it by path
|
|
foreach (string Dep in BuildRecord.Dependencies)
|
|
{
|
|
if (Path.GetExtension(Dep).ToLower() == ".dll")
|
|
{
|
|
FileReference DepFile = FileReference.Combine(RecordRoot, Dep);
|
|
// if ht's not in the engine or the project, we will just stage it next to the .exe, in a last ditch effort
|
|
if (!DepFile.IsUnderDirectory(Context.EngineDirectory) && !DepFile.IsUnderDirectory(Context.ProjectDirectory))
|
|
{
|
|
SC.StageFile(StagedFileType.NonUFS, DepFile, StagedFileReference.Combine(StagedBinariesDir, DepFile.GetFileName()));
|
|
}
|
|
else
|
|
{
|
|
Context.NonUFSFilesToStage.Add(DepFile);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch(Exception)
|
|
{
|
|
// skip json files that fail
|
|
}
|
|
}
|
|
|
|
if (Context.IniPlatformName == "Linux")
|
|
{
|
|
// linux needs dotnet runtime
|
|
SC.StageFiles(StagedFileType.NonUFS, Unreal.FindDotnetDirectoryForPlatform(RuntimePlatform.Type.Linux), StageFilesSearch.AllDirectories);
|
|
}
|
|
|
|
// not sure if we need this or not now
|
|
|
|
// ask each platform if they need extra files
|
|
//foreach (UnrealTargetPlatform Platform in UnrealTargetPlatform.GetValidPlatforms())
|
|
//{
|
|
// List<FileReference> Files = new List<FileReference>();
|
|
// AutomationTool.Platform.GetPlatform(Platform).GetPlatformUATDependencies(Context.ProjectDirectory, Files);
|
|
// Context.NonUFSFilesToStage.AddRange(Files.Where(x => FileReference.Exists(x)));
|
|
//}
|
|
}
|
|
|
|
protected virtual void StagePluginDirectory(DirectoryReference PluginDir, ModifyStageContext Context, bool bStageUncookedContent)
|
|
{
|
|
foreach (DirectoryReference Subdir in DirectoryReference.EnumerateDirectories(PluginDir))
|
|
{
|
|
StagePluginSubdirectory(Subdir, Context, bStageUncookedContent);
|
|
}
|
|
}
|
|
|
|
protected virtual void StagePluginSubdirectory(DirectoryReference PluginSubdir, ModifyStageContext Context, bool bStageUncookedContent)
|
|
{
|
|
string DirNameLower = PluginSubdir.GetDirectoryName().ToLower();
|
|
|
|
if (DirNameLower == "content")
|
|
{
|
|
if (bStageUncookedContent)
|
|
{
|
|
Context.FilesToUncook.AddRange(DirectoryReference.EnumerateFiles(PluginSubdir, "*", SearchOption.AllDirectories));
|
|
}
|
|
else
|
|
{
|
|
Context.UFSFilesToStage.AddRange(DirectoryReference.EnumerateFiles(PluginSubdir, "*", SearchOption.AllDirectories));
|
|
}
|
|
}
|
|
else if (DirNameLower == "resources" || DirNameLower == "config" || DirNameLower == "scripttemplates")
|
|
{
|
|
Context.UFSFilesToStage.AddRange(DirectoryReference.EnumerateFiles(PluginSubdir, "*", SearchOption.AllDirectories));
|
|
}
|
|
else if (DirNameLower == "documentation" && Directory.Exists(PluginSubdir.FullName + "/Source/Shared"))
|
|
{
|
|
Context.UFSFilesToStage.AddRange(DirectoryReference.EnumerateFiles(PluginSubdir, "Source/Shared/*", SearchOption.AllDirectories));
|
|
}
|
|
else if (DirNameLower == "shaders" && Context.bStageShaderDirs)
|
|
{
|
|
Context.NonUFSFilesToStage.AddRange(DirectoryReference.EnumerateFiles(PluginSubdir, "*", SearchOption.AllDirectories));
|
|
}
|
|
}
|
|
|
|
protected virtual ModifyStageContext CreateContext(ProjectParams Params, DeploymentContext SC)
|
|
{
|
|
return new ModifyStageContext(ConfigHelper, Unreal.EngineDirectory, Params, SC, this);
|
|
}
|
|
|
|
protected virtual void ModifyParams(ProjectParams BuildParams)
|
|
{
|
|
}
|
|
|
|
protected virtual void ModifyReleaseParams(ProjectParams ReleaseParams)
|
|
{
|
|
}
|
|
|
|
protected virtual void FinalizeRelease(ProjectParams ReleaseParams)
|
|
{
|
|
}
|
|
|
|
protected virtual void PreModifyDeploymentContext(ProjectParams Params, DeploymentContext SC)
|
|
{
|
|
ModifyStageContext Context = CreateContext(Params, SC);
|
|
|
|
DefaultPreModifyDeploymentContext(Params, SC, Context);
|
|
|
|
Context.Apply(SC);
|
|
}
|
|
|
|
protected virtual void ModifyDeploymentContext(ProjectParams Params, DeploymentContext SC)
|
|
{
|
|
ModifyStageContext Context = CreateContext(Params, SC);
|
|
|
|
DefaultModifyDeploymentContext(Params, SC, Context);
|
|
|
|
Context.Apply(SC);
|
|
|
|
// we do this after the apply to make sure we get any SC and Context based staging
|
|
if (bIsCookedCooker)
|
|
{
|
|
// cooker can run with just the -Cmd, so we reduce the size byt removing the non-Cmd executable and debug info (this is sizeable for monolithic editors)
|
|
string MainCookedTarget = Params.ServerCookedTargets[0];
|
|
// @todo mac
|
|
SC.FilesToStage.NonUFSFiles.Remove(new StagedFileReference(Path.Combine(Context.ProjectName, "Binaries", "Win64", MainCookedTarget + ".exe")));
|
|
SC.FilesToStage.NonUFSFiles.Remove(new StagedFileReference(Path.Combine(Context.ProjectName, "Binaries", "Linux", MainCookedTarget)));
|
|
SC.FilesToStage.NonUFSDebugFiles.Remove(new StagedFileReference(Path.Combine(Context.ProjectName, "Binaries", "Win64", MainCookedTarget + ".pdb")));
|
|
SC.FilesToStage.NonUFSDebugFiles.Remove(new StagedFileReference(Path.Combine(Context.ProjectName, "Binaries", "Linux", MainCookedTarget + ".sym")));
|
|
// todo: find out why pdbs are in the non-debug file list and get rid of them
|
|
SC.FilesToStage.NonUFSFiles.Remove(new StagedFileReference(Path.Combine(Context.ProjectName, "Binaries", "Win64", MainCookedTarget + ".pdb")));
|
|
SC.FilesToStage.NonUFSFiles.Remove(new StagedFileReference(Path.Combine(Context.ProjectName, "Binaries", "Linux", MainCookedTarget + ".sym")));
|
|
}
|
|
|
|
CookedEditorStageDirectory = SC.StageDirectory;
|
|
}
|
|
|
|
protected virtual void FinalizeDeploymentContext(ProjectParams Params, DeploymentContext SC)
|
|
{
|
|
}
|
|
|
|
protected virtual void SetupDLCMode(FileReference ProjectFile, out string DLCName, out string ReleaseVersion, out TargetType Type)
|
|
{
|
|
bool bBuildAgainstRelease = ConfigHelper.GetBool("bBuildAgainstRelease");
|
|
|
|
if (ParseParamValue("MakeRelease", null) != null && !bBuildAgainstRelease)
|
|
{
|
|
Logger.LogWarning("-makerelease is meant for projects that have bBuildAgainstRelease set. Will force it on, with default settings for [DLCPluginName, ReleaseName, ReleaseTargetType]");
|
|
bBuildAgainstRelease = true;
|
|
}
|
|
|
|
if (bBuildAgainstRelease)
|
|
{
|
|
DLCName = ConfigHelper.GetString("DLCPluginName");
|
|
ReleaseVersion = ConfigHelper.GetString("ReleaseName");
|
|
|
|
// if not set, default to gamename
|
|
if (string.IsNullOrEmpty(ReleaseVersion))
|
|
{
|
|
ReleaseVersion = ProjectFile.GetFileNameWithoutAnyExtensions();
|
|
}
|
|
|
|
string TargetTypeString;
|
|
TargetTypeString = ConfigHelper.GetString("ReleaseTargetType");
|
|
Type = (TargetType)Enum.Parse(typeof(TargetType), TargetTypeString);
|
|
}
|
|
else
|
|
{
|
|
DLCName = null;
|
|
ReleaseVersion = null;
|
|
Type = TargetType.Game;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected void StagePlatformExtensionFiles(ProjectParams Params, DeploymentContext SC, ModifyStageContext Context, DirectoryReference RootDir)
|
|
{
|
|
if (!Context.bStagePlatformDirs)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
// plugins are already handled in the Plugins staging code
|
|
List<string> RootFoldersToStrip = new List<string> { "source", "plugins" };//, "binaries" };
|
|
List<string> SubFoldersToStrip = new List<string> { "source", "intermediate", "tests", "binaries" + Path.DirectorySeparatorChar + HostPlatform.Current.HostEditorPlatform.ToString().ToLower() };
|
|
List<string> RootNonUFSFolders = new List<string> { "shaders", "binaries", "build", "extras" };
|
|
|
|
|
|
if (!Context.bStageShaderDirs)
|
|
{
|
|
RootFoldersToStrip.Add("shaders");
|
|
}
|
|
if (!Context.bStagePlatformBuildDirs)
|
|
{
|
|
RootFoldersToStrip.Add("build");
|
|
}
|
|
if (!Context.bStageExtrasDirs)
|
|
{
|
|
RootFoldersToStrip.Add("extras");
|
|
}
|
|
|
|
foreach (DirectoryReference PlatformDir in Unreal.GetExtensionDirs(RootDir, true, false, false))
|
|
{
|
|
foreach (DirectoryReference Subdir in DirectoryReference.EnumerateDirectories(PlatformDir, "*", SearchOption.TopDirectoryOnly))
|
|
{
|
|
string SubdirName = Subdir.GetDirectoryName().ToLower();
|
|
|
|
// Remvoe some unnecessary folders that can be large
|
|
List<FileReference> ContextFileList = Context.UFSFilesToStage;
|
|
|
|
// some files need to be NonUFS for C# etc to access
|
|
if (RootNonUFSFolders.Contains(SubdirName))
|
|
{
|
|
ContextFileList = Context.NonUFSFilesToStage;
|
|
}
|
|
|
|
List<FileReference> FilesToStage = new List<FileReference>();
|
|
// if we aren't in a bad subdir, add files
|
|
if (!RootFoldersToStrip.Contains(SubdirName))
|
|
{
|
|
FilesToStage.AddRange(DirectoryReference.EnumerateFiles(Subdir, "*", SearchOption.AllDirectories));
|
|
|
|
// now remove files in subdirs we want to skip
|
|
FilesToStage.RemoveAll(x => x.ContainsAnyNames(SubFoldersToStrip, Subdir));
|
|
ContextFileList.AddRange(FilesToStage);
|
|
}
|
|
|
|
if (SubdirName == "config")
|
|
{
|
|
Context.NonUFSFilesToStage.AddRange(DirectoryReference.EnumerateFiles(Subdir, "*SDK.json", SearchOption.AllDirectories));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void StagePluginFiles(ProjectParams Params, DeploymentContext SC, ModifyStageContext Context, bool bEnginePlugins)
|
|
{
|
|
List<FileReference> ActivePlugins = new List<FileReference>();
|
|
foreach (StageTarget Target in SC.StageTargets)
|
|
{
|
|
if (Target.Receipt.TargetType == TargetType.Editor)
|
|
{
|
|
IEnumerable<RuntimeDependency> TargetPlugins = Target.Receipt.RuntimeDependencies.Where(x => x.Path.GetExtension().ToLower() == ".uplugin");
|
|
// grab just engine plugins, or non-engine plugins depending
|
|
TargetPlugins = TargetPlugins.Where(x => (bEnginePlugins ? x.Path.IsUnderDirectory(Unreal.EngineDirectory) : !x.Path.IsUnderDirectory(Unreal.EngineDirectory)));
|
|
|
|
// convert to paths
|
|
ActivePlugins.AddRange(TargetPlugins.Select(x => x.Path));
|
|
}
|
|
}
|
|
|
|
foreach (FileReference ActivePlugin in ActivePlugins)
|
|
{
|
|
PluginInfo Plugin = new PluginInfo(ActivePlugin, bEnginePlugins ? PluginType.Engine : PluginType.Project);
|
|
// we don't cook for unsupported target platforms, but the plugin may still need to be used in the editor, so
|
|
// stage uncooked assets for these plugins
|
|
bool bStageUncookedContent = (!Plugin.Descriptor.SupportsTargetPlatform(SC.StageTargetPlatform.PlatformType));
|
|
|
|
StagePluginDirectory(ActivePlugin.Directory, Context, bStageUncookedContent);
|
|
}
|
|
|
|
}
|
|
|
|
protected void StageIniPathArray(ProjectParams Params, DeploymentContext SC, string IniKey, DirectoryReference BaseDirectory, ModifyStageContext Context)
|
|
{
|
|
HashSet<string> Entries = new HashSet<string>();
|
|
|
|
// read the ini for all platforms, and merge together to remove duplicates
|
|
foreach (UnrealTargetPlatform Platform in UnrealTargetPlatform.GetValidPlatforms())
|
|
{
|
|
ConfigHelper PlatformHelper = new ConfigHelper(Platform, ProjectFile, bIsCookedCooker);
|
|
Entries.UnionWith(ConfigHelper.GetArray(IniKey));
|
|
}
|
|
|
|
foreach (string Entry in Entries)
|
|
{
|
|
Dictionary<string, string> Props = ConfigHierarchy.GetStructKeyValuePairs(Entry);
|
|
|
|
string SubPath = Props["Path"];
|
|
string FileWildcard = "*";
|
|
List<FileReference> FileList = Context.UFSFilesToStage;
|
|
SearchOption SearchMode = SearchOption.AllDirectories;
|
|
if (Props.ContainsKey("Files"))
|
|
{
|
|
FileWildcard = Props["Files"];
|
|
}
|
|
if (Props.ContainsKey("NonUFS") && bool.Parse(Props["NonUFS"]) == true)
|
|
{
|
|
FileList = Context.NonUFSFilesToStage;
|
|
}
|
|
if (Props.ContainsKey("Recursive") && bool.Parse(Props["Recursive"]) == false)
|
|
{
|
|
SearchMode = SearchOption.TopDirectoryOnly;
|
|
}
|
|
|
|
bool bHasDest = Props.ContainsKey("Dest");
|
|
bool bHasIniSections = Props.ContainsKey("IniSections");
|
|
|
|
// settings with a Dest or IniSections are special, and have to go right to SC
|
|
if (bHasDest || bHasIniSections)
|
|
{
|
|
FileReference SourceFile = FileReference.Combine(BaseDirectory, SubPath, FileWildcard);
|
|
|
|
// allow a blank Path to mean empty file
|
|
if (SubPath == "")
|
|
{
|
|
SourceFile = FileReference.Combine(Context.EngineDirectory, "Intermediate/blankfile.txt");
|
|
FileReference.WriteAllText(SourceFile, "");
|
|
FileWildcard = "";
|
|
}
|
|
|
|
if (SearchMode == SearchOption.AllDirectories || FileWildcard.Contains("*"))
|
|
{
|
|
throw new AutomationException($"Unable to stage directories with \"Dest\" or \"IniSections\" setting for CookedEditor: '{Entry}'");
|
|
}
|
|
|
|
// setup Dest
|
|
StagedFileReference DestFile;
|
|
if (bHasDest)
|
|
{
|
|
DestFile = new StagedFileReference(Props["Dest"]);
|
|
}
|
|
else
|
|
{
|
|
DestFile = DeploymentContext.MakeRelativeStagedReference(SC, SourceFile);
|
|
}
|
|
|
|
if (bHasIniSections)
|
|
{
|
|
if (!SourceFile.HasExtension(".ini"))
|
|
{
|
|
throw new AutomationException($"Unable to stage non-ini file '{Entry}' using \"IniSections\" setting for CookedEditor: ");
|
|
}
|
|
|
|
FileReference IntermediateFile = FileReference.Combine(Context.ProjectDirectory, "Intermediate", "StagedIniSections", DeploymentContext.MakeRelativeStagedReference(SC, SourceFile).ToString());
|
|
InternalUtils.SafeCopyFile(SourceFile.ToString(), IntermediateFile.ToString(), IniSectionAllowList : Props["IniSections"].Split(',').ToList(), bSafeCreateDirectory : true);
|
|
SourceFile = IntermediateFile;
|
|
}
|
|
|
|
// now stage it to a different location as specified in the params
|
|
StagedFileType FileType = (FileList == Context.NonUFSFilesToStage) ? StagedFileType.NonUFS : StagedFileType.UFS;
|
|
if (Props.ContainsKey("Force") && bool.Parse(Props["Force"]) == true)
|
|
{
|
|
SC.ExtraFilesAllowList.Add(DestFile);
|
|
}
|
|
SC.StageFile(FileType, SourceFile, DestFile);
|
|
continue;
|
|
}
|
|
|
|
// now enumerate files based on the settings
|
|
DirectoryReference Dir = DirectoryReference.Combine(BaseDirectory, SubPath);
|
|
if (DirectoryReference.Exists(Dir))
|
|
{
|
|
if (Props.ContainsKey("Force") && bool.Parse(Props["Force"]) == true)
|
|
{
|
|
foreach (FileReference File in DirectoryReference.EnumerateFiles(Dir, FileWildcard, SearchMode))
|
|
{
|
|
SC.ExtraFilesAllowList.Add(DeploymentContext.MakeRelativeStagedReference(SC, File));
|
|
FileList.Add(File);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FileList.AddRange(DirectoryReference.EnumerateFiles(Dir, FileWildcard, SearchMode));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
protected void DefaultPreModifyDeploymentContext(ProjectParams Params, DeploymentContext SC, ModifyStageContext Context)
|
|
{
|
|
|
|
}
|
|
protected void DefaultModifyDeploymentContext(ProjectParams Params, DeploymentContext SC, ModifyStageContext Context)
|
|
{
|
|
// this will make sure that uncooked packages (maps, etc) go into the .pak, NOT the IOStore, which will fail to package them from a different location
|
|
SC.OnlyAllowPackagesFromStdCookPathInIoStore = true;
|
|
|
|
// if this is for internal use, then we allow all restricted directories and ini settings
|
|
if (!Context.bIsForExternalDistribution)
|
|
{
|
|
SC.RestrictedFolderNames.Clear();
|
|
|
|
if (SC.IniKeyDenyList != null)
|
|
{
|
|
SC.IniKeyDenyList.Clear();
|
|
}
|
|
if (SC.IniSectionDenyList != null)
|
|
{
|
|
SC.IniSectionDenyList.Clear();
|
|
}
|
|
}
|
|
|
|
StageEngineEditorFiles(Params, SC, Context);
|
|
StageProjectEditorFiles(Params, SC, Context);
|
|
|
|
// we need a better decision for this
|
|
if (Context.bStageUAT)
|
|
{
|
|
StageUAT(Params, SC, Context);
|
|
}
|
|
|
|
|
|
// final filtering
|
|
|
|
// we already cooked assets, so remove assets we may have found, except for the Uncook ones
|
|
Context.UFSFilesToStage.RemoveAll(x => x.GetExtension() == ".uasset");
|
|
|
|
if (!Context.bStageTargetFiles)
|
|
{
|
|
// don't need the .target files
|
|
Context.NonUFSFilesToStage.RemoveAll(x => x.GetExtension() == ".target");
|
|
}
|
|
|
|
if (!Context.bStageShaderDirs)
|
|
{
|
|
// don't need standalone shaders
|
|
Context.UFSFilesToStage.RemoveAll(x => x.GetExtension() == ".glsl");
|
|
Context.UFSFilesToStage.RemoveAll(x => x.GetExtension() == ".hlsl");
|
|
}
|
|
|
|
// move some files from UFS to NonUFS if they ended up there
|
|
List<string> UFSIncompatibleExtensions = new List<string> { ".py", ".pyc", ".pyd" };
|
|
Context.NonUFSFilesToStage.AddRange(Context.UFSFilesToStage.Where(x => UFSIncompatibleExtensions.Contains(x.GetExtension())));
|
|
Context.UFSFilesToStage.RemoveAll(x => UFSIncompatibleExtensions.Contains(x.GetExtension()));
|
|
Context.NonUFSFilesToStage.AddRange(Context.UFSFilesToStage.Where(x => x.ContainsName("Python", 0) || x.ContainsName("__pycache__", 0)));
|
|
Context.UFSFilesToStage.RemoveAll(x => x.ContainsName("Python", 0) || x.ContainsName("__pycache__", 0));
|
|
|
|
// strip Python data now that it's all been moved to NonUFSFilesToStage
|
|
bool bShouldStagePython = !bIsCookedCooker && Context.bStagePython;
|
|
if (bShouldStagePython)
|
|
{
|
|
// strip out any leftover static library (.lib or .a) or source code (.c, .cpp, .h, .hpp, .inl) files within any Python script directories,
|
|
// as pre-compiled modules sometimes leave these in their build alongside the resultant .dll or .pyd files
|
|
List<string> NonPythonFilesToRemove = new List<string> { ".lib", ".a", ".c", ".cpp", ".h", ".hpp", ".inl" };
|
|
Context.NonUFSFilesToStage.RemoveAll(x => x.ContainsName("Python", 0) && NonPythonFilesToRemove.Contains(x.GetExtension()));
|
|
}
|
|
else
|
|
{
|
|
// strip out any Python script directories and .py or .pyd files if we're not staging Python
|
|
int PreviousCount = Context.NonUFSFilesToStage.Count;
|
|
Context.NonUFSFilesToStage.RemoveAll(x => x.ContainsName("Python", 0));
|
|
Context.NonUFSFilesToStage.RemoveAll(x => x.GetExtension() == ".py" || x.GetExtension() == ".pyd");
|
|
Logger.LogInformation("Removed {0} Python script file(s) from NonUFSFilesToStage", PreviousCount - Context.NonUFSFilesToStage.Count);
|
|
}
|
|
{
|
|
// strip out any __pycache__ directories and .pyc files regardless of whether we're staging Python (cache files shouldn't be staged)
|
|
int PreviousCount = Context.NonUFSFilesToStage.Count;
|
|
Context.NonUFSFilesToStage.RemoveAll(x => x.ContainsName("__pycache__", 0));
|
|
Context.NonUFSFilesToStage.RemoveAll(x => x.GetExtension() == ".pyc");
|
|
Logger.LogInformation("Removed {0} Python cache file(s) from NonUFSFilesToStage", PreviousCount - Context.NonUFSFilesToStage.Count);
|
|
}
|
|
|
|
// .tps files can all live in UFS
|
|
// we do this after stripping Python, as the Python content folders may have .tps files within them
|
|
Context.UFSFilesToStage.AddRange(Context.NonUFSFilesToStage.Where(x => x.GetExtension() == ".tps"));
|
|
Context.NonUFSFilesToStage.RemoveAll(x => x.GetExtension() == ".tps");
|
|
}
|
|
|
|
protected virtual string GetReleaseTargetName(UnrealTargetPlatform Platform, TargetType ReleaseType)
|
|
{
|
|
// make the platform name, like "WindowsClient", or "LinuxGame", of the premade build we are cooking/staging against
|
|
string IniPlatformName = ConfigHierarchy.GetIniPlatformName(Platform);
|
|
string ReleaseTargetName = IniPlatformName + (ReleaseType == TargetType.Game ? "" : ReleaseType.ToString());
|
|
|
|
return ReleaseTargetName;
|
|
}
|
|
|
|
protected virtual string GetReleaseVersionPath(string ReleaseVersionName, string ReleaseTargetName)
|
|
{
|
|
return CommandUtils.CombinePaths(ProjectFile.Directory.FullName, "Releases", ReleaseVersionName, ReleaseTargetName);
|
|
}
|
|
|
|
protected virtual ProjectParams MakeParams(string DLCName, string BasedOnReleaseVersion)
|
|
{
|
|
return new ProjectParams(
|
|
Command: this
|
|
, RawProjectPath: ProjectFile
|
|
|
|
, NoBootstrapExe: true
|
|
, DLCName: DLCName
|
|
, BasedOnReleaseVersion: BasedOnReleaseVersion
|
|
, DedicatedServer: bIsCookedCooker
|
|
, NoClient: bIsCookedCooker
|
|
, OptionalContent: true
|
|
);
|
|
}
|
|
|
|
private ProjectParams GetParams()
|
|
{
|
|
// setup DLC defaults, then ask project if it should
|
|
string DLCName;
|
|
string BasedOnReleaseVersion;
|
|
TargetType ReleaseType;
|
|
SetupDLCMode(ProjectFile, out DLCName, out BasedOnReleaseVersion, out ReleaseType);
|
|
bool bIsDLC = DLCName != null;
|
|
|
|
ProjectParams Params = MakeParams(DLCName, BasedOnReleaseVersion);
|
|
|
|
// cook the cooked editor targetplatorm as the "client"
|
|
//Params.ClientCookedTargets.Add("CrashReportClientEditor");
|
|
|
|
string TargetPlatformType = bIsCookedCooker ? "CookedCooker" : "CookedEditor";
|
|
string TargetName = ConfigHelper.GetString(bIsCookedCooker ? "CookedCookerTargetName" : "CookedEditorTargetName");
|
|
UnrealTargetPlatform Platform = bIsCookedCooker ? Params.ServerTargetPlatforms[0].Type : Params.ClientTargetPlatforms[0].Type;
|
|
|
|
|
|
// look to see if ini didn't override target name
|
|
if (string.IsNullOrEmpty(TargetName))
|
|
{
|
|
// if not, then use ProjectCookedEditor
|
|
TargetName = ProjectFile.GetFileNameWithoutAnyExtensions() + TargetPlatformType;
|
|
}
|
|
|
|
string OverrideBasedOnReleaseVersion = ParseParamValue("BasedOnReleaseVersion", null);
|
|
if (!string.IsNullOrEmpty(OverrideBasedOnReleaseVersion))
|
|
{
|
|
Params.BasedOnReleaseVersion = OverrideBasedOnReleaseVersion;
|
|
}
|
|
|
|
// control the server/client taregts
|
|
Params.ServerCookedTargets.Clear();
|
|
Params.ClientCookedTargets.Clear();
|
|
List<TargetPlatformDescriptor> TargetPlatformList = new List<TargetPlatformDescriptor>() { new TargetPlatformDescriptor(Platform, TargetPlatformType) };
|
|
if (bIsCookedCooker)
|
|
{
|
|
Params.EditorTargets.Add(TargetName);
|
|
Params.ServerCookedTargets.Add(TargetName);
|
|
Params.ServerTargetPlatforms = TargetPlatformList;
|
|
}
|
|
else
|
|
{
|
|
Params.ClientCookedTargets.Add(TargetName);
|
|
Params.ClientTargetPlatforms = TargetPlatformList;
|
|
}
|
|
|
|
|
|
// when making cooked editors, we some special commandline options to override some assumptions about editor data
|
|
Params.AdditionalCookerOptions += " -ini:Engine:[Core.System]:CanStripEditorOnlyExportsAndImports=False";
|
|
|
|
// when making cooked editors, we need to generate package data in our asset registry so that the cooker can actually find various things on disc
|
|
Params.AdditionalCookerOptions += " -ini:Engine:[AssetRegistry]:bSerializePackageData=True";
|
|
|
|
// We tend to "over-cook" packages to get everything we might need, so some non-editor BPs that are referencing editor BPs may
|
|
// get cooked. This is okay, because the editor stuff should exist. We may want to revist this, and not cook anything that would
|
|
// cause the issues
|
|
Params.AdditionalCookerOptions += " -AllowUnsafeBlueprintCalls";
|
|
|
|
// if we are cooking the editor in Dlc mode, then we want to attempt to cook assets that the base game may have skipped, but still
|
|
// put into the AssetRegistry, so by default the DLC cooker will skip them. this will make the DLC cooker reevaluate these pacakges
|
|
// and choose to cook them or not (generally for editoronly assets)
|
|
Params.AdditionalCookerOptions += " -DlcReevaluateUncookedAssets";
|
|
|
|
string AssetRegistryCacheRootFolder = ParseParamValue("AssetRegistryCacheRootFolder", "");
|
|
if (!string.IsNullOrEmpty(AssetRegistryCacheRootFolder))
|
|
{
|
|
Params.AdditionalCookerOptions += string.Format(" -AssetRegistryCacheRootFolder={0}", AssetRegistryCacheRootFolder);
|
|
}
|
|
|
|
// set up cooking against a client, as DLC
|
|
if (bIsDLC)
|
|
{
|
|
// cook and stage into our project, instead of the Engine's plugins
|
|
DirectoryReference BaseOutputDirectory = DirectoryReference.Combine(ProjectFile.Directory, "Saved", "CookedEditor");
|
|
string TargetPlatformName = ConfigHierarchy.GetIniPlatformName(Platform) + TargetPlatformList[0].CookFlavor;
|
|
// Only override the defaults if something hasn't already overridden the potentially from the commandline.
|
|
if (string.IsNullOrEmpty(Params.CookOutputDir))
|
|
{
|
|
Params.CookOutputDir = DirectoryReference.Combine(BaseOutputDirectory, "Cooked", TargetPlatformName).FullName;
|
|
}
|
|
if (string.IsNullOrEmpty(Params.StageDirectoryParam))
|
|
{
|
|
Params.StageDirectoryParam = DirectoryReference.Combine(BaseOutputDirectory, "Staged").FullName;
|
|
}
|
|
|
|
// make WindowsClient or LinuxGame, etc
|
|
string ReleaseTargetName = GetReleaseTargetName(Platform, ReleaseType);
|
|
|
|
Params.AdditionalCookerOptions += " -CookAgainstFixedBase";
|
|
|
|
string DevelopmentAssetRegistryPlatformOverride = ParseParamValue("DevelopmentAssetRegistryPlatformOverride", ReleaseTargetName);
|
|
Params.AdditionalCookerOptions += $" -DevelopmentAssetRegistryPlatformOverride={DevelopmentAssetRegistryPlatformOverride}";
|
|
Params.AdditionalIoStoreOptions += $" -DevelopmentAssetRegistryPlatformOverride={DevelopmentAssetRegistryPlatformOverride}";
|
|
|
|
// point to where the premade asset registry can be found
|
|
Params.BasedOnReleaseVersionPathOverride = GetReleaseVersionPath(BasedOnReleaseVersion, ReleaseTargetName);
|
|
|
|
Params.DLCOverrideStagedSubDir = "";
|
|
Params.DLCIncludeEngineContent = true;
|
|
}
|
|
|
|
// set up override functions
|
|
Params.PreModifyDeploymentContextCallback = (P, SC) => PreModifyDeploymentContext(P, SC);
|
|
Params.ModifyDeploymentContextCallback = (P, SC) => ModifyDeploymentContext(P, SC);
|
|
Params.FinalizeDeploymentContextCallback = (P, SC) => FinalizeDeploymentContext(P, SC);
|
|
|
|
// this will make all of the files that are specified in BUild.cs files with "AdditionalPropertiesForReceipt.Add("CookerSupportFiles", ...);" be copied into this
|
|
// subdirectory along with a batch file that can be used to set platform SDK environment variables during cooking
|
|
Params.CookerSupportFilesSubdirectory = "SDK";
|
|
|
|
ModifyParams(Params);
|
|
|
|
return Params;
|
|
}
|
|
|
|
private ProjectParams GetReleaseParams(ProjectParams MainParams, string[] Options)
|
|
{
|
|
ProjectParams ReleaseParams = new ProjectParams(
|
|
Command: this
|
|
, RawProjectPath: MainParams.RawProjectPath
|
|
|
|
, Client: MainParams.Client
|
|
, CreateReleaseVersion: MainParams.BasedOnReleaseVersion
|
|
// tell the Params that we want cooked data
|
|
, Build: true
|
|
, Cook: true
|
|
, Stage: true
|
|
// MainParams already builds the editor
|
|
, SkipBuildEditor: true
|
|
, NoBootstrapExe: true
|
|
// if the param to build/cook is specified, then actually build/cook, otherwise assume the Release is already built/cooked
|
|
, SkipBuildClient: !Options.Contains("build", StringComparer.InvariantCultureIgnoreCase)
|
|
, SkipCook: !Options.Contains("cook", StringComparer.InvariantCultureIgnoreCase)
|
|
, SkipStage: !Options.Contains("stage", StringComparer.InvariantCultureIgnoreCase)
|
|
);
|
|
|
|
// if the MainParams override the ReleaseVersion path, use it directly
|
|
ReleaseParams.BasedOnReleaseVersionPathOverride = MainParams.BasedOnReleaseVersionPathOverride;
|
|
// if the MainParams have specified a base location to read the release info from, use that as the location to write
|
|
// to when creating the Release
|
|
ReleaseParams.CreateReleaseVersionBasePath = MainParams.BasedOnReleaseVersionBasePath;
|
|
|
|
// copy off the staging dir
|
|
ReleaseParams.PreModifyDeploymentContextCallback = new Action<ProjectParams, DeploymentContext>((ProjectParams P, DeploymentContext SC) => { ReleaseStageDirectory = SC.StageDirectory; });
|
|
|
|
// cooked editor doesn't work without OptionalContent now, so always generated it, and save it somewhere that staging of the cookededitor will get
|
|
ReleaseOptionalFileStageDirectory = DirectoryReference.Combine(MainParams.RawProjectPath.Directory, "Saved", "CookedEditor", "OptionalData");
|
|
ReleaseParams.OptionalContent = true;
|
|
ReleaseParams.OptionalFileStagingDirectory = ReleaseOptionalFileStageDirectory.FullName;
|
|
|
|
ModifyReleaseParams(ReleaseParams);
|
|
|
|
return ReleaseParams;
|
|
}
|
|
|
|
|
|
protected static void GatherTargetDependencies(ProjectParams Params, DeploymentContext SC, ModifyStageContext Context, string ReceiptName)
|
|
{
|
|
GatherTargetDependencies(Params, SC, Context, ReceiptName, UnrealTargetConfiguration.Development);
|
|
}
|
|
|
|
protected static void GatherTargetDependencies(ProjectParams Params, DeploymentContext SC, ModifyStageContext Context, string ReceiptName, UnrealTargetConfiguration Configuration)
|
|
{
|
|
UnrealArchitectures Architecture = Params.EditorArchitecture;
|
|
if (Architecture == null)
|
|
{
|
|
if (PlatformExports.IsPlatformAvailable(SC.StageTargetPlatform.IniPlatformType))
|
|
{
|
|
Architecture = UnrealArchitectureConfig.ForPlatform(SC.StageTargetPlatform.IniPlatformType).ActiveArchitectures(Params.RawProjectPath, null);
|
|
}
|
|
}
|
|
|
|
FileReference ReceiptFilename = TargetReceipt.GetDefaultPath(Params.RawProjectPath.Directory, ReceiptName, SC.StageTargetPlatform.IniPlatformType, Configuration, Architecture);
|
|
if (!FileReference.Exists(ReceiptFilename))
|
|
{
|
|
ReceiptFilename = TargetReceipt.GetDefaultPath(Unreal.EngineDirectory, ReceiptName, SC.StageTargetPlatform.IniPlatformType, Configuration, Architecture);
|
|
}
|
|
|
|
TargetReceipt Receipt;
|
|
if (!TargetReceipt.TryRead(ReceiptFilename, out Receipt))
|
|
{
|
|
throw new AutomationException("Missing or invalid target receipt ({0})", ReceiptFilename);
|
|
}
|
|
|
|
foreach (BuildProduct BuildProduct in Receipt.BuildProducts)
|
|
{
|
|
Context.NonUFSFilesToStage.Add(BuildProduct.Path);
|
|
}
|
|
|
|
foreach (RuntimeDependency RuntimeDependency in Receipt.RuntimeDependencies)
|
|
{
|
|
if (RuntimeDependency.Type == StagedFileType.UFS)
|
|
{
|
|
Context.UFSFilesToStage.Add(RuntimeDependency.Path);
|
|
}
|
|
else// if (RuntimeDependency.Type == StagedFileType.NonUFS)
|
|
{
|
|
Context.NonUFSFilesToStage.Add(RuntimeDependency.Path);
|
|
}
|
|
//else
|
|
//{
|
|
// // otherwise, just stage it directly
|
|
// // @todo: add a FilesToStage type to context like SC has?
|
|
// SC.StageFile(RuntimeDependency.Type, RuntimeDependency.Path);
|
|
//}
|
|
}
|
|
|
|
Context.NonUFSFilesToStage.Add(ReceiptFilename);
|
|
}
|
|
}
|