// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Reflection;
using AutomationTool;
using UnrealBuildTool;
public struct StageTarget
{
public TargetReceipt Receipt;
public bool RequireFilesExist;
}
public class DeploymentContext //: ProjectParams
{
///
/// Full path to the .uproject file
///
public FileReference RawProjectPath;
///
/// true if we should stage crash reporter
///
public bool bStageCrashReporter;
///
/// CookPlatform, where to get the cooked data from and use for sandboxes
///
public string CookPlatform;
///
/// FinalCookPlatform, directory to stage and archive the final result to
///
public string FinalCookPlatform;
///
/// Source platform to get the cooked data from
///
public Platform CookSourcePlatform;
///
/// Target platform used for sandboxes and stage directory names
///
public Platform StageTargetPlatform;
///
/// Configurations to stage. Used to determine which ThirdParty configurations to copy.
///
public List StageTargetConfigurations;
///
/// Receipts for the build targets that should be staged.
///
public List StageTargets;
///
/// this is the root directory that contains the engine: d:\a\UE4\
///
public string LocalRoot;
///
/// this is the directory that contains the "game": d:\a\UE4\ShooterGame
///
public string ProjectRoot;
///
/// raw name used for platform subdirectories Win32
///
public string PlatformDir;
///
/// this is the directory that contains the "game", staged: d:\stagedir\WindowsNoEditor\ShooterGame
///
public string StageProjectRoot;
///
/// Directory to put all of the files in: d:\stagedir\WindowsNoEditor
///
public string StageDirectory;
///
/// The relative staged project root, what would be tacked on to StageDirectory
///
public string RelativeProjectRootForStage;
///
/// The relative source project root, this will be the short project name for foreign projects, otherwise it is ProjectRoot minus the LocalRoot prefix
///
public string SourceRelativeProjectRoot;
///
/// The relative staged project root, used inside the pak file list (different slash convention than above)
///
public string RelativeProjectRootForUnrealPak;
///
/// This is what you use to test the engine which uproject you want. Many cases.
///
public string ProjectArgForCommandLines;
///
/// This is the root that the cook source platform would run from
///
public string CookSourceRuntimeRootDir;
///
/// This is the root that we are going to run from. Many cases.
///
public string RuntimeRootDir;
///
/// This is the project root that we are going to run from. Many cases.
///
public string RuntimeProjectRootDir;
///
/// This is the executable we are going to run and stage. Filled in by the platform abstraction.
///
public string RuntimeExecutable = "";
///
/// List of executables we are going to stage
///
public List StageExecutables;
///
/// Probably going away, used to construct ProjectArgForCommandLines in the case that we are running staged
///
public string UProjectCommandLineArgInternalRoot = "../../../";
///
/// Probably going away, used to construct the pak file list
///
public string PakFileInternalRoot = "../../../";
///
/// Probably going away, and currently unused, this would be used to build the list of files for the UFS server
///
public string UnrealFileServerInternalRoot = "../../../";
///
/// After staging, this is a map from source file to relative file in the stage
/// These file are binaries, etc and can't go into a pak file
///
public Dictionary> NonUFSStagingFiles = new Dictionary>();
///
/// After staging, this is a map from source file to relative file in the stage
/// These file are debug, and can't go into a pak file
///
public Dictionary> NonUFSStagingFilesDebug = new Dictionary>();
///
/// After staging, this is a map from source file to relative file in the stage
/// These file are content, and can go into a pak file
///
public Dictionary UFSStagingFiles = new Dictionary();
///
/// List of files to be archived
///
public Dictionary ArchivedFiles = new Dictionary();
///
/// Directory to archive all of the files in: d:\archivedir\WindowsNoEditor
///
public string ArchiveDirectory;
///
/// Directory to project binaries
///
public string ProjectBinariesFolder;
///
/// Filename for the manifest of file changes for iterative deployment.
///
static public readonly string UFSDeployDeltaFileName = "Manifest_DeltaUFSFiles.txt";
static public readonly string NonUFSDeployDeltaFileName = "Manifest_DeltaNonUFSFiles.txt";
///
/// Filename for the manifest of files to delete during deployment.
///
static public readonly string UFSDeployObsoleteFileName = "Manifest_ObsoleteUFSFiles.txt";
static public readonly string NonUFSDeployObsoleteFileName = "Manifest_ObsoleteNonUFSFiles.txt";
///
/// The client connects to dedicated server to get data
///
public bool DedicatedServer;
///
/// The dedicated server and client use
///
public bool bUseWebsocketNetDriver;
///
/// True if this build is staged
///
public bool Stage;
///
/// True if this build is archived
///
public bool Archive;
///
/// True if this project has code
///
public bool IsCodeBasedProject;
///
/// Project name (name of the uproject file without extension or directory name where the project is localed)
///
public string ShortProjectName;
///
/// If true, multiple platforms are being merged together - some behavior needs to change (but not much)
///
public bool bIsCombiningMultiplePlatforms = false;
///
/// If true if this platform is using streaming install chunk manifests
///
public bool PlatformUsesChunkManifests = false;
public DeploymentContext(
FileReference RawProjectPathOrName,
string InLocalRoot,
string BaseStageDirectory,
string BaseArchiveDirectory,
Platform InSourcePlatform,
Platform InTargetPlatform,
List InTargetConfigurations,
IEnumerable InStageTargets,
List InStageExecutables,
bool InServer,
bool InCooked,
bool InStageCrashReporter,
bool InStage,
bool InCookOnTheFly,
bool InArchive,
bool InProgram,
bool IsClientInsteadOfNoEditor,
bool InForceChunkManifests,
bool bInUseWebsocketNetDriver = false
)
{
bStageCrashReporter = InStageCrashReporter;
RawProjectPath = RawProjectPathOrName;
DedicatedServer = InServer;
LocalRoot = CommandUtils.CombinePaths(InLocalRoot);
CookSourcePlatform = InSourcePlatform;
StageTargetPlatform = InTargetPlatform;
StageTargetConfigurations = new List(InTargetConfigurations);
StageTargets = new List(InStageTargets);
StageExecutables = InStageExecutables;
IsCodeBasedProject = ProjectUtils.IsCodeBasedUProjectFile(RawProjectPath);
ShortProjectName = ProjectUtils.GetShortProjectName(RawProjectPath);
Stage = InStage;
Archive = InArchive;
bUseWebsocketNetDriver = bInUseWebsocketNetDriver;
if (CookSourcePlatform != null && InCooked)
{
CookPlatform = CookSourcePlatform.GetCookPlatform(DedicatedServer, IsClientInsteadOfNoEditor);
}
else if (CookSourcePlatform != null && InProgram)
{
CookPlatform = CookSourcePlatform.GetCookPlatform(false, false);
}
else
{
CookPlatform = "";
}
if (StageTargetPlatform != null && InCooked)
{
FinalCookPlatform = StageTargetPlatform.GetCookPlatform(DedicatedServer, IsClientInsteadOfNoEditor);
}
else if (StageTargetPlatform != null && InProgram)
{
FinalCookPlatform = StageTargetPlatform.GetCookPlatform(false, false);
}
else
{
FinalCookPlatform = "";
}
PlatformDir = StageTargetPlatform.PlatformType.ToString();
StageDirectory = CommandUtils.CombinePaths(BaseStageDirectory, FinalCookPlatform);
ArchiveDirectory = CommandUtils.CombinePaths(BaseArchiveDirectory, FinalCookPlatform);
if (!CommandUtils.FileExists(RawProjectPath.FullName))
{
throw new AutomationException("Can't find uproject file {0}.", RawProjectPathOrName);
}
ProjectRoot = CommandUtils.CombinePaths(CommandUtils.GetDirectoryName(RawProjectPath.FullName));
if (!CommandUtils.DirectoryExists(ProjectRoot))
{
throw new AutomationException("Project Directory {0} doesn't exist.", ProjectRoot);
}
RelativeProjectRootForStage = ShortProjectName;
ProjectArgForCommandLines = CommandUtils.MakePathSafeToUseWithCommandLine(RawProjectPath.FullName);
CookSourceRuntimeRootDir = RuntimeRootDir = LocalRoot;
RuntimeProjectRootDir = ProjectRoot;
RelativeProjectRootForUnrealPak = CommandUtils.CombinePaths(RelativeProjectRootForStage).Replace("\\", "/");
if (RelativeProjectRootForUnrealPak.StartsWith("/"))
{
RelativeProjectRootForUnrealPak = RelativeProjectRootForUnrealPak.Substring(1);
RelativeProjectRootForStage = RelativeProjectRootForStage.Substring(1);
}
SourceRelativeProjectRoot = RelativeProjectRootForStage; // for foreign projects this doesn't make much sense, but it turns into a noop on staging files
if (ProjectRoot.StartsWith(LocalRoot, StringComparison.InvariantCultureIgnoreCase))
{
SourceRelativeProjectRoot = ProjectRoot.Substring(LocalRoot.Length);
}
if (SourceRelativeProjectRoot.StartsWith("/") || SourceRelativeProjectRoot.StartsWith("\\"))
{
SourceRelativeProjectRoot = SourceRelativeProjectRoot.Substring(1);
}
if (Stage)
{
CommandUtils.CreateDirectory(StageDirectory);
StageProjectRoot = CommandUtils.CombinePaths(StageDirectory, RelativeProjectRootForStage);
RuntimeRootDir = StageDirectory;
CookSourceRuntimeRootDir = CommandUtils.CombinePaths(BaseStageDirectory, CookPlatform);
RuntimeProjectRootDir = StageProjectRoot;
ProjectArgForCommandLines = CommandUtils.MakePathSafeToUseWithCommandLine(UProjectCommandLineArgInternalRoot + RelativeProjectRootForStage + "/" + ShortProjectName + ".uproject");
}
if (Archive)
{
CommandUtils.CreateDirectory(ArchiveDirectory);
}
ProjectArgForCommandLines = ProjectArgForCommandLines.Replace("\\", "/");
ProjectBinariesFolder = CommandUtils.CombinePaths(ProjectUtils.GetClientProjectBinariesRootPath(RawProjectPath, TargetType.Game, IsCodeBasedProject), PlatformDir);
// If we were configured to use manifests across the whole project, then this platform should use manifests.
// Otherwise, read whether we are generating chunks from the ProjectPackagingSettings ini.
if (InForceChunkManifests)
{
PlatformUsesChunkManifests = true;
}
else
{
ConfigHierarchy GameIni = ConfigCache.ReadHierarchy(ConfigHierarchyType.Game, RawProjectPath.Directory, InTargetPlatform.PlatformType);
String IniPath = "/Script/UnrealEd.ProjectPackagingSettings";
bool bSetting = false;
if (GameIni.GetBool(IniPath, "bGenerateChunks", out bSetting))
{
PlatformUsesChunkManifests = bSetting;
}
}
}
public void StageFile(StagedFileType FileType, string InputPath, string OutputPath = null, bool bRemap = true)
{
InputPath = InputPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
if(OutputPath == null)
{
if(InputPath.StartsWith(ProjectRoot, StringComparison.InvariantCultureIgnoreCase))
{
OutputPath = CommandUtils.CombinePaths(RelativeProjectRootForStage, InputPath.Substring(ProjectRoot.Length).TrimStart('/', '\\'));
}
else if (InputPath.EndsWith(".uplugin", StringComparison.InvariantCultureIgnoreCase))
{
if (InputPath.StartsWith(CommandUtils.CombinePaths(LocalRoot + "/Engine"), StringComparison.InvariantCultureIgnoreCase))
{
OutputPath = CommandUtils.CombinePaths(InputPath.Substring(LocalRoot.Length).TrimStart('/', '\\'));
}
else
{
// This is a plugin that lives outside of the Engine/Plugins or Game/Plugins directory so needs to be remapped for staging/packaging
// We need to remap C:\SomePath\PluginName\PluginName.uplugin to RemappedPlugins\PluginName\PluginName.uplugin
int Index = InputPath.LastIndexOf(Path.DirectorySeparatorChar);
if (Index != -1)
{
int PluginDirIndex = InputPath.LastIndexOf(Path.DirectorySeparatorChar, Index - 1);
if (PluginDirIndex != -1)
{
OutputPath = CommandUtils.CombinePaths("RemappedPlugins", InputPath.Substring(PluginDirIndex));
}
}
if (OutputPath == null)
{
throw new AutomationException("Can't deploy {0} because the plugin path is non-standard, so could not be remapped", InputPath);
}
}
}
else if (InputPath.StartsWith(LocalRoot, StringComparison.InvariantCultureIgnoreCase))
{
OutputPath = CommandUtils.CombinePaths(InputPath.Substring(LocalRoot.Length).TrimStart('/', '\\'));
}
else
{
throw new AutomationException("Can't deploy {0} because it doesn't start with {1} or {2}", InputPath, ProjectRoot, LocalRoot);
}
}
if(bRemap)
{
OutputPath = StageTargetPlatform.Remap(OutputPath);
}
if (FileType == StagedFileType.UFS)
{
AddUniqueStagingFile(UFSStagingFiles, InputPath, OutputPath);
}
else if (FileType == StagedFileType.NonUFS)
{
AddStagingFile(NonUFSStagingFiles, InputPath, OutputPath);
}
else if (FileType == StagedFileType.DebugNonUFS)
{
AddStagingFile(NonUFSStagingFilesDebug, InputPath, OutputPath);
}
}
public void StageBuildProductsFromReceipt(TargetReceipt Receipt, bool RequireDependenciesToExist, bool TreatNonShippingBinariesAsDebugFiles)
{
// Stage all the build products needed at runtime
foreach(BuildProduct BuildProduct in Receipt.BuildProducts)
{
// allow missing files if needed
if (RequireDependenciesToExist == false && File.Exists(BuildProduct.Path) == false)
{
continue;
}
if(BuildProduct.Type == BuildProductType.Executable || BuildProduct.Type == BuildProductType.DynamicLibrary || BuildProduct.Type == BuildProductType.RequiredResource)
{
StagedFileType FileTypeToUse = StagedFileType.NonUFS;
if (TreatNonShippingBinariesAsDebugFiles && Receipt.Configuration != UnrealTargetConfiguration.Shipping)
{
FileTypeToUse = StagedFileType.DebugNonUFS;
}
StageFile(FileTypeToUse, BuildProduct.Path);
}
else if(BuildProduct.Type == BuildProductType.SymbolFile || BuildProduct.Type == BuildProductType.MapFile)
{
// Symbol files aren't true dependencies so we can skip if they don't exist
if (File.Exists(BuildProduct.Path))
{
StageFile(StagedFileType.DebugNonUFS, BuildProduct.Path);
}
}
}
}
public void StageRuntimeDependenciesFromReceipt(TargetReceipt Receipt, bool RequireDependenciesToExist, bool bUsingPakFile)
{
// Patterns to exclude from wildcard searches. Any maps and assets must be cooked.
List ExcludePatterns = new List();
ExcludePatterns.Add(".../*.umap");
ExcludePatterns.Add(".../*.uasset");
// Also stage any additional runtime dependencies, like ThirdParty DLLs
foreach(RuntimeDependency RuntimeDependency in Receipt.RuntimeDependencies)
{
foreach(FileReference MatchingFile in CommandUtils.ResolveFilespec(CommandUtils.RootDirectory, RuntimeDependency.Path, ExcludePatterns))
{
// allow missing files if needed
if ((RequireDependenciesToExist && RuntimeDependency.Type != StagedFileType.DebugNonUFS) || FileReference.Exists(MatchingFile))
{
bool bRemap = RuntimeDependency.Type != StagedFileType.UFS || !bUsingPakFile;
StageFile(RuntimeDependency.Type, MatchingFile.FullName, bRemap: bRemap);
}
}
}
}
///
/// Correctly collapses any ../ or ./ entries in a path.
///
/// The path to be collapsed
/// true if the path could be collapsed, false otherwise.
static bool CollapseRelativeDirectories(ref string InPath)
{
string LocalString = InPath;
bool bHadBackSlashes = false;
// look to see what kind of slashes we had
if (LocalString.IndexOf("\\") != -1)
{
LocalString = LocalString.Replace("\\", "/");
bHadBackSlashes = true;
}
string ParentDir = "/..";
int ParentDirLength = ParentDir.Length;
for (; ; )
{
// An empty path is finished
if (string.IsNullOrEmpty(LocalString))
break;
// Consider empty paths or paths which start with .. or /.. as invalid
if (LocalString.StartsWith("..") || LocalString.StartsWith(ParentDir))
return false;
// If there are no "/.."s left then we're done
int Index = LocalString.IndexOf(ParentDir);
if (Index == -1)
break;
int PreviousSeparatorIndex = Index;
for (; ; )
{
// Find the previous slash
PreviousSeparatorIndex = Math.Max(0, LocalString.LastIndexOf("/", PreviousSeparatorIndex - 1));
// Stop if we've hit the start of the string
if (PreviousSeparatorIndex == 0)
break;
// Stop if we've found a directory that isn't "/./"
if ((Index - PreviousSeparatorIndex) > 1 && (LocalString[PreviousSeparatorIndex + 1] != '.' || LocalString[PreviousSeparatorIndex + 2] != '/'))
break;
}
// If we're attempting to remove the drive letter, that's illegal
int Colon = LocalString.IndexOf(":", PreviousSeparatorIndex);
if (Colon >= 0 && Colon < Index)
return false;
LocalString = LocalString.Substring(0, PreviousSeparatorIndex) + LocalString.Substring(Index + ParentDirLength);
}
LocalString = LocalString.Replace("./", "");
// restore back slashes now
if (bHadBackSlashes)
{
LocalString = LocalString.Replace("/", "\\");
}
// and pass back out
InPath = LocalString;
return true;
}
public void StageFiles(StagedFileType FileType, string InPath, string Wildcard = "*", bool bRecursive = true, string[] ExcludeWildcard = null, string NewPath = null, bool bAllowNone = false, bool bRemap = true, string NewName = null, bool bAllowNotForLicenseesFiles = true, bool bStripFilesForOtherPlatforms = true, bool bConvertToLower = false)
{
int FilesAdded = 0;
// make sure any ..'s are removed
CollapseRelativeDirectories(ref InPath);
if (CommandUtils.DirectoryExists(InPath))
{
var All = CommandUtils.FindFiles(Wildcard, bRecursive, InPath);
var Exclude = new HashSet();
if (ExcludeWildcard != null)
{
foreach (var Excl in ExcludeWildcard)
{
var Remove = CommandUtils.FindFiles(Excl, bRecursive, InPath);
foreach (var File in Remove)
{
Exclude.Add(CommandUtils.CombinePaths(File));
}
}
}
foreach (var AllFile in All)
{
var FileToCopy = CommandUtils.CombinePaths(AllFile);
if (Exclude.Contains(FileToCopy))
{
continue;
}
if (!bAllowNotForLicenseesFiles && (FileToCopy.Contains("NotForLicensees") || FileToCopy.Contains("NoRedist")))
{
continue;
}
if (bStripFilesForOtherPlatforms && !bIsCombiningMultiplePlatforms)
{
bool OtherPlatform = false;
foreach (UnrealTargetPlatform Plat in Enum.GetValues(typeof(UnrealTargetPlatform)))
{
bool bMatchesIniPlatform = (AllFile.EndsWith(".ini") && Plat == StageTargetPlatform.IniPlatformType); // filter ini files for the ini file platform
bool bMatchesTargetPlatform = (Plat == StageTargetPlatform.PlatformType || Plat == UnrealTargetPlatform.Unknown); // filter platform files for the target platform
if (!bMatchesIniPlatform && !bMatchesTargetPlatform)
{
var Search = FileToCopy;
if (Search.StartsWith(LocalRoot, StringComparison.InvariantCultureIgnoreCase))
{
if (LocalRoot.EndsWith("\\") || LocalRoot.EndsWith("/"))
{
Search = Search.Substring(LocalRoot.Length - 1);
}
else
{
Search = Search.Substring(LocalRoot.Length);
}
}
if (Search.StartsWith(ProjectRoot, StringComparison.InvariantCultureIgnoreCase))
{
if (ProjectRoot.EndsWith("\\") || ProjectRoot.EndsWith("/"))
{
Search = Search.Substring(ProjectRoot.Length - 1);
}
else
{
Search = Search.Substring(ProjectRoot.Length);
}
}
if (Search.IndexOf(CommandUtils.CombinePaths("/" + Plat.ToString() + "/"), 0, StringComparison.InvariantCultureIgnoreCase) >= 0)
{
OtherPlatform = true;
break;
}
}
}
if (OtherPlatform)
{
continue;
}
}
string Dest;
if (!FileToCopy.StartsWith(InPath))
{
throw new AutomationException("Can't deploy {0}; it was supposed to start with {1}", FileToCopy, InPath);
}
string FileToRemap = FileToCopy;
// If the specified a new directory, first we deal with that, then apply the other things
// this is used to collapse the sandbox, among other things
if (NewPath != null)
{
Dest = FileToRemap.Substring(InPath.Length);
if (Dest.StartsWith("/") || Dest.StartsWith("\\"))
{
Dest = Dest.Substring(1);
}
Dest = CommandUtils.CombinePaths(NewPath, Dest);
#if false // if the cooker rebases, I don't think we need to ever rebase while staging to a new path
if (Dest.StartsWith("/") || Dest.StartsWith("\\"))
{
Dest = Dest.Substring(1);
}
// project relative stuff in a collapsed sandbox
if (Dest.StartsWith(SourceRelativeProjectRoot, StringComparison.InvariantCultureIgnoreCase))
{
Dest = Dest.Substring(SourceRelativeProjectRoot.Length);
if (Dest.StartsWith("/") || Dest.StartsWith("\\"))
{
Dest = Dest.Substring(1);
}
Dest = CommandUtils.CombinePaths(RelativeProjectRootForStage, Dest);
}
#endif
}
// project relative file
else if (FileToRemap.StartsWith(ProjectRoot, StringComparison.InvariantCultureIgnoreCase))
{
Dest = FileToRemap.Substring(ProjectRoot.Length);
if (Dest.StartsWith("/") || Dest.StartsWith("\\"))
{
Dest = Dest.Substring(1);
}
Dest = CommandUtils.CombinePaths(RelativeProjectRootForStage, Dest);
}
// engine relative file
else if (FileToRemap.StartsWith(LocalRoot, StringComparison.InvariantCultureIgnoreCase))
{
Dest = CommandUtils.CombinePaths(FileToRemap.Substring(LocalRoot.Length));
}
else
{
throw new AutomationException("Can't deploy {0} because it doesn't start with {1} or {2}", FileToRemap, ProjectRoot, LocalRoot);
}
if (Dest.StartsWith("/") || Dest.StartsWith("\\"))
{
Dest = Dest.Substring(1);
}
if (NewName != null)
{
Dest = CommandUtils.CombinePaths(Path.GetDirectoryName(Dest), NewName);
}
if (bRemap)
{
Dest = StageTargetPlatform.Remap(Dest);
}
if (bConvertToLower)
{
Dest = Dest.ToLowerInvariant();
}
if (FileType == StagedFileType.UFS)
{
AddUniqueStagingFile(UFSStagingFiles, FileToCopy, Dest);
}
else if (FileType == StagedFileType.NonUFS)
{
AddStagingFile(NonUFSStagingFiles, FileToCopy, Dest);
}
else if (FileType == StagedFileType.DebugNonUFS)
{
AddStagingFile(NonUFSStagingFilesDebug, FileToCopy, Dest);
}
FilesAdded++;
}
}
if (FilesAdded == 0 && !bAllowNone && !bIsCombiningMultiplePlatforms)
{
throw new AutomationException(ExitCode.Error_StageMissingFile, "No files found to deploy for {0} with wildcard {1} and exclusions {2}", InPath, Wildcard, ExcludeWildcard);
}
}
private void AddUniqueStagingFile(Dictionary FilesToStage, string FileToCopy, string Dest)
{
string ExistingDest;
if (FilesToStage.TryGetValue(FileToCopy, out ExistingDest))
{
// If the file already exists, it must have the same dest
if (String.Compare(ExistingDest, Dest, true) != 0)
{
throw new AutomationException("Attempting to add \"{0}\" to staging map but it already exists with a different destination (existing: \"{1}\", new: \"{2}\"",
FileToCopy, ExistingDest, Dest);
}
}
else
{
FilesToStage.Add(FileToCopy, Dest);
}
}
private void AddStagingFile(Dictionary> FilesToStage, string FileToCopy, string Dest)
{
List ExistingDest;
if (FilesToStage.TryGetValue(FileToCopy, out ExistingDest))
{
if (!FilesToStage[FileToCopy].Contains(Dest))
{
FilesToStage[FileToCopy].Add(Dest);
}
}
else
{
FilesToStage.Add(FileToCopy, new List(){Dest});
}
}
public int ArchiveFiles(string InPath, string Wildcard = "*", bool bRecursive = true, string[] ExcludeWildcard = null, string NewPath = null)
{
int FilesAdded = 0;
if (CommandUtils.DirectoryExists(InPath))
{
var All = CommandUtils.FindFiles(Wildcard, bRecursive, InPath);
var Exclude = new HashSet();
if (ExcludeWildcard != null)
{
foreach (var Excl in ExcludeWildcard)
{
var Remove = CommandUtils.FindFiles(Excl, bRecursive, InPath);
foreach (var File in Remove)
{
Exclude.Add(CommandUtils.CombinePaths(File));
}
}
}
foreach (var AllFile in All)
{
var FileToCopy = CommandUtils.CombinePaths(AllFile);
if (Exclude.Contains(FileToCopy))
{
continue;
}
if (!bIsCombiningMultiplePlatforms)
{
bool OtherPlatform = false;
foreach (UnrealTargetPlatform Plat in Enum.GetValues(typeof(UnrealTargetPlatform)))
{
if (Plat != StageTargetPlatform.PlatformType && Plat != UnrealTargetPlatform.Unknown)
{
var Search = FileToCopy;
if (Search.StartsWith(LocalRoot, StringComparison.InvariantCultureIgnoreCase))
{
if (LocalRoot.EndsWith("\\") || LocalRoot.EndsWith("/"))
{
Search = Search.Substring(LocalRoot.Length - 1);
}
else
{
Search = Search.Substring(LocalRoot.Length);
}
}
if (Search.StartsWith(ProjectRoot, StringComparison.InvariantCultureIgnoreCase))
{
if (ProjectRoot.EndsWith("\\") || ProjectRoot.EndsWith("/"))
{
Search = Search.Substring(ProjectRoot.Length - 1);
}
else
{
Search = Search.Substring(ProjectRoot.Length);
}
}
if (Search.IndexOf(CommandUtils.CombinePaths("/" + Plat.ToString() + "/"), 0, StringComparison.InvariantCultureIgnoreCase) >= 0)
{
OtherPlatform = true;
break;
}
}
}
if (OtherPlatform)
{
continue;
}
}
string Dest;
if (!FileToCopy.StartsWith(InPath))
{
throw new AutomationException("Can't archive {0}; it was supposed to start with {1}", FileToCopy, InPath);
}
// If the specified a new directory, first we deal with that, then apply the other things
// this is used to collapse the sandbox, among other things
if (NewPath != null)
{
Dest = FileToCopy.Substring(InPath.Length);
if (Dest.StartsWith("/") || Dest.StartsWith("\\"))
{
Dest = Dest.Substring(1);
}
Dest = CommandUtils.CombinePaths(NewPath, Dest);
}
else
{
Dest = FileToCopy.Substring(InPath.Length);
}
if (Dest.StartsWith("/") || Dest.StartsWith("\\"))
{
Dest = Dest.Substring(1);
}
if (ArchivedFiles.ContainsKey(FileToCopy))
{
if (ArchivedFiles[FileToCopy] != Dest)
{
throw new AutomationException("Can't archive {0}: it was already in the files to archive with a different destination '{1}'", FileToCopy, Dest);
}
}
else
{
ArchivedFiles.Add(FileToCopy, Dest);
}
FilesAdded++;
}
}
return FilesAdded;
}
public String GetUFSDeploymentDeltaPath(string DeviceName)
{
//replace the port name in the case of deploy while adb is using wifi
string SanitizedDeviceName = DeviceName.Replace(":", "_");
return Path.Combine(StageDirectory, UFSDeployDeltaFileName + SanitizedDeviceName);
}
public String GetNonUFSDeploymentDeltaPath(string DeviceName)
{
//replace the port name in the case of deploy while adb is using wifi
string SanitizedDeviceName = DeviceName.Replace(":", "_");
return Path.Combine(StageDirectory, NonUFSDeployDeltaFileName + SanitizedDeviceName);
}
public String GetUFSDeploymentObsoletePath(string DeviceName)
{
//replace the port name in the case of deploy while adb is using wifi
string SanitizedDeviceName = DeviceName.Replace(":", "_");
return Path.Combine(StageDirectory, UFSDeployObsoleteFileName + SanitizedDeviceName);
}
public String GetNonUFSDeploymentObsoletePath(string DeviceName)
{
//replace the port name in the case of deploy while adb is using wifi
string SanitizedDeviceName = DeviceName.Replace(":", "_");
return Path.Combine(StageDirectory, NonUFSDeployObsoleteFileName + SanitizedDeviceName);
}
public string UFSDeployedManifestFileName
{
get
{
return "Manifest_UFSFiles_" + StageTargetPlatform.PlatformType.ToString() + ".txt";
}
}
public string NonUFSDeployedManifestFileName
{
get
{
return "Manifest_NonUFSFiles_" + StageTargetPlatform.PlatformType.ToString() + ".txt";
}
}
public static string GetNonUFSDeployedManifestFileName(UnrealTargetPlatform PlatformType)
{
return "Manifest_NonUFSFiles_" + PlatformType.ToString() + ".txt";
}
public static string GetUFSDeployedManifestFileName(UnrealTargetPlatform PlatformType)
{
return "Manifest_UFSFiles_" + PlatformType.ToString() + ".txt";
}
public static string GetDebugFilesManifestFileName(UnrealTargetPlatform PlatformType)
{
return "Manifest_DebugFiles_" + PlatformType.ToString() + ".txt";
}
}