2023-06-06 17:40:06 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
2023-09-30 11:46:22 -04:00
using System.Diagnostics.CodeAnalysis ;
2023-06-06 17:40:06 -04:00
using System.IO ;
using System.Linq ;
2023-08-24 12:05:59 -04:00
using System.Runtime.InteropServices ;
2023-06-06 17:40:06 -04:00
using System.Text ;
2023-09-30 11:46:22 -04:00
using System.Text.Json ;
using System.Text.Json.Serialization ;
2023-06-06 17:40:06 -04:00
using System.Threading.Tasks ;
using System.Xml ;
using EpicGames.Core ;
2024-04-30 02:28:24 -04:00
using EpicGames.Horde ;
2024-03-24 22:03:42 -04:00
using EpicGames.ProjectStore ;
2024-05-22 13:17:22 -04:00
using Microsoft.Extensions.Logging ;
2023-06-06 17:40:06 -04:00
namespace AutomationTool.Tasks
{
2023-10-03 10:33:40 -04:00
/// <summary>
/// Enumeration of different storage options for snapshots.
/// </summary>
public enum SnapshotStorageType
2023-06-06 17:40:06 -04:00
{
2023-10-03 10:33:40 -04:00
/// <summary>
/// A reserved non-valid storage type for snapshots.
/// </summary>
2023-06-06 17:40:06 -04:00
Invalid ,
2023-10-03 10:33:40 -04:00
/// <summary>
/// Snapshot stored in cloud repositories such as Unreal Cloud DDC.
/// </summary>
2023-06-06 17:40:06 -04:00
Cloud ,
2023-10-03 10:33:40 -04:00
/// <summary>
/// Snapshot stored in a zenserver.
/// </summary>
2023-06-06 17:40:06 -04:00
Zen ,
2023-10-03 10:33:40 -04:00
/// <summary>
/// Snapshot stored as a file on disk.
/// </summary>
2023-06-06 17:40:06 -04:00
File ,
}
/// <summary>
/// Parameters for a task that exports an snapshot from ZenServer
/// </summary>
public class ZenExportSnapshotTaskParameters
{
/// <summary>
/// The project from which to export the snapshot
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public FileReference Project { get ; set ; }
2023-06-06 17:40:06 -04:00
/// <summary>
/// The target platform(s) to export the snapshot for
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public string Platform { get ; set ; }
2023-06-06 17:40:06 -04:00
2023-09-30 11:46:22 -04:00
/// <summary>
/// A file to read with information about the snapshot that should be used as a base when exporting this new snapshot
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public FileReference SnapshotBaseDescriptorFile { get ; set ; }
2023-09-30 11:46:22 -04:00
2023-06-06 17:40:06 -04:00
/// <summary>
/// A file to create with information about the snapshot that was exported
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public FileReference SnapshotDescriptorFile { get ; set ; }
2023-06-06 17:40:06 -04:00
/// <summary>
/// The type of destination to export the snapshot to (cloud, ...)
/// </summary>
[TaskParameter]
2024-05-21 20:54:09 -04:00
public string DestinationStorageType { get ; set ; }
2023-06-06 17:40:06 -04:00
2023-09-30 11:46:22 -04:00
/// <summary>
/// The identifier to use when exporting to a destination
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public string DestinationIdentifier { get ; set ; }
2023-09-30 11:46:22 -04:00
2023-06-06 17:40:06 -04:00
/// <summary>
/// The host name to use when exporting to a cloud destination
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public string DestinationCloudHost { get ; set ; }
2023-06-06 17:40:06 -04:00
2024-02-29 12:25:13 -05:00
/// <summary>
/// The host name to use when writing a snapshot descriptor for a cloud destination
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public string SnapshotDescriptorCloudHost { get ; set ; }
2024-02-29 12:25:13 -05:00
2024-05-28 15:59:41 -04:00
/// <summary>
/// The target platform to use when writing a snapshot descriptor
/// </summary>
[TaskParameter(Optional = true)]
public string SnapshotDescriptorPlatform { get ; set ; }
2024-02-29 12:25:13 -05:00
/// <summary>
/// The http version to use when exporting to a cloud destination
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public string DestinationCloudHttpVersion { get ; set ; }
2024-02-29 12:25:13 -05:00
/// <summary>
/// The http version to use when writing a snapshot descriptor for a cloud destination
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public string SnapshotDescriptorCloudHttpVersion { get ; set ; }
2024-02-29 12:25:13 -05:00
2023-06-06 17:40:06 -04:00
/// <summary>
/// The namespace to use when exporting to a cloud destination
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public string DestinationCloudNamespace { get ; set ; }
2023-06-06 17:40:06 -04:00
/// <summary>
/// A custom bucket name to use when exporting to a cloud destination
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public string DestinationCloudBucket { get ; set ; }
2023-08-22 15:43:51 -04:00
2024-03-28 17:19:08 -04:00
/// <summary>
/// The host name to use when exporting to a zen destination
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public string DestinationZenHost { get ; set ; }
2024-03-28 17:19:08 -04:00
2023-08-22 15:43:51 -04:00
/// <summary>
2023-09-30 11:46:22 -04:00
/// The directory to use when exporting to a file destination
2023-08-22 15:43:51 -04:00
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public DirectoryReference DestinationFileDir { get ; set ; }
2023-08-22 15:43:51 -04:00
/// <summary>
2023-09-30 11:46:22 -04:00
/// The filename to use when exporting to a file destination
2023-08-22 15:43:51 -04:00
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public string DestinationFileName { get ; set ; }
2023-10-10 17:19:43 -04:00
/// <summary>
2024-01-31 13:01:29 -05:00
/// Optional. Where to look for the ue.projectstore
2023-10-10 17:19:43 -04:00
/// The pattern {Platform} can be used for exporting multiple platforms at once.
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public string OverridePlatformCookedDir { get ; set ; }
2024-03-25 19:36:39 -04:00
/// <summary>
/// Optional. Whether to force export of data even if the destination claims to have them.
/// </summary>
[TaskParameter(Optional = true)]
2024-05-28 15:59:41 -04:00
public bool ForceExport { get ; set ; } = false ;
/// <summary>
/// Optional. Whether to entirely bypass the exporting of data and write a snapshot descriptor as if the data had been exported.
/// </summary>
[TaskParameter(Optional = true)]
public bool SkipExport { get ; set ; } = false ;
2023-06-06 17:40:06 -04:00
}
/// <summary>
/// Exports an snapshot from Zen to a specified destination.
/// </summary>
[TaskElement("ZenExportSnapshot", typeof(ZenExportSnapshotTaskParameters))]
public class ZenExportSnapshotTask : BgTaskImpl
{
2023-10-03 10:33:40 -04:00
/// <summary>
/// Metadata about a snapshot
/// </summary>
2024-05-22 11:01:26 -04:00
class SnapshotDescriptor
2023-09-30 11:46:22 -04:00
{
2023-10-03 10:33:40 -04:00
/// <summary>
/// Name of the snapshot
/// </summary>
public string Name { get ; set ; }
2023-09-30 11:46:22 -04:00
2023-10-03 10:33:40 -04:00
/// <summary>
/// Storage type used for the snapshot
/// </summary>
public SnapshotStorageType Type { get ; set ; }
2023-09-30 11:46:22 -04:00
2023-10-03 10:33:40 -04:00
/// <summary>
/// Target platform for this snapshot
/// </summary>
public string TargetPlatform { get ; set ; }
2024-05-22 13:17:22 -04:00
2023-10-03 10:33:40 -04:00
/// <summary>
/// For cloud snapshots, the host they are stored on.
/// </summary>
public string Host { get ; set ; }
/// <summary>
/// For cloud snapshots, the namespace they are stored in.
/// </summary>
public string Namespace { get ; set ; }
/// <summary>
/// For cloud snapshots, the bucket they are stored in.
/// </summary>
public string Bucket { get ; set ; }
/// <summary>
/// For cloud snapshots, the key they are stored in.
/// </summary>
public string Key { get ; set ; }
2024-05-22 13:17:22 -04:00
2023-10-03 10:33:40 -04:00
/// <summary>
/// For file snapshots, the directory it is stored in.
/// </summary>
public string Directory { get ; set ; }
/// <summary>
/// For file snapshots, the filename (not including path) that they are stored in.
/// </summary>
public string Filename { get ; set ; }
}
/// <summary>
/// A collection of one or more snapshot descriptors
/// </summary>
2024-05-22 11:01:26 -04:00
class SnapshotDescriptorCollection
2023-10-03 10:33:40 -04:00
{
/// <summary>
/// The list of snapshots contained within this collection.
/// </summary>
public List < SnapshotDescriptor > Snapshots { get ; set ; }
2023-09-30 11:46:22 -04:00
}
2024-05-22 13:17:22 -04:00
2023-06-06 17:40:06 -04:00
private class ExportSourceData
{
2024-05-22 13:17:22 -04:00
public bool _isLocalHost ;
public string _hostName ;
public int _hostPort ;
public string _projectId ;
public string _oplogId ;
public string _targetPlatform ;
public SnapshotDescriptor _snapshotBaseDescriptor ;
2023-06-06 17:40:06 -04:00
}
/// <summary>
/// Parameters for the task
/// </summary>
2024-05-22 13:17:22 -04:00
readonly ZenExportSnapshotTaskParameters _parameters ;
2023-06-06 17:40:06 -04:00
/// <summary>
/// Constructor.
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="parameters">Parameters for this task</param>
public ZenExportSnapshotTask ( ZenExportSnapshotTaskParameters parameters )
2023-06-06 17:40:06 -04:00
{
2024-05-22 13:17:22 -04:00
_parameters = parameters ;
2023-06-06 17:40:06 -04:00
}
2023-08-25 14:13:23 -04:00
/// <summary>
2023-08-25 14:35:49 -04:00
/// Gets the assumed path to where Zen should exist
2023-08-25 14:13:23 -04:00
/// </summary>
/// <returns></returns>
public static FileReference ZenExeFileReference ( )
{
2023-08-25 14:35:49 -04:00
return ResolveFile ( String . Format ( "Engine/Binaries/{0}/zen{1}" , HostPlatform . Current . HostEditorPlatform . ToString ( ) , RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ? ".exe" : "" ) ) ;
2023-08-25 14:13:23 -04:00
}
/// <summary>
/// Ensures that ZenServer is running on this current machine. This is needed before running any oplog commands
/// This passes the sponsor'd process Id to launch zen.
/// This ensures that zen does not live longer than the lifetime of a particular a process that needs Zen to be running
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="projectFile"></param>
public static void ZenLaunch ( FileReference projectFile )
2023-08-24 12:05:59 -04:00
{
// Get the ZenLaunch executable path
2024-05-22 13:17:22 -04:00
FileReference zenLaunchExe = ResolveFile ( String . Format ( "Engine/Binaries/{0}/ZenLaunch{1}" , HostPlatform . Current . HostEditorPlatform . ToString ( ) , RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ? ".exe" : "" ) ) ;
2023-08-24 12:05:59 -04:00
2024-05-22 13:17:22 -04:00
StringBuilder zenLaunchCommandline = new StringBuilder ( ) ;
zenLaunchCommandline . AppendFormat ( "{0} -SponsorProcessID={1}" , CommandUtils . MakePathSafeToUseWithCommandLine ( projectFile . FullName ) , Environment . ProcessId ) ;
2023-08-24 12:05:59 -04:00
2024-05-22 13:17:22 -04:00
CommandUtils . RunAndLog ( CommandUtils . CmdEnv , zenLaunchExe . FullName , zenLaunchCommandline . ToString ( ) , Options : CommandUtils . ERunOptions . Default ) ;
2023-09-30 11:46:22 -04:00
}
2024-05-22 13:17:22 -04:00
2023-09-30 11:46:22 -04:00
static JsonSerializerOptions GetDefaultJsonSerializerOptions ( )
{
JsonSerializerOptions options = new JsonSerializerOptions ( ) ;
options . AllowTrailingCommas = true ;
options . ReadCommentHandling = JsonCommentHandling . Skip ;
options . PropertyNameCaseInsensitive = true ;
options . Converters . Add ( new JsonStringEnumConverter ( ) ) ;
return options ;
}
#nullable enable
static bool TryLoadJson < T > ( FileReference file , [ NotNullWhen ( true ) ] out T ? obj ) where T : class
{
if ( ! FileReference . Exists ( file ) )
{
obj = null ;
return false ;
}
try
{
obj = LoadJson < T > ( file ) ;
return true ;
}
2024-05-22 13:17:22 -04:00
catch ( Exception )
2023-09-30 11:46:22 -04:00
{
obj = null ;
return false ;
}
}
static T LoadJson < T > ( FileReference file )
{
byte [ ] data = FileReference . ReadAllBytes ( file ) ;
return JsonSerializer . Deserialize < T > ( data , GetDefaultJsonSerializerOptions ( ) ) ! ;
}
2024-03-28 18:55:45 -04:00
2024-05-22 13:17:22 -04:00
static string SanitizeOplogName ( string name )
2024-03-28 18:55:45 -04:00
{
2024-05-22 13:17:22 -04:00
return name . Replace ( '/' , '_' ) . Replace ( ' ' , '_' ) . Replace ( '+' , '_' ) . Replace ( '-' , '_' ) ;
2024-03-28 18:55:45 -04:00
}
2024-05-22 13:17:22 -04:00
private void WriteExportSource ( JsonWriter writer , SnapshotStorageType destinationStorageType , ExportSourceData exportSource , string name )
2023-09-30 11:46:22 -04:00
{
2024-05-28 15:59:41 -04:00
string targetPlatform = _parameters . SnapshotDescriptorPlatform ;
if ( string . IsNullOrEmpty ( targetPlatform ) )
{
targetPlatform = exportSource . _targetPlatform ;
}
2024-05-22 13:17:22 -04:00
writer . WriteObjectStart ( ) ;
switch ( destinationStorageType )
2023-09-30 11:46:22 -04:00
{
case SnapshotStorageType . Cloud :
2024-05-22 13:17:22 -04:00
string bucketName = _parameters . DestinationCloudBucket ;
string projectNameAsBucketName = _parameters . Project . GetFileNameWithoutAnyExtensions ( ) . ToLowerInvariant ( ) ;
if ( string . IsNullOrEmpty ( bucketName ) )
2023-09-30 11:46:22 -04:00
{
2024-05-22 13:17:22 -04:00
bucketName = projectNameAsBucketName ;
2023-09-30 11:46:22 -04:00
}
2024-05-22 13:17:22 -04:00
bucketName = SanitizeBucketName ( bucketName ) ;
2023-09-30 11:46:22 -04:00
2024-05-22 13:17:22 -04:00
string hostName = _parameters . SnapshotDescriptorCloudHost ;
if ( string . IsNullOrEmpty ( hostName ) )
2024-02-29 12:25:13 -05:00
{
2024-05-22 13:17:22 -04:00
hostName = _parameters . DestinationCloudHost ;
2024-02-29 12:25:13 -05:00
}
2024-05-22 13:17:22 -04:00
string httpVersion = _parameters . SnapshotDescriptorCloudHttpVersion ;
if ( string . IsNullOrEmpty ( httpVersion ) )
2024-02-29 12:25:13 -05:00
{
2024-05-22 13:17:22 -04:00
httpVersion = _parameters . DestinationCloudHttpVersion ;
2024-02-29 12:25:13 -05:00
}
2024-05-22 13:17:22 -04:00
IoHash destinationKeyHash = IoHash . Compute ( Encoding . UTF8 . GetBytes ( name ) ) ;
writer . WriteValue ( "name" , name ) ;
writer . WriteValue ( "type" , "cloud" ) ;
2024-05-28 15:59:41 -04:00
writer . WriteValue ( "targetplatform" , targetPlatform ) ;
2024-05-22 13:17:22 -04:00
writer . WriteValue ( "host" , hostName ) ;
if ( ! string . IsNullOrEmpty ( httpVersion ) & & ! httpVersion . Equals ( "None" , StringComparison . OrdinalIgnoreCase ) )
2024-02-29 12:25:13 -05:00
{
2024-05-22 13:17:22 -04:00
writer . WriteValue ( "httpversion" , httpVersion ) ;
2024-02-29 12:25:13 -05:00
}
2024-05-22 13:17:22 -04:00
writer . WriteValue ( "namespace" , _parameters . DestinationCloudNamespace ) ;
writer . WriteValue ( "bucket" , bucketName ) ;
writer . WriteValue ( "key" , destinationKeyHash . ToString ( ) . ToLowerInvariant ( ) ) ;
2023-09-30 11:46:22 -04:00
break ;
2024-03-28 17:19:08 -04:00
case SnapshotStorageType . Zen :
2024-05-22 13:17:22 -04:00
string projectName = _parameters . Project . GetFileNameWithoutAnyExtensions ( ) . ToLowerInvariant ( ) + ".oplog" ;
2024-03-28 17:19:08 -04:00
2024-05-22 13:17:22 -04:00
writer . WriteValue ( "name" , name ) ;
writer . WriteValue ( "type" , "zen" ) ;
2024-05-28 15:59:41 -04:00
writer . WriteValue ( "targetplatform" , targetPlatform ) ;
2024-05-22 13:17:22 -04:00
writer . WriteValue ( "host" , _parameters . DestinationZenHost ) ;
writer . WriteValue ( "projectid" , projectName ) ;
writer . WriteValue ( "oplogid" , SanitizeOplogName ( name ) ) ;
2024-03-28 17:19:08 -04:00
break ;
2023-09-30 11:46:22 -04:00
case SnapshotStorageType . File :
2024-05-22 13:17:22 -04:00
writer . WriteValue ( "name" , name ) ;
writer . WriteValue ( "type" , "file" ) ;
2024-05-28 15:59:41 -04:00
writer . WriteValue ( "targetplatform" , targetPlatform ) ;
2024-05-22 13:17:22 -04:00
writer . WriteValue ( "directory" , _parameters . DestinationFileDir . FullName ) ;
writer . WriteValue ( "filename" , _parameters . DestinationFileName ) ;
2023-09-30 11:46:22 -04:00
break ;
}
2024-05-22 13:17:22 -04:00
writer . WriteObjectEnd ( ) ;
2023-08-24 12:05:59 -04:00
}
2024-05-22 13:17:22 -04:00
private static bool TryRunAndLogWithoutSpew ( string app , string commandLine , bool ignoreFailure )
2024-04-05 16:58:50 -04:00
{
2024-05-22 13:17:22 -04:00
ProcessResult . SpewFilterCallbackType silentOutputFilter = new ProcessResult . SpewFilterCallbackType ( line = >
2024-04-05 16:58:50 -04:00
{
return null ;
} ) ;
try
{
2024-05-22 13:17:22 -04:00
CommandUtils . RunAndLog ( CommandUtils . CmdEnv , app , commandLine , MaxSuccessCode : 0 , Options : CommandUtils . ERunOptions . Default , SpewFilterCallback : silentOutputFilter ) ;
2024-04-05 16:58:50 -04:00
}
catch ( CommandUtils . CommandFailedException e )
{
2024-05-22 13:17:22 -04:00
if ( ! ignoreFailure )
2024-04-17 16:18:53 -04:00
{
2024-05-21 21:06:38 -04:00
Logger . LogWarning ( "{Text}" , e . ToString ( ) ) ;
2024-04-17 16:18:53 -04:00
}
2024-04-06 00:55:05 -04:00
return false ;
2024-04-05 16:58:50 -04:00
}
2024-04-06 00:55:05 -04:00
return true ;
}
2024-05-22 13:17:22 -04:00
private static bool TryExportOplogCommand ( string app , string commandLine )
2024-04-06 00:55:05 -04:00
{
2024-05-22 13:17:22 -04:00
int attemptLimit = 2 ;
int attempt = 0 ;
while ( attempt < attemptLimit )
2024-04-06 00:55:05 -04:00
{
2024-05-22 13:17:22 -04:00
if ( TryRunAndLogWithoutSpew ( app , commandLine , false ) )
2024-04-06 00:55:05 -04:00
{
return true ;
}
2024-05-22 13:17:22 -04:00
Logger . LogWarning ( "Attempt {AttemptNum} of exporting the oplog failed, {Action}..." , attempt + 1 , attempt < ( attemptLimit - 1 ) ? "retrying" : "abandoning" ) ;
2024-04-06 00:55:05 -04:00
2024-05-22 13:17:22 -04:00
attempt = attempt + 1 ;
2024-04-06 00:55:05 -04:00
}
return false ;
2024-04-05 16:58:50 -04:00
}
2024-05-22 13:17:22 -04:00
private static string SanitizeBucketName ( string inString )
2024-04-30 02:28:24 -04:00
{
2024-05-22 13:17:22 -04:00
return StringId . Sanitize ( inString ) . ToString ( ) ;
2024-04-30 02:28:24 -04:00
}
2023-06-06 17:40:06 -04:00
/// <summary>
2024-05-22 13:17:22 -04:00
/// ExecuteAsync the task.
2023-06-06 17:40:06 -04:00
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="job">Information about the current job</param>
/// <param name="buildProducts">Set of build products produced by this node.</param>
/// <param name="tagNameToFileSet">Mapping from tag names to the set of files they include</param>
public override Task ExecuteAsync ( JobContext job , HashSet < FileReference > buildProducts , Dictionary < string , HashSet < FileReference > > tagNameToFileSet )
2023-06-06 17:40:06 -04:00
{
2024-05-22 13:17:22 -04:00
SnapshotStorageType destinationStorageType = SnapshotStorageType . Invalid ;
if ( ! String . IsNullOrEmpty ( _parameters . DestinationStorageType ) )
2023-06-06 17:40:06 -04:00
{
2024-05-22 13:17:22 -04:00
destinationStorageType = ( SnapshotStorageType ) Enum . Parse ( typeof ( SnapshotStorageType ) , _parameters . DestinationStorageType ) ;
2023-06-06 17:40:06 -04:00
}
2024-05-22 13:17:22 -04:00
FileReference projectFile = _parameters . Project ;
if ( ! FileReference . Exists ( projectFile ) )
2023-06-06 17:40:06 -04:00
{
2024-05-22 13:17:22 -04:00
throw new AutomationException ( "Missing project file - {0}" , projectFile . FullName ) ;
2023-06-06 17:40:06 -04:00
}
2024-05-22 13:17:22 -04:00
ZenLaunch ( projectFile ) ;
2023-08-24 12:05:59 -04:00
2024-05-22 13:17:22 -04:00
List < ExportSourceData > exportSources = new List < ExportSourceData > ( ) ;
foreach ( string platform in _parameters . Platform . Split ( '+' ) )
2023-06-06 17:40:06 -04:00
{
2024-05-22 13:17:22 -04:00
DirectoryReference platformCookedDirectory ;
if ( string . IsNullOrEmpty ( _parameters . OverridePlatformCookedDir ) )
2023-10-10 17:19:43 -04:00
{
2024-05-22 13:17:22 -04:00
platformCookedDirectory = DirectoryReference . Combine ( projectFile . Directory , "Saved" , "Cooked" , platform ) ;
2023-10-10 17:19:43 -04:00
}
else
{
2024-05-22 13:17:22 -04:00
platformCookedDirectory = new DirectoryReference ( _parameters . OverridePlatformCookedDir . Replace ( "{Platform}" , platform , StringComparison . InvariantCultureIgnoreCase ) ) ;
2023-10-10 17:19:43 -04:00
}
2024-05-22 13:17:22 -04:00
if ( ! DirectoryReference . Exists ( platformCookedDirectory ) )
2023-06-06 17:40:06 -04:00
{
2024-05-22 13:17:22 -04:00
throw new AutomationException ( "Cook output directory not found ({0})" , platformCookedDirectory . FullName ) ;
2023-06-06 17:40:06 -04:00
}
2024-05-22 13:17:22 -04:00
FileReference projectStoreFile = FileReference . Combine ( platformCookedDirectory , "ue.projectstore" ) ;
ProjectStoreData ? parsedProjectStore = null ;
if ( TryLoadJson ( projectStoreFile , out parsedProjectStore ) & & ( parsedProjectStore ! = null ) & & ( parsedProjectStore . ZenServer ! = null ) )
2023-06-06 17:40:06 -04:00
{
2024-05-22 13:17:22 -04:00
ExportSourceData newExportSource = new ExportSourceData ( ) ;
newExportSource . _isLocalHost = parsedProjectStore . ZenServer . IsLocalHost ;
newExportSource . _hostName = parsedProjectStore . ZenServer . HostName ;
newExportSource . _hostPort = parsedProjectStore . ZenServer . HostPort ;
newExportSource . _projectId = parsedProjectStore . ZenServer . ProjectId ;
newExportSource . _oplogId = parsedProjectStore . ZenServer . OplogId ;
newExportSource . _targetPlatform = platform ;
newExportSource . _snapshotBaseDescriptor = null ;
2023-06-06 17:40:06 -04:00
2024-05-22 13:17:22 -04:00
if ( _parameters . SnapshotBaseDescriptorFile ! = null )
2023-09-30 11:46:22 -04:00
{
2024-05-22 13:17:22 -04:00
FileReference platformSnapshotBase = new FileReference ( _parameters . SnapshotBaseDescriptorFile . FullName . Replace ( "{Platform}" , platform , StringComparison . InvariantCultureIgnoreCase ) ) ;
2023-10-04 14:57:31 -04:00
2024-05-22 13:17:22 -04:00
SnapshotDescriptorCollection ? parsedDescriptorCollection = null ;
if ( TryLoadJson ( platformSnapshotBase , out parsedDescriptorCollection ) & & ( parsedDescriptorCollection ! = null ) & & ( parsedDescriptorCollection . Snapshots ! = null ) )
2023-10-03 10:33:40 -04:00
{
2024-05-22 13:17:22 -04:00
foreach ( SnapshotDescriptor parsedDescriptor in parsedDescriptorCollection . Snapshots )
2023-10-03 10:33:40 -04:00
{
2024-05-22 13:17:22 -04:00
if ( parsedDescriptor . TargetPlatform = = platform )
2023-10-04 14:57:31 -04:00
{
2024-05-22 13:17:22 -04:00
newExportSource . _snapshotBaseDescriptor = parsedDescriptor ;
2023-10-04 14:57:31 -04:00
break ;
}
2023-10-03 10:33:40 -04:00
}
}
2023-09-30 11:46:22 -04:00
}
2024-05-22 13:17:22 -04:00
exportSources . Add ( newExportSource ) ;
2023-06-06 17:40:06 -04:00
}
}
2024-05-22 13:17:22 -04:00
int exportIndex ;
string [ ] exportNames = new string [ exportSources . Count ] ;
List < ExportSourceData > successfullyExportedSources = new List < ExportSourceData > ( ) ;
2023-06-06 17:40:06 -04:00
2023-08-24 12:05:59 -04:00
// Get the Zen executable path
2024-05-22 13:17:22 -04:00
FileReference zenExe = ZenExeFileReference ( ) ;
2023-06-06 17:40:06 -04:00
2023-08-28 02:02:10 -04:00
// Format the command line
2024-05-22 13:17:22 -04:00
StringBuilder oplogExportCommandline = new StringBuilder ( ) ;
oplogExportCommandline . Append ( "oplog-export --embedloosefiles" ) ;
2024-05-28 15:59:41 -04:00
if ( _parameters . ForceExport )
2024-03-25 19:36:39 -04:00
{
2024-05-22 13:17:22 -04:00
oplogExportCommandline . Append ( " --force" ) ;
2024-03-25 19:36:39 -04:00
}
2023-06-06 17:40:06 -04:00
2024-05-22 13:17:22 -04:00
switch ( destinationStorageType )
2023-06-06 17:40:06 -04:00
{
case SnapshotStorageType . Cloud :
2024-05-22 13:17:22 -04:00
if ( string . IsNullOrEmpty ( _parameters . DestinationCloudHost ) )
2023-06-06 17:40:06 -04:00
{
throw new AutomationException ( "Missing destination cloud host" ) ;
}
2024-05-22 13:17:22 -04:00
if ( string . IsNullOrEmpty ( _parameters . DestinationCloudNamespace ) )
2023-06-06 17:40:06 -04:00
{
throw new AutomationException ( "Missing destination cloud namespace" ) ;
}
2024-05-22 13:17:22 -04:00
if ( string . IsNullOrEmpty ( _parameters . DestinationIdentifier ) )
2023-06-06 17:40:06 -04:00
{
2023-09-30 11:46:22 -04:00
throw new AutomationException ( "Missing destination identifier when exporting to cloud" ) ;
2023-06-06 17:40:06 -04:00
}
2024-05-22 13:17:22 -04:00
string bucketName = _parameters . DestinationCloudBucket ;
string projectNameAsBucketName = projectFile . GetFileNameWithoutAnyExtensions ( ) . ToLowerInvariant ( ) ;
if ( string . IsNullOrEmpty ( bucketName ) )
2023-06-06 17:40:06 -04:00
{
2024-05-22 13:17:22 -04:00
bucketName = projectNameAsBucketName ;
2023-06-06 17:40:06 -04:00
}
2024-05-22 13:17:22 -04:00
bucketName = SanitizeBucketName ( bucketName ) ;
2023-06-06 17:40:06 -04:00
2024-05-22 13:17:22 -04:00
oplogExportCommandline . AppendFormat ( " --cloud {0} --namespace {1} --bucket {2}" , _parameters . DestinationCloudHost , _parameters . DestinationCloudNamespace , bucketName ) ;
2023-06-06 17:40:06 -04:00
2024-05-22 13:17:22 -04:00
if ( ! string . IsNullOrEmpty ( _parameters . DestinationCloudHttpVersion ) )
2024-02-29 12:25:13 -05:00
{
2024-05-22 13:17:22 -04:00
if ( _parameters . DestinationCloudHttpVersion . Equals ( "http2-only" , StringComparison . OrdinalIgnoreCase ) )
2024-02-29 12:25:13 -05:00
{
2024-05-22 13:17:22 -04:00
oplogExportCommandline . Append ( " --assume-http2" ) ;
2024-02-29 12:25:13 -05:00
}
else
{
throw new AutomationException ( "Unexpected destination cloud http version" ) ;
}
}
2024-05-22 13:17:22 -04:00
exportIndex = 0 ;
foreach ( ExportSourceData exportSource in exportSources )
2023-06-06 17:40:06 -04:00
{
2024-05-22 13:17:22 -04:00
string hostUrlArg = string . Format ( "--hosturl http://{0}:{1}" , exportSource . _isLocalHost ? "localhost" : exportSource . _hostName , exportSource . _hostPort ) ;
string baseKeyArg = string . Empty ;
if ( ( exportSource . _snapshotBaseDescriptor ! = null ) & & ! string . IsNullOrEmpty ( exportSource . _snapshotBaseDescriptor . Key ) )
2023-09-30 11:46:22 -04:00
{
2024-05-22 13:17:22 -04:00
if ( exportSource . _snapshotBaseDescriptor . Type = = SnapshotStorageType . Cloud )
2023-09-30 11:46:22 -04:00
{
2024-05-22 13:17:22 -04:00
baseKeyArg = " --basekey " + exportSource . _snapshotBaseDescriptor . Key ;
2023-09-30 11:46:22 -04:00
}
else
{
2024-05-22 13:17:22 -04:00
Logger . LogWarning ( "Base snapshot descriptor was for a snapshot storage type {Type}, but we're producing a snapshot of type cloud. Skipping use of base snapshot." , exportSource . _snapshotBaseDescriptor . Type ) ;
2023-09-30 11:46:22 -04:00
}
}
2023-06-13 23:49:03 -04:00
2024-05-22 13:17:22 -04:00
StringBuilder exportSingleSourceCommandline = new StringBuilder ( oplogExportCommandline . Length ) ;
exportSingleSourceCommandline . Append ( oplogExportCommandline ) ;
2023-06-06 17:40:06 -04:00
2024-05-22 13:17:22 -04:00
StringBuilder destinationKeyBuilder = new StringBuilder ( ) ;
destinationKeyBuilder . AppendFormat ( "{0}.{1}.{2}" , projectNameAsBucketName , _parameters . DestinationIdentifier , exportSource . _oplogId ) ;
exportNames [ exportIndex ] = destinationKeyBuilder . ToString ( ) . ToLowerInvariant ( ) ;
IoHash destinationKeyHash = IoHash . Compute ( Encoding . UTF8 . GetBytes ( exportNames [ exportIndex ] ) ) ;
2023-06-06 17:40:06 -04:00
2024-05-22 13:17:22 -04:00
exportSingleSourceCommandline . AppendFormat ( " {0} --key {1} {2} {3} {4}" , hostUrlArg , destinationKeyHash . ToString ( ) . ToLowerInvariant ( ) , baseKeyArg , exportSource . _projectId , exportSource . _oplogId ) ;
2024-05-28 15:59:41 -04:00
if ( _parameters . SkipExport | | TryExportOplogCommand ( zenExe . FullName , exportSingleSourceCommandline . ToString ( ) ) )
2024-04-06 00:55:05 -04:00
{
2024-05-22 13:17:22 -04:00
successfullyExportedSources . Add ( exportSource ) ;
2024-04-06 00:55:05 -04:00
}
2023-06-06 17:40:06 -04:00
2024-05-22 13:17:22 -04:00
exportIndex = exportIndex + 1 ;
2023-06-06 17:40:06 -04:00
}
2024-03-28 17:19:08 -04:00
break ;
case SnapshotStorageType . Zen :
2024-05-22 13:17:22 -04:00
if ( string . IsNullOrEmpty ( _parameters . DestinationZenHost ) )
2024-03-28 17:19:08 -04:00
{
throw new AutomationException ( "Missing destination zen host" ) ;
}
2024-05-22 13:17:22 -04:00
if ( string . IsNullOrEmpty ( _parameters . DestinationIdentifier ) )
2024-03-28 17:19:08 -04:00
{
throw new AutomationException ( "Missing destination identifier when exporting to zen" ) ;
}
2024-05-22 13:17:22 -04:00
string projectName = projectFile . GetFileNameWithoutAnyExtensions ( ) . ToLowerInvariant ( ) + ".oplog" ;
2024-03-28 17:19:08 -04:00
2024-05-22 13:17:22 -04:00
StringBuilder createProjectCommandline = new StringBuilder ( ) ;
createProjectCommandline . AppendFormat ( "project-create --hosturl {0} {1}" , _parameters . DestinationZenHost , projectName ) ;
TryRunAndLogWithoutSpew ( zenExe . FullName , createProjectCommandline . ToString ( ) , true ) ;
2024-03-28 18:55:45 -04:00
2024-05-22 13:17:22 -04:00
oplogExportCommandline . AppendFormat ( " --zen {0}" , _parameters . DestinationZenHost ) ;
2024-03-28 17:19:08 -04:00
2024-05-22 13:17:22 -04:00
exportIndex = 0 ;
foreach ( ExportSourceData exportSource in exportSources )
2024-03-28 17:19:08 -04:00
{
2024-05-22 13:17:22 -04:00
string hostUrlArg = string . Format ( "--hosturl http://{0}:{1}" , exportSource . _isLocalHost ? "localhost" : exportSource . _hostName , exportSource . _hostPort ) ;
2024-03-28 17:19:08 -04:00
2024-05-22 13:17:22 -04:00
StringBuilder exportSingleSourceCommandline = new StringBuilder ( oplogExportCommandline . Length ) ;
exportSingleSourceCommandline . Append ( oplogExportCommandline ) ;
2024-03-28 17:19:08 -04:00
2024-05-22 13:17:22 -04:00
StringBuilder destinationKeyBuilder = new StringBuilder ( ) ;
destinationKeyBuilder . AppendFormat ( "{0}.{1}" , _parameters . DestinationIdentifier , exportSource . _oplogId ) ;
exportNames [ exportIndex ] = destinationKeyBuilder . ToString ( ) . ToLowerInvariant ( ) ;
string destinationOplog = SanitizeOplogName ( exportNames [ exportIndex ] ) ;
2024-03-28 17:19:08 -04:00
2024-05-22 13:17:22 -04:00
exportSingleSourceCommandline . AppendFormat ( " {0} --target-project {1} --target-oplog {2} {3} {4}" , hostUrlArg , projectName , destinationOplog , exportSource . _projectId , exportSource . _oplogId ) ;
2024-05-28 15:59:41 -04:00
if ( _parameters . SkipExport | | TryExportOplogCommand ( zenExe . FullName , exportSingleSourceCommandline . ToString ( ) ) )
2024-04-06 00:55:05 -04:00
{
2024-05-22 13:17:22 -04:00
successfullyExportedSources . Add ( exportSource ) ;
2024-04-06 00:55:05 -04:00
}
2024-03-28 17:19:08 -04:00
2024-05-22 13:17:22 -04:00
exportIndex = exportIndex + 1 ;
2024-03-28 17:19:08 -04:00
}
2023-08-22 15:43:51 -04:00
break ;
case SnapshotStorageType . File :
2024-05-22 13:17:22 -04:00
string defaultProjectId = ProjectUtils . GetProjectPathId ( projectFile ) ;
exportIndex = 0 ;
foreach ( ExportSourceData exportSource in exportSources )
2023-09-30 11:46:22 -04:00
{
2024-05-22 13:17:22 -04:00
StringBuilder exportNameBuilder = new StringBuilder ( ) ;
exportNameBuilder . AppendFormat ( "{0}.{1}.{2}" , projectFile . GetFileNameWithoutAnyExtensions ( ) . ToLowerInvariant ( ) , _parameters . DestinationIdentifier , exportSource . _oplogId ) ;
exportNames [ exportIndex ] = exportNameBuilder . ToString ( ) . ToLowerInvariant ( ) ;
2023-08-22 15:43:51 -04:00
2024-05-22 13:17:22 -04:00
StringBuilder exportSingleSourceCommandline = new StringBuilder ( oplogExportCommandline . Length ) ;
exportSingleSourceCommandline . Append ( oplogExportCommandline ) ;
2023-09-30 11:46:22 -04:00
2024-05-22 13:17:22 -04:00
string destinationFileName = exportSource . _oplogId ;
if ( ! string . IsNullOrEmpty ( _parameters . DestinationFileName ) )
2023-09-30 11:46:22 -04:00
{
2024-05-22 13:17:22 -04:00
destinationFileName = _parameters . DestinationFileName . Replace ( "{Platform}" , exportSource . _targetPlatform , StringComparison . InvariantCultureIgnoreCase ) ;
2023-09-30 11:46:22 -04:00
}
2024-05-22 13:17:22 -04:00
string projectId = string . IsNullOrEmpty ( exportSource . _projectId ) ? defaultProjectId : exportSource . _projectId ;
string baseNameArg = string . Empty ;
DirectoryReference platformDestinationFileDir = new DirectoryReference ( _parameters . DestinationFileDir . FullName . Replace ( "{Platform}" , exportSource . _targetPlatform , StringComparison . InvariantCultureIgnoreCase ) ) ;
if ( ( exportSource . _snapshotBaseDescriptor ! = null ) & & ! string . IsNullOrEmpty ( exportSource . _snapshotBaseDescriptor . Directory ) & & ! string . IsNullOrEmpty ( exportSource . _snapshotBaseDescriptor . Filename ) )
2023-09-30 11:46:22 -04:00
{
2024-05-22 13:17:22 -04:00
if ( exportSource . _snapshotBaseDescriptor . Type = = SnapshotStorageType . File )
2023-09-30 11:46:22 -04:00
{
2024-05-22 13:17:22 -04:00
FileReference baseSnapshotFile = new FileReference ( Path . Combine ( exportSource . _snapshotBaseDescriptor . Directory , exportSource . _snapshotBaseDescriptor . Filename ) ) ;
if ( FileReference . Exists ( baseSnapshotFile ) )
2023-09-30 11:46:22 -04:00
{
2024-05-22 13:17:22 -04:00
baseNameArg = " --basename " + CommandUtils . MakePathSafeToUseWithCommandLine ( baseSnapshotFile . FullName ) ;
2023-09-30 11:46:22 -04:00
}
else
{
Logger . LogWarning ( "Base snapshot descriptor missing. Skipping use of base snapshot." ) ;
}
}
else
{
2024-05-22 13:17:22 -04:00
Logger . LogWarning ( "Base snapshot descriptor was for a snapshot storage type {Type}, but we're producing a snapshot of type file. Skipping use of base snapshot." , exportSource . _snapshotBaseDescriptor . Type ) ;
2023-09-30 11:46:22 -04:00
}
}
2024-05-22 13:17:22 -04:00
exportSingleSourceCommandline . AppendFormat ( " --file {0} --name {1} {2} {3} {4}" , CommandUtils . MakePathSafeToUseWithCommandLine ( platformDestinationFileDir . FullName ) , destinationFileName , baseNameArg , projectId , exportSource . _oplogId ) ;
2023-09-30 11:46:22 -04:00
2024-05-28 15:59:41 -04:00
if ( _parameters . SkipExport | | TryExportOplogCommand ( zenExe . FullName , exportSingleSourceCommandline . ToString ( ) ) )
2024-04-06 00:55:05 -04:00
{
2024-05-22 13:17:22 -04:00
successfullyExportedSources . Add ( exportSource ) ;
2024-04-06 00:55:05 -04:00
}
2023-09-30 11:46:22 -04:00
2024-05-22 13:17:22 -04:00
exportIndex = exportIndex + 1 ;
2023-09-30 11:46:22 -04:00
}
2023-06-06 17:40:06 -04:00
break ;
default :
2024-05-22 13:17:22 -04:00
throw new AutomationException ( "Unknown/invalid/unimplemented destination storage type - {0}" , _parameters . DestinationStorageType ) ;
2023-06-06 17:40:06 -04:00
}
2023-09-30 11:46:22 -04:00
2024-05-22 13:17:22 -04:00
if ( ( _parameters . SnapshotDescriptorFile ! = null ) & & successfullyExportedSources . Any ( ) )
2023-09-30 11:46:22 -04:00
{
2024-05-22 13:17:22 -04:00
if ( _parameters . SnapshotDescriptorFile . FullName . Contains ( "{Platform}" , StringComparison . OrdinalIgnoreCase ) )
2023-09-30 11:46:22 -04:00
{
// Separate descriptor file per platform
2024-05-22 13:17:22 -04:00
exportIndex = 0 ;
foreach ( ExportSourceData exportSource in successfullyExportedSources )
2023-09-30 11:46:22 -04:00
{
2024-05-22 13:17:22 -04:00
FileReference platformSnapshotDescriptorFile = new FileReference ( _parameters . SnapshotDescriptorFile . FullName . Replace ( "{Platform}" , exportSource . _targetPlatform , StringComparison . InvariantCultureIgnoreCase ) ) ;
DirectoryReference . CreateDirectory ( platformSnapshotDescriptorFile . Directory ) ;
using ( JsonWriter writer = new JsonWriter ( platformSnapshotDescriptorFile ) )
2023-09-30 11:46:22 -04:00
{
2024-05-22 13:17:22 -04:00
writer . WriteObjectStart ( ) ;
writer . WriteArrayStart ( "snapshots" ) ;
WriteExportSource ( writer , destinationStorageType , exportSource , exportNames [ exportIndex ] ) ;
writer . WriteArrayEnd ( ) ;
writer . WriteObjectEnd ( ) ;
2023-09-30 11:46:22 -04:00
}
2024-05-22 13:17:22 -04:00
exportIndex = exportIndex + 1 ;
2023-09-30 11:46:22 -04:00
}
}
else
{
// Write out a single snapshot descriptor with info about all snapshots
2024-05-22 13:17:22 -04:00
DirectoryReference . CreateDirectory ( _parameters . SnapshotDescriptorFile . Directory ) ;
using ( JsonWriter writer = new JsonWriter ( _parameters . SnapshotDescriptorFile ) )
2023-09-30 11:46:22 -04:00
{
2024-05-22 13:17:22 -04:00
writer . WriteObjectStart ( ) ;
writer . WriteArrayStart ( "snapshots" ) ;
exportIndex = 0 ;
foreach ( ExportSourceData exportSource in successfullyExportedSources )
2023-09-30 11:46:22 -04:00
{
2024-05-22 13:17:22 -04:00
WriteExportSource ( writer , destinationStorageType , exportSource , exportNames [ exportIndex ] ) ;
exportIndex = exportIndex + 1 ;
2023-09-30 11:46:22 -04:00
}
2024-05-22 13:17:22 -04:00
writer . WriteArrayEnd ( ) ;
writer . WriteObjectEnd ( ) ;
2023-09-30 11:46:22 -04:00
}
}
}
2023-06-06 17:40:06 -04:00
return Task . CompletedTask ;
}
/// <summary>
/// Output this task out to an XML writer.
/// </summary>
2024-05-22 13:17:22 -04:00
public override void Write ( XmlWriter writer )
2023-06-06 17:40:06 -04:00
{
2024-05-22 13:17:22 -04:00
Write ( writer , _parameters ) ;
2023-06-06 17:40:06 -04:00
}
/// <summary>
/// Find all the tags which are used as inputs to this task
/// </summary>
/// <returns>The tag names which are read by this task</returns>
public override IEnumerable < string > FindConsumedTagNames ( )
{
yield break ;
}
/// <summary>
/// Find all the tags which are modified by this task
/// </summary>
/// <returns>The tag names which are modified by this task</returns>
public override IEnumerable < string > FindProducedTagNames ( )
{
yield break ;
}
}
}