2019-12-26 23:01:54 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2019-07-23 15:25:57 -04:00
using System ;
using System.Collections.Generic ;
using System.Text ;
using System.IO ;
using System.Linq ;
using AutomationTool ;
using UnrealBuildTool ;
2020-12-21 23:07:37 -04:00
using EpicGames.Core ;
2019-08-01 13:28:12 -04:00
using System.Text.RegularExpressions ;
2020-10-29 13:38:15 -04:00
using System.Threading ;
2021-06-17 01:49:00 -04:00
using UnrealBuildBase ;
2023-03-08 12:43:35 -05:00
using Microsoft.Extensions.Logging ;
2020-10-29 13:38:15 -04:00
abstract class SyncProjectBase : BuildCommand
{
protected bool FindProjectFileAndP4Path ( string InProjectArgument , out FileReference OutProjectFile , out string OutP4ProjectPath )
{
OutProjectFile = null ;
OutP4ProjectPath = null ;
OutProjectFile = ProjectUtils . FindProjectFileFromName ( InProjectArgument ) ;
if ( OutProjectFile ! = null )
{
// get the path in P4
P4WhereRecord Record = GetP4RecordForPath ( OutProjectFile . FullName ) ;
OutP4ProjectPath = Record . ClientFile ;
}
else
{
// if they provided a name and not a path then find the file (requires that it's synced).
string RelativePath = InProjectArgument ;
if ( Path . GetExtension ( RelativePath ) . Length = = 0 )
{
RelativePath = Path . ChangeExtension ( RelativePath , "uproject" ) ;
}
if ( ! RelativePath . Contains ( Path . DirectorySeparatorChar ) & & ! RelativePath . Contains ( Path . AltDirectorySeparatorChar ) )
{
RelativePath = CommandUtils . CombinePaths ( PathSeparator . Slash , Path . GetFileNameWithoutExtension ( RelativePath ) , RelativePath ) ;
}
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "{InProjectArgument} not on disk. Searching P4 for {RelativePath}" , InProjectArgument , RelativePath ) ;
2020-10-29 13:38:15 -04:00
List < string > SearchPaths = new List < string > ( ) ;
SearchPaths . Add ( "" ) ;
string ProjectDirsFile = Directory . EnumerateFiles ( CommandUtils . CombinePaths ( CmdEnv . LocalRoot ) , "*.uprojectdirs" ) . FirstOrDefault ( ) ;
if ( ProjectDirsFile ! = null )
{
foreach ( string FilePath in File . ReadAllLines ( ProjectDirsFile ) )
{
string Trimmed = FilePath . Trim ( ) ;
if ( ! Trimmed . StartsWith ( "./" , StringComparison . OrdinalIgnoreCase ) & &
! Trimmed . StartsWith ( ";" , StringComparison . OrdinalIgnoreCase ) & &
Trimmed . IndexOfAny ( Path . GetInvalidPathChars ( ) ) < 0 )
{
SearchPaths . Add ( Trimmed ) ;
}
}
}
// Get the root of the branch containing the selected file
foreach ( string SearchPath in SearchPaths )
{
string P4Path = CommandUtils . CombinePaths ( PathSeparator . Slash , P4Env . ClientRoot , SearchPath , RelativePath ) ;
if ( P4 . FileExistsInDepot ( P4Path ) )
{
P4WhereRecord Record = GetP4RecordForPath ( P4Path ) ;
// make sure to sync with //workspace/path as it cleans up files if the user has stream switched
OutP4ProjectPath = Record . ClientFile ;
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "Found project at {OutP4ProjectPath}" , OutP4ProjectPath ) ;
2020-10-29 13:38:15 -04:00
break ;
}
}
}
2023-03-08 12:43:35 -05:00
Logger . LogDebug ( "Resolved {InProjectArgument} to P4 Path {OutP4ProjectPath}" , InProjectArgument , OutP4ProjectPath ) ;
2020-10-29 13:38:15 -04:00
return OutP4ProjectPath ! = null & & OutProjectFile ! = null ;
}
protected P4WhereRecord GetP4RecordForPath ( string FilePath )
{
return P4 . Where ( FilePath , AllowSpew : false ) . FirstOrDefault ( x = > x . DepotFile ! = null & & ! x . bUnmap ) ;
}
}
2019-07-23 15:25:57 -04:00
[Help("Syncs and builds all the binaries required for a project")]
[Help("project=<FortniteGame>", "Project to sync. Will search current path and paths in ueprojectdirs. If omitted will sync projectdirs")]
[Help("threads=N", "How many threads to use when syncing. Default=2. When >1 all output happens first")]
2019-08-13 16:24:19 -04:00
[Help("cl=<12345>", "Changelist to sync to. If omitted will sync to latest CL of the workspace path. 0 Will Remove files!")]
2019-08-06 16:06:27 -04:00
[Help("clean", "Clean old files before building")]
2019-07-23 15:25:57 -04:00
[Help("build", "Build after syncing")]
2019-08-01 13:28:12 -04:00
[Help("open", "Open project editor after syncing")]
[Help("generate", "Generate project files after syncing")]
2019-07-23 15:25:57 -04:00
[Help("force", "force sync files (files opened for edit will be untouched)")]
[Help("preview", "Shows commands that will be executed but performs no operations")]
2019-08-13 16:24:19 -04:00
[Help("projectonly", "Only sync the project")]
[Help("paths", "Only sync this path. Can be comma-separated list.")]
2019-08-01 13:28:12 -04:00
[Help("retries=n", "Number of retries for a timed out file. Defaults to 3")]
[Help("unversioned", "Do not set an engine version after syncing")]
2019-08-13 16:24:19 -04:00
[Help("path", "Only sync files that match this path. Can be comma-separated list.")]
2019-07-23 15:25:57 -04:00
[RequireP4]
[DoesNotNeedP4CL]
2020-10-29 13:38:15 -04:00
class SyncProject : SyncProjectBase
2019-07-23 15:25:57 -04:00
{
public override ExitCode Execute ( )
{
2023-02-27 10:20:42 -05:00
if ( ! ParseParam ( "Deprecated" ) )
{
2023-03-08 12:43:35 -05:00
Logger . LogError ( "**************************************************************************" ) ;
Logger . LogError ( "The SyncProject command has been deprecated, and will be removed in an" ) ;
Logger . LogError ( "upcoming UnrealEngine release." ) ;
Logger . LogError ( "" ) ;
Logger . LogError ( "Similar functionality is available through the UnrealGameSync command-line" ) ;
Logger . LogError ( "tool, which is supported on Windows, Mac and Linux." ) ;
Logger . LogError ( "" ) ;
Logger . LogError ( "To ignore this warning and continue to using SyncProject anyway, add the" ) ;
Logger . LogError ( "-Deprecated argument to the command line." ) ;
Logger . LogError ( "**************************************************************************" ) ;
2023-02-27 10:20:42 -05:00
return ExitCode . Error_Unknown ;
}
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "************************* SyncProject" ) ;
2019-07-23 15:25:57 -04:00
// These are files that should always be synced because tools update them
string [ ] ForceSyncFiles = new string [ ]
{
"Engine/Build/Build.version" ,
2021-08-10 13:35:01 -04:00
"Engine/Source/Programs/Shared/MetaData.cs"
2019-07-23 15:25:57 -04:00
} ;
2019-08-13 16:24:19 -04:00
// Parse the project filename (as a local path)
string ProjectArg = ParseParamValue ( "Project" , null ) ;
2019-07-23 15:25:57 -04:00
// Parse the changelist to sync to. -1 will sync to head.
int CL = ParseParamInt ( "CL" , - 1 ) ;
int Threads = ParseParamInt ( "threads" , 2 ) ;
bool ForceSync = ParseParam ( "force" ) ;
bool PreviewOnly = ParseParam ( "preview" ) ;
2019-08-13 16:24:19 -04:00
2019-08-01 13:28:12 -04:00
bool ProjectOnly = ParseParam ( "projectonly" ) ;
2019-08-13 16:24:19 -04:00
2019-08-01 13:28:12 -04:00
int Retries = ParseParamInt ( "retries" , 3 ) ;
bool Unversioned = ParseParam ( "unversioned" ) ;
2019-07-23 15:25:57 -04:00
2019-08-01 13:28:12 -04:00
bool BuildProject = ParseParam ( "build" ) ;
bool OpenProject = ParseParam ( "open" ) ;
bool GenerateProject = ParseParam ( "generate" ) ;
2019-08-13 16:24:19 -04:00
string PathArg = ParseParamValue ( "paths" , "" ) ;
2019-07-23 15:25:57 -04:00
2019-08-13 16:24:19 -04:00
IEnumerable < string > ExplicitPaths = PathArg . Split ( new char [ ] { ',' , ';' } , StringSplitOptions . RemoveEmptyEntries ) . Select ( S = > S . Trim ( ) ) ;
2020-01-10 14:30:31 -05:00
if ( CL = = 0 )
2019-07-23 15:25:57 -04:00
{
2020-01-10 14:30:31 -05:00
if ( ! ForceSync )
{
throw new AutomationException ( "If using 0 CL to remove files -force must also be specified" ) ;
}
// Cannot unsync the engine folder
ProjectOnly = true ;
2019-07-23 15:25:57 -04:00
}
else if ( CL = = - 1 )
{
// find most recent change
List < P4Connection . ChangeRecord > Records ;
string Cmd = string . Format ( "-s submitted -m1 //{0}/..." , P4Env . Client ) ;
if ( ! P4 . Changes ( out Records , Cmd , false ) )
{
throw new AutomationException ( "p4 changes failed. Cannot find most recent CL" ) ;
}
CL = Records . First ( ) . CL ;
}
2019-08-13 16:24:19 -04:00
bool EngineOnly = string . IsNullOrEmpty ( ProjectArg ) ;
2019-07-23 15:25:57 -04:00
2019-08-13 16:24:19 -04:00
// Will be the full local path to the project file once resolved
FileReference ProjectFile = null ;
// Will be the full workspace path to the project in P4 (if a project was specified)
2019-08-01 13:28:12 -04:00
string P4ProjectPath = null ;
2019-08-13 16:24:19 -04:00
// Will be the full workspace path to the engine in P4 (If projectonly wasn't specified);
string P4EnginePath = null ;
2019-07-23 15:25:57 -04:00
2019-08-13 16:24:19 -04:00
// If we're syncing a project find where it is in P4
if ( ! EngineOnly )
{
2020-10-29 13:38:15 -04:00
if ( ! FindProjectFileAndP4Path ( ProjectArg , out ProjectFile , out P4ProjectPath ) )
2019-08-13 16:24:19 -04:00
{
throw new AutomationException ( "Could not find project file for {0} locally or in P4. Provide a full path or check the subdirectory is listed in UE4Games.uprojectdirs" , ProjectArg ) ;
2020-10-29 13:38:15 -04:00
}
2019-07-23 15:25:57 -04:00
}
// Build the list of paths that need syncing
List < string > SyncPaths = new List < string > ( ) ;
2019-08-13 16:24:19 -04:00
//
if ( ExplicitPaths . Any ( ) )
2019-07-23 15:25:57 -04:00
{
2019-08-13 16:24:19 -04:00
// Add all explicit paths as <root>/Path/...
ExplicitPaths . ToList ( ) . ForEach ( P = > SyncPaths . Add ( CommandUtils . CombinePaths ( PathSeparator . Slash , P4Env . ClientRoot , P , "..." ) ) ) ;
2019-07-23 15:25:57 -04:00
}
2019-08-13 16:24:19 -04:00
else
2019-07-23 15:25:57 -04:00
{
2019-11-26 11:22:47 -05:00
// See if the engine is in P4 too by checking the p4 location of a local file
2020-10-29 13:38:15 -04:00
if ( ! ProjectOnly )
2019-08-13 16:24:19 -04:00
{
2020-10-29 13:38:15 -04:00
string LocalEngineFile = CommandUtils . CombinePaths ( CmdEnv . LocalRoot , "Engine" , "Source" , "UnrealEditor.target.cs" ) ;
P4WhereRecord EngineRecord = GetP4RecordForPath ( LocalEngineFile ) ;
if ( P4 . FileExistsInDepot ( EngineRecord . DepotFile ) )
{
// make sure to sync with //workspace/path as it cleans up files if the user has stream switched
P4EnginePath = EngineRecord . ClientFile . Replace ( "Engine/Source/UnrealEditor.target.cs" , "" ) ;
SyncPaths . Add ( CommandUtils . CombinePaths ( PathSeparator . Slash , P4EnginePath + "*" ) ) ;
SyncPaths . Add ( CommandUtils . CombinePaths ( PathSeparator . Slash , P4EnginePath , "Engine" , "..." ) ) ;
}
2019-08-13 16:24:19 -04:00
}
if ( ! string . IsNullOrEmpty ( P4ProjectPath ) )
{
string P4ProjectDir = Regex . Replace ( P4ProjectPath , @"[^/]+\.uproject" , "..." , RegexOptions . IgnoreCase ) ;
SyncPaths . Add ( P4ProjectDir ) ;
}
}
2019-07-23 15:25:57 -04:00
// Force these files as they can be overwritten by tools
2019-08-13 16:24:19 -04:00
if ( ! PreviewOnly & & ! string . IsNullOrEmpty ( P4EnginePath ) & & ! ProjectOnly )
2019-07-23 15:25:57 -04:00
{
2019-08-13 16:24:19 -04:00
IEnumerable < string > ForceSyncList = ForceSyncFiles . Select ( F = > CommandUtils . CombinePaths ( PathSeparator . Slash , P4EnginePath , F ) ) ;
2019-07-23 15:25:57 -04:00
foreach ( var F in ForceSyncList )
{
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "Force-updating {F}" , F ) ;
2019-07-23 15:25:57 -04:00
string SyncCommand = string . Format ( "-f {0}@{1}" , F , CL ) ;
// sync with retries
2019-08-13 16:24:19 -04:00
P4 . Sync ( SyncCommand , true , false , Retries ) ;
2019-07-23 15:25:57 -04:00
}
}
// Sync all the things
foreach ( string SyncPath in SyncPaths )
{
string SyncCommand = "" ;
if ( Threads > 1 )
{
SyncCommand = string . Format ( " --parallel \"threads={0}\" {1}" , Threads , SyncCommand ) ;
}
if ( ForceSync )
{
SyncCommand = SyncCommand + " -f" ;
}
SyncCommand + = string . Format ( " {0}@{1}" , SyncPath , CL ) ;
if ( ! PreviewOnly )
{
// sync with retries
2019-08-13 16:24:19 -04:00
P4 . Sync ( SyncCommand , true , false , Retries ) ;
2019-07-23 15:25:57 -04:00
}
else
{
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "sync {SyncCommand}" , SyncCommand ) ;
2019-07-23 15:25:57 -04:00
}
}
2019-08-13 16:24:19 -04:00
// P4 utils don't return errors :(
2019-07-23 15:25:57 -04:00
ExitCode ExitStatus = ExitCode . Success ;
2019-08-13 16:24:19 -04:00
// Sync is complete so do the actions
if ( ! PreviewOnly & & CL > 0 )
2019-07-23 15:25:57 -04:00
{
2019-08-13 16:24:19 -04:00
// Argument to pass to the editor (could be null with no project).
string ProjectArgForEditor = "" ;
if ( ! string . IsNullOrEmpty ( ProjectArg ) )
{
// If we synced the project from P4 we couldn't resolve this earlier
if ( ProjectFile = = null )
{
NativeProjects . ClearCache ( ) ;
ProjectFile = ProjectUtils . FindProjectFileFromName ( ProjectArg ) ;
}
ProjectArgForEditor = ProjectFile . FullName ;
}
2019-08-01 13:28:12 -04:00
if ( GenerateProject )
{
2023-03-08 12:43:35 -05:00
Logger . LogDebug ( "Generating project files for {ProjectArgForEditor}" , ProjectArgForEditor ) ;
2019-08-13 16:24:19 -04:00
2021-01-31 15:09:58 -04:00
if ( BuildHostPlatform . Current . Platform = = UnrealTargetPlatform . Win64 )
2019-08-01 13:28:12 -04:00
{
2019-08-13 16:24:19 -04:00
CommandUtils . Run ( "GenerateProjectFiles.bat" , ProjectArgForEditor ) ;
2019-08-01 13:28:12 -04:00
}
else
{
2019-08-13 16:24:19 -04:00
CommandUtils . Run ( "GenerateProjectFiles.sh" , ProjectArgForEditor ) ;
2019-08-01 13:28:12 -04:00
}
}
2021-10-27 15:14:40 -04:00
UnrealBuild Build = new UnrealBuild ( this ) ;
2019-08-13 16:24:19 -04:00
if ( ! Unversioned & & ! ProjectOnly )
2019-08-01 13:28:12 -04:00
{
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "Updating Version files to CL: {CL}" , CL ) ;
2019-08-01 13:28:12 -04:00
Build . UpdateVersionFiles ( ActuallyUpdateVersionFiles : true , ChangelistNumberOverride : CL , IsPromotedOverride : false ) ;
}
2019-07-23 15:25:57 -04:00
// Build everything
2019-08-01 13:28:12 -04:00
if ( BuildProject & & ExitStatus = = ExitCode . Success )
2019-07-23 15:25:57 -04:00
{
2023-03-08 12:43:35 -05:00
Logger . LogDebug ( "Building Editor for {ProjectArgForEditor}" , ProjectArgForEditor ) ;
2019-08-13 16:24:19 -04:00
2020-04-05 14:46:04 -04:00
// Invalidate the location of the Target.cs files incase they were synced
if ( ProjectFile ! = null )
{
string TargetSourceDir = CommandUtils . CombinePaths ( Path . GetDirectoryName ( ProjectFile . FullName ) , "Source" ) ;
2021-08-09 10:39:09 -04:00
Rules . InvalidateRulesFileCache ( TargetSourceDir ) ;
2020-04-05 14:46:04 -04:00
}
2019-07-23 15:25:57 -04:00
BuildEditor BuildCmd = new BuildEditor ( ) ;
2022-07-20 18:42:29 -04:00
BuildCmd . Params = this . Params ;
2019-07-23 15:25:57 -04:00
ExitStatus = BuildCmd . Execute ( ) ;
}
2022-09-12 18:21:40 -04:00
// If both -build and -open are specified
// on the commandline, then we will rely on the successful build to call
// it's open of the editor. Otherwise just open the editor
if ( ! BuildProject & & OpenProject & & ExitStatus = = ExitCode . Success )
2019-07-23 15:25:57 -04:00
{
2023-03-08 12:43:35 -05:00
Logger . LogDebug ( "Opening Editor for {ProjectArgForEditor}" , ProjectArgForEditor ) ;
2019-08-13 16:24:19 -04:00
2019-07-23 15:25:57 -04:00
OpenEditor OpenCmd = new OpenEditor ( ) ;
2022-07-20 18:42:29 -04:00
OpenCmd . Params = this . Params ;
2019-07-23 15:25:57 -04:00
ExitStatus = OpenCmd . Execute ( ) ;
}
}
return ExitCode . Success ;
2020-10-29 13:38:15 -04:00
}
2019-07-23 15:25:57 -04:00
}