//reference * will cause the dll created from this script to inherit all references from the parent app using OozyBuild; /// reference * using Microsoft.Win32; using P4API; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Windows.Controls; using System.Xml.Serialization; namespace sga_merge_prebuild { /****************************************************************************************************************** ** ******************************************************************************************************************/ public static class ProjectExtension { public static SgaPrebuildSettings Settings( this OozyBuild.Project project ) { return project.customSettings as SgaPrebuildSettings; } } /****************************************************************************************************************** ** ******************************************************************************************************************/ public class SgaPrebuildSettings : OozyBuild.CustomSettings { public SgaPrebuildSettings() { //Defaults. These values are persisted, and so will only be set as default on new projects, or if settings are wiped visualStudioVersion = "2019"; buildBreakers = new SerializableDictionary>(); packageDir = ""; packageLimit = 5; transferDir = @""; logDir = @"tools_ng\script\util\BuildDept\GTA5_GEN9_BuildTool_V2\BuildTool\CI_Logs"; lastCIChangelist = -1; obscureSlackMessages = true; lastResult = new CIResult(); lastNightbuildResult = new CIResult(); branchStatus = new SerializableDictionary(); } public void Setup( Project inProject ) { project = inProject; if( Oozy.continuous ) { sendEmail = false; } if( string.IsNullOrEmpty( Project.IncredibuildExe ) || !File.Exists( Project.IncredibuildExe ) ) { incredibuild_IsEnabled = false; } else { incredibuild_IsEnabled = true; } } public void EnableButton( Button button, bool enable ) { if( button == null ) { return; } if( System.Windows.Application.Current != null ) { System.Windows.Application.Current.Dispatcher.BeginInvoke( (Action)(() => { button.IsEnabled = enable; }) ); } } /****************************************************************************************************************** ** SPECIAL VARIABLES which are used internally if they exist ******************************************************************************************************************/ public bool playBoredMessage { get; set; } //Don't make public, this should be un-editable. If undefined, the newest version found will be used. string visualStudioVersion { get; set; } [PropertyToolTip( "Nightbuild" )] public bool nightBuild { get; set; } [PropertyToolTip( "Log dir" )] public string logDir { get; set; } [PropertyToolTip("Upload symbols")] public bool uploadSymbols { get; set; } [PropertyToolTip( "Create Package" )] public bool createPackage { get; set; } [PropertyToolTip( "Package dir" )] public string packageDir { get; set; } [PropertyToolTip( "Transfer Package" )] public bool transferPackage { get; set; } [PropertyToolTip( "Transfer dir" )] public string transferDir { get; set; } [PropertyToolTip( "Keep this many packages in Package Dir. Last nightbuild is always kept" )] [XmlIgnore] public int packageLimit { get; set; } [ContinuousMode] [PropertyToolTip( "Requires slack.ini containing URL=" )] public bool postSlackMessages { get; set; } [ContinuousMode] [PropertyToolTip( "Don't put identifiable information in the slack messages" )] public bool obscureSlackMessages { get; set; } [ContinuousMode] [PropertyToolTip( "Send email on CI or package" )] public bool sendEmail { get; set; } [ContinuousMode] [PropertyToolTip( "Submit scc outputs defined in NOM to p4" )] public bool submitP4 { get; set; } /****************************************************************************************************************** ** END OF SPECIAL VARIABLES ******************************************************************************************************************/ [ContinuousMode] public bool createLabels { get; set; } public int lastCIChangelist { get; set; } public SerializableDictionary> buildBreakers { get; set; } public int filesSyncedToday { get; set; } public SerializableDictionary branchStatus { get; set; } public SerializableDictionary lastBuildStatus; public bool incredibuild { get; set; } bool incredibuild_IsEnabled { get; set; } public CIResult lastResult { get; set; } public CIResult lastNightbuildResult { get; set; } [XmlIgnore] public Project project; [XmlIgnore] public BuildExtension extension; } public class FileToMerge { public string source; public string dest; } /****************************************************************************************************************** ** ******************************************************************************************************************/ public class BuildExtension : GTA5Extension { string packageUser; bool buildOnlyIfSynced; //Dictionary of branches and whether changelist/bugstar information needs to be stored, along with p4 paths relevant to building protected override Dictionary branchPaths { get; set; } /****************************************************************************************************************** ** ******************************************************************************************************************/ public static BuildExtension Init( Project inProject ) { return new BuildExtension( inProject ); } /****************************************************************************************************************** ** ******************************************************************************************************************/ public BuildExtension( Project inProject ) { project = inProject; SgaPrebuildSettings settings = new SgaPrebuildSettings(); settings.Setup( inProject ); settings.extension = this; branchPaths = new Dictionary() { { "dev_gen9_sga_merge", new BranchSyncInfo() { storeChanges = true, syncPaths = new List() { @"gta5\titleupdate\dev_gen9_sga_merge\...", @"gta5\src\dev_gen9_sga_merge\...", @"gta5\script\dev_gen9_sga_merge\...", @"gta5\assets_gen9_sga\GameText\dev_gen9_sga_merge\...", @"gta5\assets_gen9_sga\metadata\...", @"gta5\assets_gen9_sga\titleupdate\dev_gen9_sga_merge\...", @"gta5\assets_gen9_sga\maps\ParentTxds.xml", @"gta5\build\disk_images\...", @"gta5_dlc\mpPacks\mpSum2\build\dev_gen9_sga\...", @"gta5_dlc\mpPacks\mpSum2\assets_gen9_disc\gametext\...", @"gta5_dlc\patchPacks\patchDay27NG\build\dev_gen9_sga\...", @"gta5\tools_ng\...", } } }, }; //Setting customSettings will serialize in persisted data project.customSettings = settings; //ensure any new or un-persisted branch infos are setup to save having null checks all over the place if( settings.branchStatus == null ) { settings.branchStatus = new SerializableDictionary(); } foreach( var pair in branchPaths ) { if( !settings.branchStatus.ContainsKey( pair.Key ) ) { settings.branchStatus[pair.Key] = new BranchStatus(); } } project.OnContinuous += OnContinuous; project.OnNightbuild += OnNightbuild; project.AddLog( "sga_merge_prebuild loaded" ); } object PackageBuild( QueueEntry queueEntry ) { SgaPrebuildSettings settings = project.Settings(); DateTime start = DateTime.Now; project.builder.buildStartingTime = start; string logPath = project.builder.GetBuilderLogPath( start ); Program.PushLogPath( logPath ); package = true; packageUser = queueEntry.testingUser; string msg = ""; if( !string.IsNullOrEmpty( packageUser ) ) { msg += DateTime.Now.ToString( "dd/MM/yyyy HH:mm" ) + ": PackageBuild started for " + packageUser + "\n"; } else { msg += DateTime.Now.ToString( "dd/MM/yyyy HH:mm" ) + ": PackageBuild started\n"; } string silentStr = queueEntry.values["silent"]; bool silent = string.Compare( silentStr, "true", true ) == 0; bool oldPost = settings.postSlackMessages; bool oldSubmit = settings.submitP4; bool oldCreateLabel = settings.createLabels; bool oldSendEmail = settings.sendEmail; if( silent ) { settings.postSlackMessages = false; settings.submitP4 = false; settings.createLabels = false; settings.sendEmail = false; } BuildParams buildParams = new BuildParams(); buildParams.rebuild = false; buildParams.flags = BuildTypeFlags.Packaging; msg += "Options:\n"; foreach( var pair in queueEntry.values ) { if( string.IsNullOrEmpty( pair.Value ) ) { msg += pair.Key + "\n"; } else { msg += pair.Key + " = " + pair.Value + "\n"; } buildParams[pair.Key] = pair.Value; } if( buildParams.ContainsKey( "installer" ) ) { buildParams["installer"] = "-platformpackage"; } if( buildParams.ContainsKey( "skipinitial" ) ) { buildParams["skipinitial"] = "-skipinitpackage"; } project.StartBuildThread( msg ); project.AddLog( msg ); VariableParser.GetValueDelegate getEnv = ( string var, List values ) => { string val; if( buildParams.TryGetValue( var, out val ) ) { values.Add( val ); } }; VariableParser.GetValue += getEnv; CIResult result = null; try { int retry = 10; while( result == null && retry > 0 ) { result = DoBuild( project.builder, buildParams ); if( result == null ) { project.AddError( "PackageBuild failed with something more than likely p4 related, waiting to try again" ); Thread.Sleep( 10 * 1000 ); } } } catch( Exception ex ) { project.AddError( "PackageBuild exception" ); project.PostBuildError( App.LogException( ex ) ); } VariableParser.GetValue -= getEnv; if( silent ) { settings.postSlackMessages = oldPost; settings.submitP4 = oldSubmit; settings.createLabels = oldCreateLabel; settings.sendEmail = oldSendEmail; } packageUser = ""; package = false; bool ret = result != null && result.ranNormally; project.PostBuildMessage( "Package Complete." ); if( ret ) { project.StopBuildThreadSuccess(); } else { project.StopBuildThreadError(); } Program.PopLogPath(); return ret; } int Execute( out string outLog, string dir, string command, string args ) { project.AddLog( "[" + dir + "] " + command + " " + args ); int ret = -1; try { BuildCommand symbol = new BuildCommand( dir, command, args ); Task th = project.builder.Execute( symbol ); th.Wait(); ret = th.Result; BuildLog log = symbol.log; StringBuilder buildLog = new StringBuilder( 4096 ); foreach( var l in log.Log() ) { switch( l.level ) { case OutputLevel.StdOut: project.AddLog( l.info ); break; case OutputLevel.StdErr: project.AddError( l.info ); break; case OutputLevel.Info: case OutputLevel.Warning: project.AddWarning( l.info ); break; } Program.Log( l.info ); buildLog.Append( l.info + "\n" ); } outLog = buildLog.ToString(); } catch( Exception ex ) { ret = -1; outLog = ex.Message; } return ret; } static string symbolServer = @"\\rsgdndnas1.rockstar.t2.corp\rsgdnd_symbolserver$\"; int UploadProsperoSymbols( string projectName, int changelist, string branch, out string log ) { int ret = Execute( out log, project.builder.workingDirectory, Path.Combine( Environment.ExpandEnvironmentVariables( "%SCE_PROSPERO_SDK_DIR%" ), @"host_tools\bin\prospero-symupload.exe" ), @"add /f " + Path.Combine( project.builder.workingDirectory, @"titleupdate\" + branch + @"\" ) + @"*.elf /s " + symbolServer + " /compress /tag " + changelist + " /o" ); if( ret == 0 ) { Program.Log( "Prospero symbols uploaded" ); } else { Program.Log( "Prospero symbols failed" ); } return ret; } int UploadScarlettSymbols( string projectName, int changelist, string branch, out string log ) { int ret = Execute( out log, project.builder.workingDirectory, Path.Combine( Environment.ExpandEnvironmentVariables( "%ProgramFiles(x86)%" ), @"Windows Kits\10\Debuggers\x64\symstore.exe" ), @"add /f " + Path.Combine( project.builder.workingDirectory, @"titleupdate\" + branch + @"\" ) + @"game_scarlett*.exe /s " + symbolServer + " /compress /t " + projectName + " /v " + changelist + " /o" ); if( ret == 0 ) { ret = Execute( out log, project.builder.workingDirectory, Path.Combine( Environment.ExpandEnvironmentVariables( "%ProgramFiles(x86)%" ), @"Windows Kits\10\Debuggers\x64\symstore.exe" ), @"add /f " + Path.Combine( project.builder.workingDirectory, @"titleupdate\" + branch + @"\" ) + @"game_scarlett*.pdb /s " + symbolServer + " /compress /t " + projectName + " /v " + changelist + " /o" ); } if( ret == 0 ) { Program.Log( "Scarlett symbols uploaded" ); } else { Program.Log( "Scarlett symbols failed" ); } return ret; } int UploadWin64Symbols( string projectName, int changelist, string branch, out string log ) { int ret = Execute( out log, project.builder.workingDirectory, Path.Combine( Environment.ExpandEnvironmentVariables( "%ProgramFiles(x86)%" ), @"Windows Kits\10\Debuggers\x64\symstore.exe" ), @"add /f " + Path.Combine( project.builder.workingDirectory, @"titleupdate\" + branch + @"\" ) + @"game_win64*.exe /s " + symbolServer + " /compress /t " + projectName + " /v " + changelist + " /o" ); if( ret == 0 ) { ret = Execute( out log, project.builder.workingDirectory, Path.Combine( Environment.ExpandEnvironmentVariables( "%ProgramFiles(x86)%" ), @"Windows Kits\10\Debuggers\x64\symstore.exe" ), @"add /f " + Path.Combine( project.builder.workingDirectory, @"titleupdate\" + branch + @"\" ) + @"game_win64*.pdb /s " + symbolServer + " /compress /t " + projectName + " /v " + changelist + " /o" ); } if( ret == 0 ) { Program.Log( "Scarlett symbols uploaded" ); } else { Program.Log( "Scarlett symbols failed" ); } return ret; } //bool UpdateVersion( int cl, string file, string branch, P4 p4 = null ) //{ // string versionFile = Path.Combine( project.builder.workingDirectory, file ); // if( !File.Exists( versionFile ) ) // { // project.AddError( "Missing " + versionFile ); // return false; // } // if( p4 == null ) // { // p4 = new P4( project.p4IniFile ); // if( !p4.Connect() ) // { // project.AddError( "Failed to connect to p4" ); // return false; // } // } // project.AddLog( "Sync " + versionFile ); // p4.Sync( versionFile, autoResolve: true, clobberWritable: true ); // project.AddLog( "Checkout " + versionFile ); // p4.OpenForEdit( versionFile ); // string[] lines = File.ReadAllLines( versionFile ); // Regex version = new Regex( @"^\s*\[VERSION_NUMBER\]\s*$" ); // bool found = false; // for( int i = 0; i < lines.Length; ++i ) // { // if( version.Match( lines[i] ).Success ) // { // ++i; // lines[i] = cl.ToString() + "-" + branch; // project.AddLog( "Set version to " + lines[i] ); // found = true; // break; // } // } // if( found ) // { // project.AddLog( "Writing " + versionFile ); // File.WriteAllLines( versionFile, lines ); // } // else // { // project.AddError( "Failed to find [VERSION_NUMBER] in" ); // for( int i = 0; i < lines.Length; ++i ) // { // project.AddError( "\t " + lines[i] ); // } // } // return found; //} //int UpdateVersion( string file, string branch ) //{ // int ret = -1; // SgaPrebuildSettings settings = project.Settings(); // int cl = currentChangelist; // cl = cl == 0 ? settings.lastChangelist : cl; // if( cl != 0 ) // { // ret = UpdateVersion( cl, file, branch ) ? 0 : 1; // } // return ret; //} /****************************************************************************************************************** ** TODO put this version into the api ******************************************************************************************************************/ public IList GetChangelistsAtRevision( P4 p4, IList specs ) { Perforce.P4.ChangesCmdFlags flags = Perforce.P4.ChangesCmdFlags.None; Perforce.P4.ChangesCmdOptions options = new Perforce.P4.ChangesCmdOptions( flags, "", 1, Perforce.P4.ChangeListStatus.Submitted, "", 0 ); IList changes = p4.rep.GetChangelists( options, specs.ToArray() ); return changes; } /****************************************************************************************************************** ** Called from .nom. Syncs files relevant to the branch ******************************************************************************************************************/ int SyncBranch( string branchName, string outputFile = "" ) { P4 p4 = SetupP4(); if (p4 == null) { return 1; } SgaPrebuildSettings settings = project.Settings(); IList files = SyncBranches( p4, branchName, autoResolve: false ); if( files == null ) { return 1; } BranchStatus branchStatus = settings.branchStatus[branchName]; if( files.Count > 0 ) { List buildPaths = new List(); AddBuildInfo( GetBuildInfoFromFiles( p4, buildPaths, files, branchStatus.lastChangelist, branchStatus.syncedChangelist ) ); } //Don't need to do anymore if no outputFile if( string.IsNullOrEmpty( outputFile ) ) { return 0; } List paths = GetBranchPaths( branchName ); IList specs = new List( paths.Count ); foreach( string path in paths ) { specs.Add( new Perforce.P4.FileSpec( new Perforce.P4.LocalPath( Path.Combine( p4root, path) ) ) ); } specs = p4.Where( specs ); specs.SetVersion( new Perforce.P4.HaveRevision() ); IList changes = GetChangelistsAtRevision( p4, specs ); StringBuilder sb = new StringBuilder(); foreach( Perforce.P4.Changelist change in changes ) { sb.Append( "Change " + change.Id + " on " + change.ModifiedDate.ToString( "yyyy/mm/dd" ) + " by " + change.OwnerName + "'" + change.Description.WordWrap( 18 ).Replace('\n', ' ') + "'" + Environment.NewLine); } try { string dir = Path.GetDirectoryName( outputFile ); Util.MakeDirectory( dir ); File.WriteAllText( outputFile, sb.ToString() ); } catch( Exception ex ) { App.LogException( ex ); return ex.HResult; } return 0; } /****************************************************************************************************************** ** ******************************************************************************************************************/ void QuickOnOutput( object sender, string output, OutputLevel level ) { switch( level ) { case OutputLevel.Info: case OutputLevel.StdOut: project.AddLog( output ); break; case OutputLevel.Warning: project.AddWarning( output ); break; case OutputLevel.StdErr: project.AddError( output ); break; } } /****************************************************************************************************************** ** ******************************************************************************************************************/ public void Activate() { if ( Oozy.continuous ) { SgaPrebuildSettings settings = project.Settings(); project.SetContinuous( new TimeSpan( 0, 0, 0 ) ); int hour = 00; int min = 00; DateTime now = DateTime.Now; project.SetNightbuild( new DateTime( now.Year, now.Month, now.Day, hour, min, 0 ) ); project.builder.builderLogPath = Path.Combine( project.enginePath, settings.logDir ); project.builder.buildQueue.Register( "PackageBuild", BuildTypeFlags.Packaging, PackageBuild ); } } /****************************************************************************************************************** ** ******************************************************************************************************************/ public void DeActivate() { if( Oozy.continuous ) { project.builder.buildQueue.Unregister( "PackageBuild" ); project.SetContinuous( null ); project.SetNightbuild( null ); } } /****************************************************************************************************************** ** ******************************************************************************************************************/ private void IPStateChanged( IPInfo info, IPInfo.State oldState, IPInfo.State newState ) { string user = project.GetSlackUser( info.user ); if( oldState == IPInfo.State.Dead && newState == IPInfo.State.Alive ) { project.PostSlackMessage( new List() { user }, "Your PC lives." ); } else if( oldState == IPInfo.State.Alive && newState == IPInfo.State.Dead ) { project.PostSlackMessage( new List() { user }, "Your PC has gone away." ); } if( !info.initiallyReported ) { info.initiallyReported = true; if( oldState == IPInfo.State.Unknown && newState == IPInfo.State.Alive ) { project.PostSlackMessage( new List() { user }, "Your PC is currently alive." ); } else if( oldState == IPInfo.State.Unknown && newState == IPInfo.State.Dead ) { project.PostSlackMessage( new List() { user }, "Your PC is currently dead." ); } } } /****************************************************************************************************************** ** ******************************************************************************************************************/ void ReplaceCommand( Builder builder, string xmlDefinedName, string displayName, string command ) { BuildEntry build = builder.GetBuildByName( xmlDefinedName ); if( build != null ) { build.displayName = displayName; BuildBatchFile batch = build as BuildBatchFile; if( batch != null ) { foreach( var subBuild in batch.builds ) { if( subBuild is BuildCommand ) { build.displayName = displayName; (subBuild as BuildCommand).command = command; return; } } Console.Error.WriteLine( xmlDefinedName + " couldn't find a BuildCommand, you been nom fiddling?" ); return; } Console.Error.WriteLine( xmlDefinedName + ": isn't a BuildBatchFile, you been nom fiddling?" ); return; } Console.Error.WriteLine( "Couldn't find \"" + xmlDefinedName + "\" in build list" ); } /****************************************************************************************************************** ** This is called when any build related options have changed. "building" is set when GenerateBuild is called for doing the actual build ******************************************************************************************************************/ public bool GeneratePackageBuild( Builder builder, BuildParams buildParams ) { bool ret = true; return ret; } /****************************************************************************************************************** ** ******************************************************************************************************************/ bool OnNightbuild( Builder builder ) { bool reboot = SystemUpdates.PendingReboot(); if( reboot ) { project.PostBuildError( "I have pending updates and stuff, looks like I need to be rebooted. If I do it myself I won't auto-restart." ); } SgaPrebuildSettings settings = project.Settings(); DateTime now = DateTime.Now; string msg = now.ToString( "dd/MM/yyyy HH:mm" ) + ": Nightbuild started"; project.StartBuildThread( msg ); project.AddLog( msg ); //Friday will have had changes probably, and it's "nightbuild" will happen on saturday. Hence check monday here to cater for no changes on sunday buildOnlyIfSynced = (now.DayOfWeek == DayOfWeek.Saturday || now.DayOfWeek == DayOfWeek.Sunday || now.DayOfWeek == DayOfWeek.Monday); nightbuild = true; CIResult result = null; BuildParams buildParams = new BuildParams(); buildParams.rebuild = true; buildParams.flags = BuildTypeFlags.Nightbuild; try { int retry = 10; while( result == null && retry > 0 ) { result = DoBuild( builder, buildParams ); if( result == null ) { project.AddError( "Nightbuild failed with something more than likely p4 related, waiting to try again" ); Thread.Sleep( 10 * 1000 ); } } } catch( Exception ex ) { project.AddError( "OnNightbuild exception" ); project.PostBuildError( App.LogException( ex ) ); } settings.filesSyncedToday = 0; buildOnlyIfSynced = false; nightbuild = false; project.PostBuildMessage( "Nightbuild Complete." ); if( result != null && result.ranNormally ) { project.StopBuildThreadSuccess(); } else { project.StopBuildThreadError(); } if( result == null ) { return true; //want that log } return result.ranNormally; } static public int SetupIB( string[] args ) { try { using( RegistryKey keyBase = RegistryKey.OpenBaseKey( RegistryHive.LocalMachine, RegistryView.Registry32 ) ) { if( null == keyBase ) { return 1; } using( RegistryKey builderKey = keyBase.CreateSubKey( @"SOFTWARE\WOW6432Node\Xoreax\IncrediBuild\Builder" ) ) { builderKey.SetValue( "UseMSBuild", "1", RegistryValueKind.String ); } using( RegistryKey buildServiceKey = keyBase.CreateSubKey( @"SOFTWARE\WOW6432Node\Xoreax\IncrediBuild\BuildService" ) ) { buildServiceKey.SetValue( "CoordHost", "Rsgibc", RegistryValueKind.String ); buildServiceKey.SetValue( "CoordPort", "31104", RegistryValueKind.String ); buildServiceKey.SetValue( "BackupCoordHost", "10.39.18.36", RegistryValueKind.String ); buildServiceKey.SetValue( "BackupCoordPort", "31104", RegistryValueKind.String ); buildServiceKey.SetValue( "BackupCoordDisplay", "Rsgldnibc4", RegistryValueKind.String ); } } string exe = @"N:\RSGEDI\Outsource\Ruffian\Ruffian Tools\Utils\IBSetupConsole.exe"; Builder builder = new Builder(); builder.test = false; BuildEntry entry = new BuildCommand( Path.GetDirectoryName( exe ), exe, "/Install /Components=Agent /Coordinator=Rsgibc" ); Task build = builder.Execute( entry ); build.Wait(); return entry.errorCode; } catch( Exception ex ) { App.LogException( ex ); return ex.HResult; } } string InstallIB() { if( 0 != Program.RunLocalFunction( this, SetupIB, new string[1] ) ) { return "Failed to FixIB"; } return ""; } /****************************************************************************************************************** ** ******************************************************************************************************************/ bool OnContinuous( Builder builder ) { CIResult result = null; SgaPrebuildSettings settings = project.Settings(); try { project.AddLog( DateTime.Now.ToString( "dd/MM/yyyy HH:mm" ) + ": Continuous started" ); BuildParams buildParams = new BuildParams(); buildParams.flags = BuildTypeFlags.Continuous; result = DoBuild( builder, buildParams ); } catch( Exception ex ) { project.AddError( "OnContinuous exception\n" + App.LogException( ex ) ); } if( result == null ) { return true; //want that log } return result.ranNormally; } /****************************************************************************************************************** ** ******************************************************************************************************************/ CIResult DoBuild( Builder builder, BuildParams buildParams ) { SgaPrebuildSettings settings = project.Settings(); P4 p4 = SetupP4(); if( p4 == null ) { return null; } bool syncedFiles = false; int currentChangelist = -1; foreach( var pair in settings.branchStatus ) { project.AddLog( "Last changelist for " + pair.Key + " was " + pair.Value.lastChangelist ); } bool skipForWeekend = false; IList syncFiles = new List(); IList latest = p4.GetChangelists( 1, Perforce.P4.ChangeListStatus.Submitted ); buildInfo = null; buildPaths = new List(); if( latest.Count > 0 ) { currentChangelist = latest[0].Id; bool preview = project.builder.test; IList files = SyncBranches( p4, currentChangelist: currentChangelist ); if( files == null ) { //The api will never return null unless p4 has failed somewhere return null; } else { if( nightbuild || package ) { project.PostBuildMessage( "Synced " + files.Count + " files.\nNumber of files synced today is " + settings.filesSyncedToday ); } if( !preview ) { p4.RevertUnchangedFiles(); } settings.filesSyncedToday += files.Count; if( !(nightbuild || package) && (files == null || files.Count == 0) ) { project.AddError( "P4: No files synced." ); settings.lastCIChangelist = currentChangelist; return new CIResult(); } if( (nightbuild || package) && buildOnlyIfSynced && settings.filesSyncedToday == 0 ) { project.AddWarning( "It's nightbuild, it's the weekend and nothing has changed today." ); project.PostBuildMessage( "It's nightbuild, it's the weekend and nothing has changed today." ); settings.lastCIChangelist = currentChangelist; skipForWeekend = true; } } if( files.Count > 0 ) { syncedFiles = true; buildInfo = GetBuildInfoFromFiles( p4, buildPaths, files, settings.lastCIChangelist, currentChangelist ); if( buildInfo == null ) { return null; } } } //pump it at the project, to handle changes CIResult result = null; bool createPackage = settings.createPackage; if( skipForWeekend ) { result = settings.lastNightbuildResult; } else { bool retry = false; project.buildEnvironment["RSG_AUTOMATION_CODEBUILDER_SRC_CL"] = currentChangelist.ToString(); bool oldCreatePackage = settings.createPackage; //just make nightbuild act as CI if( (nightbuild && !package) && !settings.nightBuild ) { settings.createPackage = false; } //do a time check, disallow nightbuild but allow hand requested packages if( !package && nightbuild ) { DateTime StartHols = new DateTime( 2021, 12, 11, 12, 0, 0 ); DateTime EndHols = new DateTime( 2022, 1, 4, 12, 0, 0 ); DateTime now = DateTime.Now; if( now > StartHols && now < EndHols ) { project.AddLog( "Skipping package, it's Christmas" ); settings.createPackage = false; } } if( syncedFiles ) { AddBuildInfo( buildInfo ); } else { buildInfo = new List(); } if( syncedFiles && buildParams.flags.HasFlag( BuildTypeFlags.Packaging ) ) { project.PostBuildMessage( "Files were synced, doing a CI pass." ); BuildTypeFlags oldFlags = buildParams.flags; buildParams.flags &= ~BuildTypeFlags.Packaging; buildParams.flags |= BuildTypeFlags.Continuous; result = project.HandleFileChanges( buildParams, retry, p4, currentChangelist, buildInfo, buildPaths ); buildParams.flags = oldFlags; if( result.ranNormally && project.projectStatus == ProjectStatus.Success ) { project.PostBuildMessage( "Doing package pass." ); result = project.HandleFileChanges( buildParams, retry, p4, currentChangelist, buildInfo, buildPaths ); } else { project.PostBuildError( "Skipping package pass, something went wrong." ); } } else { result = project.HandleFileChanges( buildParams, retry, p4, currentChangelist, buildInfo, buildPaths ); } settings.createPackage = oldCreatePackage; } if( nightbuild ) { settings.lastNightbuildResult = result; } else { settings.lastResult = result; } string msg = ""; List sendToUsers = new List(); Stopwatch symbolTime = null; string tmpLabel = "GTA5_GEN9_PREBUILD_TMP"; string QALabel = "GTA5_GEN9_PREBUILD"; if( result.ranNormally ) { if( nightbuild || package ) { List QA = new List() { "gen9qa", }; bool showReport = false; if( !skipForWeekend ) { project.PostBuildMessage( "Processing post build." ); if( project.projectStatus == ProjectStatus.Success ) { //clear out build info, this could cause unwanted growth otherwise foreach( var pair in branchPaths ) { //maybe clear out package info after 7 days? if( nightbuild ) { settings.branchStatus[pair.Key].sinceNightbuild.Clear(); } settings.branchStatus[pair.Key].current.Clear(); } bool uploadSymbols = !builder.test && settings.uploadSymbols; bool prosperoOk = false; bool scarlettOk = false; bool win64Ok = false; string prosperoOutput; string scarlettOutput; string win64Output; if( uploadSymbols ) { string branch = ""; if( !buildParams.TryGetValue( "packagename", out branch ) ) { if( !buildParams.TryGetValue( "branch", out branch ) ) { branch = "dev_gen9_sga_merge"; } } symbolTime = new Stopwatch(); symbolTime.Start(); project.PostBuildMessage( "Uploading prospero symbols for " + branch + "." ); prosperoOk = UploadProsperoSymbols( "GTAO", currentChangelist, branch, out prosperoOutput ) == 0 /*&& UploadProsperoSymbols("GTAO", currentChangelist, "dev_gen9_trailer", out prosperoOutput) == 0*/; project.PostBuildMessage( "Uploading scarlett symbols for " + branch + "." ); scarlettOk = UploadScarlettSymbols( "GTAO", currentChangelist, branch, out scarlettOutput ) == 0 /*&& UploadScarlettSymbols("GTAO", currentChangelist, "dev_gen9_trailer", out scarlettOutput) == 0*/; project.PostBuildMessage( "Uploading x64 symbols for " + branch + "." ); win64Ok = UploadWin64Symbols( "GTAO", currentChangelist, branch, out win64Output ) == 0 /*&& UploadWin64Symbols("GTAO", currentChangelist, "dev_gen9_trailer", out win64Output) == 0*/; symbolTime.Stop(); } if( createPackage ) { if( !builder.test ) { if( settings.createLabels ) { //Create/get temp label Perforce.P4.Label label = p4.GetLabel( tmpLabel ); label.Description = "Synced to @" + currentChangelist; //set the description p4.UpdateLabel( label ); //Add the range of files we synced to List specs = new List(); specs.AddRange( syncFiles ); //add files that have been submited due to the build if( result.filesCheckedIn.Count > 0 ) { specs.AddRange( result.filesCheckedIn ); } //Tag all the files in this label p4.Tag( label.Id, specs ); //Get rid of the previous label p4.DeleteLabel( QALabel ); p4.CopyLabel( QALabel, label.Id ); p4.DeleteLabel( label.Id ); } } sendToUsers = QA; showReport = true; } if( uploadSymbols ) { if( !prosperoOk ) { msg += "`Looks like the Prospero symbols didn't upload.`\n"; } if( !scarlettOk ) { msg += "`Looks like the Scarlett symbols didn't upload.`\n"; } if( !win64Ok ) { msg += "`Looks like the Win64 symbols didn't upload.`\n"; } } } else if( createPackage ) { showReport = true; } } else { if( DateTime.Now.DayOfWeek == DayOfWeek.Monday ) { sendToUsers = QA; msg += "Nothing was built as there was no need, previous package details follows:\n"; showReport = true; } } if( showReport ) { if( project.projectStatus == ProjectStatus.Success ) { string packages = string.Join( ", ", result.branchInfo.Select( a => a.Value.name ) ); if( package ) { msg += "Packages " + packages + " complete, sync to *@" + QALabel + "* which has been built to _" + result.builtToChangelist + "_.\n"; } else { msg += "Nightbuild complete, sync to *@" + QALabel + "* which has been built to _" + result.builtToChangelist + "_.\n"; } if( settings.createPackage ) { foreach( var pair in result.branchInfo ) { if( string.IsNullOrEmpty( pair.Value.packageDir ) ) { msg += "'For some reason it looks like no files were copied for " + pair.Key + "`\n"; } else { msg += "Packages for " + pair.Key + " can be found in ```" + SlackClient.MakeFileHyperLink( pair.Value.packageDir, pair.Value.packageDir ) + "```\n"; string prosperoDeployBat = Path.Combine( pair.Value.packageDir, "CopyAndDeployProspero.bat" ); msg += "Use " + SlackClient.MakeFileHyperLink( prosperoDeployBat, prosperoDeployBat ) + " to copy and deploy.\n"; string scarlettDeployBat = Path.Combine( pair.Value.packageDir, "CopyAndDeployScarlett.bat" ); msg += "Scarlett: " + SlackClient.MakeFileHyperLink( scarlettDeployBat, scarlettDeployBat ) + " \n"; if( pair.Key != result.branchInfo.Last().Key ) { msg += "-----------------------------"; } } } } } else { if( package ) { if( !string.IsNullOrEmpty( packageUser ) ) { sendToUsers = new List( 1 ) { packageUser }; msg += "`Sorry, package failed, no new build for you yet.`"; } } else if( nightbuild ) { sendToUsers = QA; msg += "`Sorry, nightbuild failed, no new build for you yet.`"; } } } } settings.lastCIChangelist = currentChangelist; settings.lastBuildStatus = project.buildStatus; } bool showTimings = !string.IsNullOrEmpty( msg ); if( nightbuild && SystemUpdates.PendingReboot() ) { msg += "`I have pending updates and stuff, looks like I need to be rebooted. If I do it myself I won't auto-restart.`\n"; } List response = null; if( showTimings ) { String addendum = "Build Took: " + result.buildDuration.OozyTime() + "\n"; if( symbolTime != null ) { addendum += "Upload Symbols Took: " + symbolTime.Elapsed.OozyTime() + "\n"; } addendum += "Copy Took: " + result.copyDuration.OozyTime() + "\n"; addendum += "Total Copied: " + ((float)(result.copySize) / 1024.0 / 1024.0 / 1024.0).ToString( "0.00" ) + " GB\n"; if( !sendToUsers.Empty() ) { response = project.NotifySlackUsers( sendToUsers, msg ); } else { response = project.PostSlackMessage( msg ); } if( response != null && !string.IsNullOrEmpty( addendum ) ) { project.PostSlackMessage( addendum, response ); } } if( nightbuild && settings.transferPackage ) { try { foreach( var pair in result.branchInfo ) { string dest = Path.GetFileName( pair.Value.packageDir ); dest = Path.Combine( settings.transferDir, pair.Key, dest ); project.PostSlackMessage( "Copying " + pair.Value.packageDir + " to " + dest, response ); Program.CopyFiles( pair.Value.packageDir, dest, throwOnFail: true ); project.PostSlackMessage( "Completed.", response ); } } catch( Exception ex ) { msg = App.LogException( ex ); project.PostSlackMessage( msg, response ); } } bool submitProjectFiles = !builder.test; if( submitProjectFiles && settings.submitP4 ) { RemoveProjGen( p4, true ); } project.buildEnvironment["RSG_AUTOMATION_CODEBUILDER_SRC_CL"] = ""; return result; } } }