2019-07-23 15:25:57 -04:00
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
using System.Text ;
using System.IO ;
using System.Linq ;
using AutomationTool ;
using UnrealBuildTool ;
using Tools.DotNETCommon ;
2019-08-01 13:28:12 -04:00
using System.Text.RegularExpressions ;
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")]
[Help("cl=< 12345 >", "Changelist to sync to. If omitted will sync to latest CL of the workspace path")]
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-01 13:28:12 -04:00
[Help("projectonly", "Only shync the project")]
[Help("maxwait=n", "Maximum wait time for a file. Defaults to 0 (disabled).")]
[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-07-23 15:25:57 -04:00
[RequireP4]
[DoesNotNeedP4CL]
class SyncProject : BuildCommand
{
public override ExitCode Execute ( )
{
LogInformation ( "************************* SyncProject" ) ;
// These are files that should always be synced because tools update them
string [ ] ForceSyncFiles = new string [ ]
{
"Engine/Build/Build.version" ,
"Engine/Source/Programs/DotNETCommon/MetaData.cs"
} ;
// 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-01 13:28:12 -04:00
bool ProjectOnly = ParseParam ( "projectonly" ) ;
int MaxWait = ParseParamInt ( "maxwait" , 0 ) ;
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-07-23 15:25:57 -04:00
if ( CL = = 0 )
{
throw new AutomationException ( "If specified CL must be a number. Omit -CL for latest." ) ;
}
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 ;
}
// Parse the project filename (as a local path)
2019-08-01 13:28:12 -04:00
string ProjectName = ParseParamValue ( "Project" , null ) ;
2019-07-23 15:25:57 -04:00
2019-08-01 13:28:12 -04:00
// this will be the path to the project in P4, if one is specified
string P4ProjectPath = null ;
if ( ! string . IsNullOrEmpty ( ProjectName ) )
2019-07-23 15:25:57 -04:00
{
2019-08-01 13:28:12 -04:00
// We want to find the Perforce path to the project so we can sync this even if it doesn't
// exist locally
2019-07-23 15:25:57 -04:00
2019-08-01 13:28:12 -04:00
// First try to find a local file that exists
FileReference ProjectFile = ProjectUtils . FindProjectFileFromName ( ProjectName ) ;
if ( ProjectFile ! = null )
2019-07-23 15:25:57 -04:00
{
2019-08-01 13:28:12 -04:00
// get the path in P4
P4WhereRecord Record = P4 . Where ( ProjectFile . FullName , AllowSpew : false ) . FirstOrDefault ( x = > x . DepotFile ! = null & & ! x . bUnmap ) ;
P4ProjectPath = Record . ClientFile ;
2019-07-23 15:25:57 -04:00
}
2019-08-01 13:28:12 -04:00
else
2019-07-23 15:25:57 -04:00
{
2019-08-01 13:28:12 -04:00
// Couldn't be find locally, so try to find where it is in P4 by checking workspace root and uproject dirs
// ok, check P4...
string RelativePath = ProjectName ;
2019-07-23 15:25:57 -04:00
2019-08-01 13:28:12 -04:00
// Change EngineTest into EngineTest.uproject
if ( Path . GetExtension ( RelativePath ) . Length = = 0 )
2019-07-23 15:25:57 -04:00
{
2019-08-01 13:28:12 -04:00
RelativePath = Path . ChangeExtension ( RelativePath , "uproject" ) ;
2019-07-23 15:25:57 -04:00
}
2019-08-01 13:28:12 -04:00
// Change EngineTest.uproject into EngineTest/EngineTest.uproject
if ( ! RelativePath . Contains ( Path . DirectorySeparatorChar ) & & ! RelativePath . Contains ( Path . AltDirectorySeparatorChar ) )
{
RelativePath = CommandUtils . CombinePaths ( PathSeparator . Slash , Path . GetFileNameWithoutExtension ( RelativePath ) , RelativePath ) ;
}
// the calls to P4.FileExists will outout text regardless of spew setting so tell
// people why the are going to see these...
Log . TraceInformation ( "{0} not on disk. Searching P4 for {1}" , ProjectName , RelativePath ) ;
List < string > SearchPaths = new List < string > ( ) ;
// Search workspace root
SearchPaths . Add ( "" ) ;
// now check uproject dirs
string ProjectDirsFile = Directory . EnumerateFiles ( CommandUtils . CombinePaths ( CmdEnv . LocalRoot ) , "*.uprojectdirs" ) . FirstOrDefault ( ) ;
if ( ProjectDirsFile ! = null )
{
// read the project dirs file, removing anything invalid and the cur-path entry
// we add ourselves
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 ) ;
}
}
}
// now check P4
foreach ( string SearchPath in SearchPaths )
{
string P4Path = CommandUtils . CombinePaths ( PathSeparator . Slash , P4Env . ClientRoot , SearchPath , RelativePath ) ;
if ( P4 . FileExistsInDepot ( P4Path ) )
{
P4WhereRecord Record = P4 . Where ( P4Path , AllowSpew : false ) . FirstOrDefault ( x = > x . DepotFile ! = null & & ! x . bUnmap ) ;
P4ProjectPath = Record . DepotFile ;
Log . TraceInformation ( "Found project at {0}" , P4ProjectPath ) ;
break ;
}
}
if ( string . IsNullOrEmpty ( P4ProjectPath ) )
{
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" , ProjectName ) ;
}
2019-07-23 15:25:57 -04:00
}
}
// Build the list of paths that need syncing
List < string > SyncPaths = new List < string > ( ) ;
2019-08-01 13:28:12 -04:00
// See if the engine is in P4 too by checking the p4 location of a local file
string LocalEngineFile = CommandUtils . CombinePaths ( CmdEnv . LocalRoot , "Engine" , "Source" , "UE4Editor.target.cs" ) ;
P4WhereRecord EngineRecord = P4 . Where ( LocalEngineFile , AllowSpew : false ) . FirstOrDefault ( x = > x . DepotFile ! = null & & ! x . bUnmap ) ;
string P4EngineLocation = null ;
// add engine files if they exist
if ( ! ProjectOnly & & P4 . FileExistsInDepot ( EngineRecord . DepotFile ) )
2019-07-23 15:25:57 -04:00
{
2019-08-01 13:28:12 -04:00
P4EngineLocation = EngineRecord . DepotFile . Replace ( "Engine/Source/UE4Editor.target.cs" , "" ) ;
SyncPaths . Add ( CommandUtils . CombinePaths ( PathSeparator . Slash , P4EngineLocation + "*" ) ) ;
SyncPaths . Add ( CommandUtils . CombinePaths ( PathSeparator . Slash , P4EngineLocation , "Engine" , "..." ) ) ;
2019-07-23 15:25:57 -04:00
}
2019-08-01 13:28:12 -04:00
// add project file if any
if ( ! string . IsNullOrEmpty ( P4ProjectPath ) )
2019-07-23 15:25:57 -04:00
{
2019-08-01 13:28:12 -04:00
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-01 13:28:12 -04:00
if ( ! PreviewOnly & & ! string . IsNullOrEmpty ( P4EngineLocation ) )
2019-07-23 15:25:57 -04:00
{
2019-08-01 13:28:12 -04:00
IEnumerable < string > ForceSyncList = ForceSyncFiles . Select ( F = > CommandUtils . CombinePaths ( PathSeparator . Slash , P4EngineLocation , F ) ) ;
2019-07-23 15:25:57 -04:00
foreach ( var F in ForceSyncList )
{
LogInformation ( "Force-updating {0}" , F ) ;
string SyncCommand = string . Format ( "-f {0}@{1}" , F , CL ) ;
// sync with retries
2019-08-01 13:28:12 -04:00
P4 . Sync ( SyncCommand , true , false , Retries , MaxWait ) ;
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-01 13:28:12 -04:00
P4 . Sync ( SyncCommand , true , false , Retries , MaxWait ) ;
2019-07-23 15:25:57 -04:00
}
else
{
LogInformation ( "sync {0}" , SyncCommand ) ;
}
}
ExitCode ExitStatus = ExitCode . Success ;
if ( ! PreviewOnly )
{
2019-08-01 13:28:12 -04:00
// Get a reference to the project file
FileReference ProjectFile = ProjectUtils . FindProjectFileFromName ( ProjectName ) ;
if ( GenerateProject )
{
// generate project files.
if ( BuildHostPlatform . Current . Platform = = UnrealTargetPlatform . Win64 | |
BuildHostPlatform . Current . Platform = = UnrealTargetPlatform . Win32 )
{
CommandUtils . Run ( "GenerateProjectFiles.bat" , ProjectFile . FullName ) ;
}
else
{
CommandUtils . Run ( "GenerateProjectFiles.sh" , ProjectFile . FullName ) ;
}
}
2019-07-23 15:25:57 -04:00
UE4Build Build = new UE4Build ( this ) ;
2019-08-01 13:28:12 -04:00
if ( ! Unversioned )
{
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
{
BuildEditor BuildCmd = new BuildEditor ( ) ;
2019-08-06 16:06:27 -04:00
BuildCmd . Clean = ParseParam ( "clean" ) ;
2019-08-01 13:28:12 -04:00
BuildCmd . ProjectName = ProjectName ;
2019-07-23 15:25:57 -04:00
ExitStatus = BuildCmd . Execute ( ) ;
}
2019-08-01 13:28:12 -04:00
if ( OpenProject & & ExitStatus = = ExitCode . Success )
2019-07-23 15:25:57 -04:00
{
OpenEditor OpenCmd = new OpenEditor ( ) ;
2019-08-01 13:28:12 -04:00
OpenCmd . ProjectName = ProjectName ;
2019-07-23 15:25:57 -04:00
ExitStatus = OpenCmd . Execute ( ) ;
}
}
return ExitCode . Success ;
}
}