//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; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Remoting.Messaging; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Xml.Serialization; using System.Xml.Linq; using System.Xml.XPath; //using clitest; /// reference E:\src\xbox\TestBed\clitest\bin\Release\clitest.exe using System.Reflection; using System.Linq.Expressions; namespace GTAO { /****************************************************************************************************************** ** ******************************************************************************************************************/ public static class ProjectExtension { public static Settings Settings( this OozyBuild.Project project ) { return project.customSettings as Settings; } } /****************************************************************************************************************** ** ******************************************************************************************************************/ public class Settings : OozyBuild.CustomSettings // : OozyBuild.UEBuildSettings { public Settings() { //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 = "X:\\Packages"; packageLimit = 5; transferDir = @"N:\Transfer\RSGDND\Resilio\gta5\gen9_packaged_builds"; logDir = @"N:\RSGDND\CIPackages\Logs"; lastCIChangelist = -1; obscureSlackMessages = true; lastResult = new CIResult(); lastNightbuildResult = new CIResult(); branchStatus = new SerializableDictionary(); PropertyChanged += ( s, e ) => { // if( e.PropertyName == "packageDir" ) // { // EnablePackageButton( !string.IsNullOrEmpty(packageDir) && !Oozy.packaging ); // } }; } public void Setup( Project inProject ) { project = inProject; if( Oozy.continuous ) { project.builder.buildQueue.Register( "RetryContinuous", BuildTypeFlags.Continuous, RetryContinuousQueueEntry ); retryButton = new Button(); retryButton.Content = "Retry"; retryButton.Click += ( s, args ) => { if( /*project.runningContinuous || project.builder.building*/ project.builder.buildQueue.Processing() || Oozy.pendingRestart /*|| Oozy.packaging*/ ) { return; } EnableRetryButton( false ); project.builder.buildQueue.Add( "RetryContinuous" ); // QueueEntry q = new QueueEntry( BuildTypeFlags.Continuous ); // q.action += (QueueEntry e) => // (new Thread( () => // { #if false project.AddLog( DateTime.Now.ToString( "dd/MM/yyyy HH:mm" ) + ": retry started" ); string ini = project.p4IniFile; if( File.Exists( ini ) ) { P4 p4 = new P4( ini ); project.AddLog( "Connecting to " + p4.Port ); if( !p4.Connect() ) { project.AddError( "P4: Failed to connect to perforce" ); EnableRetryButton( true ); } else { project.SetupSCCOutput( p4 ); CIResult result = project.HandleFileChanges( false, true, p4, lastChangelist, lastBuildInfo, lastPaths ); if( result.ranNormally ) { lastBuildStatus = project.buildStatus; bool failed = lastBuildStatus.Failed(); EnableRetryButton( failed ); } } } else { project.AddLog( "p4ini \"" + ini + "\" not found, aborting retry" ); } #endif // project.runningContinuous = false; // } )).Start(); // }; // project.builder.buildQueue.Add( q ); // }; EnableRetryButton( lastBuildStatus != null && lastBuildStatus.Count > 0 && lastBuildStatus.Failed() ); }; sendEmail = false; } if( string.IsNullOrEmpty( Project.IncredibuildExe ) || !File.Exists( Project.IncredibuildExe ) ) { incredibuild_IsEnabled = false; } else { incredibuild_IsEnabled = true; } } object RetryContinuousQueueEntry( QueueEntry queueEntry ) { project.AddLog( DateTime.Now.ToString( "dd/MM/yyyy HH:mm" ) + ": retry started" ); #if false string ini = project.p4IniFile; if( File.Exists( ini ) ) { P4 p4 = new P4( ini ); project.AddLog( "Connecting to " + p4.Port ); if( !p4.Connect() ) { project.AddError( "P4: Failed to connect to perforce" ); EnableRetryButton( true ); } else { project.SetupSCCOutput( p4 ); project.buildEnvironment["RSG_AUTOMATION_CODEBUILDER_SRC_CL"] = lastCIChangelist.ToString(); project.buildEnvironment["RunProjGen"] = "true"; BuildParams buildParams = new BuildParams(); buildParams.flags = BuildTypeFlags.Continuous; CIResult result = project.HandleFileChanges( buildParams, true, p4, lastCIChangelist, lastBuildInfo, lastPaths ); if( result.ranNormally ) { lastBuildStatus = project.buildStatus; bool failed = lastBuildStatus.Failed(); EnableRetryButton( failed ); } project.buildEnvironment["RSG_AUTOMATION_CODEBUILDER_SRC_CL"] = ""; return true; } } else { project.AddLog( "p4ini \"" + ini + "\" not found, aborting retry" ); } #endif return false; } // public async System.Threading.Tasks.Task PerformTestShelve(int cl, string user = "") public CIResult PerformTestShelve(int cl, string user = "", bool rebuild = true) { CIResult ret = null; // if( project.runningContinuous || project.builder.building || Oozy.pendingRestart || Oozy.packaging ) // { // return false; // } Settings settings = project.Settings(); // project.runningContinuous = true; // project.testingShelve = true; // project.testingUser = user; // project.testingShelveId = cl; project.AddLog( DateTime.Now.ToString( "dd/MM/yyyy HH:mm" ) + ": testing shelve " + cl ); // (new Thread( () => // { ret = extension.TestShelve(cl, rebuild); // project.testingShelveId = -1; // project.testingUser = string.Empty; // project.testingShelve = false; // project.runningContinuous = false; // } )).Start(); return ret; } 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; }) ); } } public void EnableRetryButton(bool enable) { EnableButton( retryButton, 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( "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; } // bool postSlackMessages_IsEnabled { get { return Oozy.continuous; } 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; } // bool sendEmail_IsEnabled { get { return Oozy.continuous; } set {} } [ContinuousMode] [PropertyToolTip( "Submit scc outputs defined in NOM to p4" )] public bool submitP4 { get; set; } // bool submitP4_IsEnabled { get { return Oozy.continuous; } set {} } /****************************************************************************************************************** ** END OF SPECIAL VARIABLES ******************************************************************************************************************/ [ContinuousMode] public bool tagDataLabel { get; set; } [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; } [XmlIgnore] public Button retryButton { get; set; } public List filesToMerge = new List(); public CIResult lastResult { get; set; } public CIResult lastNightbuildResult { get; set; } public bool pendingReboot { 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; iCal calendar = new iCal(); bool buildOnlyIfSynced; string notifiedIBFailed; //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 ) { // string resolveDir = @"X:\gta5\tools_ng\bin\gen9\RageScriptEditor\"; // AppDomain.CurrentDomain.AssemblyResolve += ( s, e ) => // { // string path = Path.Combine( resolveDir, new AssemblyName( e.Name ).Name + ".dll" ); // if( File.Exists( path ) ) // { // return Assembly.LoadFile( path ); // } // return null; // }; return new BuildExtension( inProject ); } /****************************************************************************************************************** ** ******************************************************************************************************************/ public BuildExtension( Project inProject ) { project = inProject; Settings settings = new Settings(); settings.Setup( inProject ); settings.extension = this; branchPaths = new Dictionary() { { "dev_ng", new BranchSyncInfo() { storeChanges = false, syncPaths = new List() { @"gta5\tools_ng\...", @"gta5\src\dev_ng\...", @"gta5\src\dev_ng\rage\...", @"gta5\build\disk_images\gen9\...", @"x:\3rdParty\...", } } }, { "dev_ng_live", new BranchSyncInfo() { storeChanges = false, syncPaths = new List() { @"gta5\tools_ng\...", @"gta5\src\dev_ng_live\...", @"gta5\src\dev_ng_live\rage\...", @"gta5\build\disk_images\gen9\...", @"x:\3rdParty\...", } } }, { "dev_gen9_sga", new BranchSyncInfo() { storeChanges = true, syncPaths = new List() { @"gta5\tools_ng\...", @"gta5\script\dev_gen9_sga\...", @"gta5\build\dev_gen9_sga\...", @"gta5\titleupdate\dev_gen9_sga\...", @"gta5\titleupdate\dev_gen9_sga\common\shaders\...", @"gta5\titleupdate\dev_gen9_sga\xbsx\audio\...", @"gta5\titleupdate\dev_gen9_sga\ps5\audio\...", @"gta5\src\dev_gen9_sga\...", @"gta5\src\dev_gen9_sga\rage\...", @"gta5\build\disk_images\gen9\...", @"x:\3rdParty\...", } } }, { "dev_gen9_unstable", new BranchSyncInfo() { storeChanges = true, syncPaths = new List() { @"gta5\tools_ng\...", @"gta5\script\dev_gen9_sga\...", @"gta5\build\dev_gen9_sga\...", @"gta5\titleupdate\dev_gen9_sga\...", @"gta5\titleupdate\dev_gen9_sga\common\shaders\...", @"gta5\titleupdate\dev_gen9_sga\xbsx\audio\...", @"gta5\titleupdate\dev_gen9_sga\ps5\audio\...", @"gta5\src\dev_gen9_sga_unstable\...", @"gta5\src\dev_gen9_sga_unstable\rage\...", @"gta5\build\disk_images\gen9\...", @"x:\3rdParty\...", } } }, { "dev_gen9_sga_live", new BranchSyncInfo() { storeChanges = true, syncPaths = new List() { @"gta5\src\dev_gen9_sga_live\...", @"gta5\src\dev_gen9_sga_live\rage\...", } } }, }; //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( "GTAO loaded" ); } object PackageBuild( QueueEntry queueEntry ) { Settings 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 oldTagData = settings.tagDataLabel; bool oldCreateLabel = settings.createLabels; bool oldSendEmail = settings.sendEmail; if( silent ) { settings.postSlackMessages = false; settings.submitP4 = false; settings.tagDataLabel = 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.tagDataLabel = oldTagData; 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; } object TestShelveQueueEntry( QueueEntry queueEntry ) { Settings settings = project.Settings(); string msg = "Testing shelve " + queueEntry.testingShelveId + " against " + settings.lastCIChangelist + " for " + queueEntry.testingUser; Logger.Log( msg ); project.AddLog( msg ); project.StartBuildThread( msg ); bool ret = true; OnEntryStartedHandler startedHandler = ( s, build ) => { if( !build.skip && build.status != BuildEntry.Status.Disabled ) { project.AddWarning( "STARTED: " + build.displayName ); } }; OnEntryFinishedHandler finishedHandler = ( s, build ) => { if( build.Failed() ) { project.PostBuildError( "FAILED: " + build.displayName + " ( " + build.logPath + " )" ); project.AddError( "FAILED: " + build.displayName + " ( " + build.logPath + " )" ); // project.RemoveClientLogging( userSocket ); ret = false; } else if( build.Succeeded() ) { project.PostBuildMessage( "OK: " + build.displayName + " ( " + build.logPath + " )" ); project.AddSuccess( "OK: " + build.displayName + " ( " + build.logPath + " )" ); } }; //project.SetupClientLogging( userSocket ); project.builder.OnEntryStarted += startedHandler; project.builder.OnEntryFinished += finishedHandler; /*System.Threading.Tasks.Task th = settings.PerformTestShelve( q.testingShelveId, userSocket.user ); th.Wait(); */ bool rebuild = queueEntry.values["clean"] != "false"; bool breakOnFirstError = false; string str; if( queueEntry.values.TryGetValue( "breakonerror", out str ) ) { if( !string.IsNullOrEmpty( str ) && (string.Compare(str, "yes", true) == 0 || string.Compare( str, "true", true ) == 0 ) ) { breakOnFirstError = true; } } bool continueOnError = Oozy.continuousContinuesOnError; if( breakOnFirstError ) { Oozy.continuousContinuesOnError = false; } bool oldSubmit = settings.submitP4; settings.submitP4 = false; CIResult result = settings.PerformTestShelve( queueEntry.testingShelveId, queueEntry.testingUser, rebuild: rebuild ); settings.submitP4 = oldSubmit; Oozy.continuousContinuesOnError = continueOnError; if( result == null || !result.ranNormally ) { ret = false; } // for( int i = 0; i < 10; ++i ) // { // project.AddLog( "Test message " + i ); // Thread.Sleep( 1000 ); // } project.builder.OnEntryFinished -= finishedHandler; project.builder.OnEntryStarted -= startedHandler; msg = ""; if( ret ) { msg = "Test shelve completed successfully.\n"; } else { msg = "`Test shelve Failed.`\n"; } DateTime time = project.builder.buildStartingTime ?? DateTime.Now; string logPath = project.builder.GetBuilderLogPath( time ); if( !string.IsNullOrEmpty( msg ) ) { msg += "Logs are in " + SlackClient.MakeFileHyperLink( logPath, logPath ); project.PostBuildMessage( msg ); } if( ret ) { project.StopBuildThreadSuccess(); } else { project.StopBuildThreadError(); } // project.RemoveClientLogging( userSocket ); 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; } string CaptureScriptErrors( string logPath, int maxErrors ) { //string[] lines = File.ReadAllLines( logPath ); return ""; } List skippedFiles; void CheckSkipped( uint cmdId, int msgId, int level, string data ) { if( msgId == Perforce.P4.P4ClientError.MsgServer_ResolvedSkipped ) { Regex skipped = new Regex( @"^(?[\s\S]+) - resolve skipped.", RegexOptions.IgnoreCase ); Match m = skipped.Match( data ); if( m.Success ) { string file = m.Groups["file"].Value; skippedFiles.Add( file ); } } } int SetupLocalXml(string branchList, string platformList) { try { string[] branches = branchList.Split( new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries ); string[] platforms = platformList.Split( new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries ); XDocument xmlDoc = new XDocument( new XDeclaration( "1.0", "UTF-8", "yes" ), new XElement( "local", //TODO: DHM FIX ME: obsolete format new XComment( "Tools Migration: old XAttribute-format but still read by RSG.Base.Configuration." ), new XElement( "branches", new XAttribute( "default", branches[0] ), branches.Select( branch => new XElement( "branch", new XAttribute( "name", branch ), new XElement( "targets", platforms.Select( kvp => new XElement( "target", new XAttribute( "platform", kvp ), new XAttribute( "enabled", true ) ) ) ) ) ) ), //TODO: DHM FIX ME: end obsolete format // New XElement-format. new XComment( "Tools Migration: new XElement-format." ), new XElement( "ConfigFileVersion", 2 ), new XElement( "Branches", new XElement( "Default", branches[0] ), branches.Select( branch => new XElement( "Branch", new XElement( "Name", branch ), new XElement( "Targets", platforms.Select( kvp => new XElement( "Target", new XElement( "Platform", kvp ), new XElement( "Enabled", true ) ) ) ) ) ) ), new XElement( "DefaultPlatforms", platforms.Select( kvp => new XElement( "DefaultPlatform", new XElement( "Platform", kvp ), new XElement( "Enabled", true ) ) ) ) ) ); string local = Path.Combine( project.builder.workingDirectory, @"tools_ng\etc\local.xml" ); xmlDoc.Save( local ); } catch( Exception ex ) { project.AddError( App.LogException( ex ) ); return ex.HResult; } return 0; } int UpdateNatives( int changelistId ) { int ret = 0; if( changelistId == 0 ) { changelistId = -1; } P4 p4 = currentP4; if( p4 == null ) { project.AddError("UpdateNatives: p4 is null"); return 1; } Settings settings = project.Settings(); string srcPath = @"src\dev_gen9\game\script_headers"; string dstPath = @"script\dev_gen9\core\common\native"; List paths = buildPaths.Where(a => a.StartsWith(Path.Combine(project.builder.workingDirectory, srcPath), StringComparison.OrdinalIgnoreCase)).ToList(); List dests = new List(paths.Count); foreach( string src in paths ) { string dst = src.Replace(srcPath, dstPath); if( !settings.filesToMerge.Exists(a => string.Compare(a.source, src, true) == 0 && string.Compare(a.dest, dst, true) == 0) ) { settings.filesToMerge.Add(new FileToMerge() { source = src, dest = dst }); } // IList merged = p4.Merge(new Perforce.P4.LocalPath(src), new Perforce.P4.LocalPath(dst), changelistId); // dests.AddRange(merged); // dst = @"x:\gta5\script\dev_gen9_trailer\core\common\native\commands_graphics.sch"; // merged = p4.Merge(new Perforce.P4.LocalPath(src), new Perforce.P4.LocalPath(dst), changelistId); // dests.AddRange(merged); } foreach( FileToMerge merge in settings.filesToMerge ) { IList merged = p4.Merge(new Perforce.P4.LocalPath(merge.source), new Perforce.P4.LocalPath(merge.dest), changelistId); dests.AddRange(merged); } StringBuilder msg = new StringBuilder(1024); if( dests.Count > 0 ) { int errors = 0; try { skippedFiles = new List(); p4.rep.Connection.InfoResultsReceived += CheckSkipped; IList records = p4.Resolve(dests, Perforce.P4.ResolveFilesCmdFlags.AutomaticMergeMode, changelistId); //p4.rep.Connection.InfoResultsReceived -= CheckSkipped; Dictionary> userMessages = new Dictionary>(StringComparer.OrdinalIgnoreCase); IList files; if( skippedFiles.Count > 0 ) { errors += skippedFiles.Count; files = new List(); foreach( string file in skippedFiles ) { files.Add(p4.GetFileSpecFromPath(file)); } files = p4.Where(files); foreach( Perforce.P4.FileSpec fileSpec in files ) { var record = records.Where(a => a.LocalFilePath != null && string.Compare(a.LocalFilePath.Path, fileSpec.LocalPath.Path, true) == 0).ToList(); if( record.Empty() ) { msg.Append("\t`Natives: Failed to find resolve record for " + fileSpec.DepotPath.Path + "`\n"); continue; } records.Remove(record[0]); var bo = buildInfo.FirstOrDefault(a => a.changes.Exists(b => b.files.FirstOrDefault(c => string.Compare(c, fileSpec.LocalPath.Path, true) == 0) != string.Empty)); if( bo == null ) { msg.Append("`\tNatives: Failed to find changelist info for " + fileSpec.DepotPath.Path + "`\n"); continue; } List messages; if( !userMessages.TryGetValue(bo.userName, out messages) ) { messages = new List(); userMessages[bo.userName] = messages; } messages.Add("`Natives: Failed to merge " + record[0].BaseFileSpec.DepotPath.Path + " to " + fileSpec.LocalPath.Path + "`\n"); } } var successFiles = records.Select(a => p4.GetFileSpecFromPath(a.FromFileSpec.DepotPath.Path)).ToList(); files = p4.Where(successFiles); int count = 0; if( files.Count == records.Count ) { foreach( var record in records ) { record.LocalFilePath = files[count++].LocalPath; if( record.Action == Perforce.P4.FileAction.None ) { continue; } List messages; var bos = buildInfo.Where(a => a.changes.Exists(b => b.files.Exists(c => string.Compare(c, record.LocalFilePath.Path, true) == 0))); foreach( BuildInfo bo in bos ) { if( !userMessages.TryGetValue(bo.userName, out messages) ) { messages = new List(); userMessages[bo.userName] = messages; } messages.Add("Natives: merged *" + record.LocalFilePath.Path + "* to *" + dstPath + "* OK\n"); } } StringBuilder threadMessage = new StringBuilder(); msg.Append("This is just a test, nothing has actually merged. This is informational only. No hamsters with cat hats, sorry.\n"); foreach( var pair in userMessages ) { msg.Append(project.GetSlackUser(/*pair.Key*/"Leigh.Bird") + " (but really _" + pair.Key + "_)\n"); threadMessage.Append(string.Join("", pair.Value)); } List response = project.PostSlackMessage(msg.ToString()); project.PostSlackMessage(threadMessage.ToString(), response); } else { ++errors; string err = project.GetSlackUser("Leigh.Bird") + " `HALP! Natives Merge: file.Count = " + files.Count + " but records.Count = " + records.Count + "`"; project.PostSlackMessage(err); } } catch( Exception ex ) { ++errors; string err = App.LogException(ex); err = project.GetSlackUser("Leigh.Bird") + " `HALP! You have made a whoopsie`\n``` " + err + " ```"; project.PostSlackMessage(err); } if( errors == 0 ) { settings.filesToMerge.Clear(); } else { //Add this when temp removed //p4.Revert(Perforce.P4.FileSpec.UnversionedSpecList(dests)); } //temp for now { settings.filesToMerge.Clear(); p4.Revert(Perforce.P4.FileSpec.UnversionedSpecList(dests)); } } return ret; } int UpdateNativesGen9(int changelistId) { int ret = 0; if( changelistId == 0 ) { changelistId = -1; } P4 p4 = currentP4; if( p4 == null ) { project.AddError("UpdateNatives: p4 is null"); return 1; } Settings settings = project.Settings(); string srcPath = @"src\dev_gen9_sga\game\script_headers"; string dstPath = @"script\dev_gen9_sga\core\common\native"; List paths = buildPaths.Where(a => a.StartsWith(Path.Combine(project.builder.workingDirectory, srcPath), StringComparison.OrdinalIgnoreCase)).ToList(); List dests = new List(paths.Count); foreach( string src in paths ) { string dst = src.Replace(srcPath, dstPath); if( !settings.filesToMerge.Exists(a => string.Compare(a.source, src, true) == 0 && string.Compare(a.dest, dst, true) == 0) ) { settings.filesToMerge.Add(new FileToMerge() { source = src, dest = dst }); } // IList merged = p4.Merge(new Perforce.P4.LocalPath(src), new Perforce.P4.LocalPath(dst), changelistId); // dests.AddRange(merged); // // dst = @"x:\gta5\script\dev_gen9_trailer\core\common\native\commands_graphics.sch"; // merged = p4.Merge(new Perforce.P4.LocalPath(src), new Perforce.P4.LocalPath(dst), changelistId); // dests.AddRange(merged); } foreach( FileToMerge merge in settings.filesToMerge ) { IList merged = p4.Merge(new Perforce.P4.LocalPath(merge.source), new Perforce.P4.LocalPath(merge.dest), changelistId); dests.AddRange(merged); } StringBuilder msg = new StringBuilder(1024); if( dests.Count > 0 ) { int errors = 0; try { skippedFiles = new List(); p4.rep.Connection.InfoResultsReceived += CheckSkipped; IList records = p4.Resolve(dests, Perforce.P4.ResolveFilesCmdFlags.AutomaticMergeMode, changelistId); //p4.rep.Connection.InfoResultsReceived -= CheckSkipped; Dictionary> userMessages = new Dictionary>(StringComparer.OrdinalIgnoreCase); IList files; if( skippedFiles.Count > 0 ) { errors += skippedFiles.Count; files = new List(); foreach( string file in skippedFiles ) { files.Add(p4.GetFileSpecFromPath(file)); } files = p4.Where(files); foreach( Perforce.P4.FileSpec fileSpec in files ) { var record = records.Where(a => a.LocalFilePath != null && string.Compare(a.LocalFilePath.Path, fileSpec.LocalPath.Path, true) == 0).ToList(); if( record.Empty() ) { msg.Append("\t`Natives: Failed to find resolve record for " + fileSpec.DepotPath.Path + "`\n"); continue; } records.Remove(record[0]); var bo = buildInfo.FirstOrDefault(a => a.changes.Exists(b => b.files.FirstOrDefault(c => string.Compare(c, fileSpec.LocalPath.Path, true) == 0) != string.Empty)); if( bo == null ) { msg.Append("`\tNatives: Failed to find changelist info for " + fileSpec.DepotPath.Path + "`\n"); continue; } List messages; if( !userMessages.TryGetValue(bo.userName, out messages) ) { messages = new List(); userMessages[bo.userName] = messages; } messages.Add("`Natives: Failed to merge " + record[0].BaseFileSpec.DepotPath.Path + " to " + fileSpec.LocalPath.Path + "`\n"); } } var successFiles = records.Select(a => p4.GetFileSpecFromPath(a.FromFileSpec.DepotPath.Path)).ToList(); files = p4.Where(successFiles); int count = 0; if( files.Count == records.Count ) { foreach( var record in records ) { record.LocalFilePath = files[count++].LocalPath; if( record.Action == Perforce.P4.FileAction.None ) { continue; } List messages; var bos = buildInfo.Where(a => a.changes.Exists(b => b.files.Exists(c => string.Compare(c, record.LocalFilePath.Path, true) == 0))); foreach( BuildInfo bo in bos ) { if( !userMessages.TryGetValue(bo.userName, out messages) ) { messages = new List(); userMessages[bo.userName] = messages; } messages.Add("Natives: merged *" + record.LocalFilePath.Path + "* to *" + dstPath + "* OK\n"); } } StringBuilder threadMessage = new StringBuilder(); msg.Append("This is just a test, nothing has actually merged. This is informational only. No hamsters with cat hats, sorry.\n"); foreach( var pair in userMessages ) { msg.Append(project.GetSlackUser(/*pair.Key*/"Leigh.Bird") + " (but really _" + pair.Key + "_)\n"); threadMessage.Append(string.Join("", pair.Value)); } List response = project.PostSlackMessage(msg.ToString()); project.PostSlackMessage(threadMessage.ToString(), response); } else { ++errors; string err = project.GetSlackUser("Leigh.Bird") + " `HALP! Natives Merge: file.Count = " + files.Count + " but records.Count = " + records.Count + "`"; project.PostSlackMessage(err); } } catch( Exception ex ) { ++errors; string err = App.LogException(ex); err = project.GetSlackUser("Leigh.Bird") + " `HALP! You have made a whoopsie`\n``` " + err + " ```"; project.PostSlackMessage(err); } if( errors == 0 ) { settings.filesToMerge.Clear(); } else { //Add this when temp removed //p4.Revert(Perforce.P4.FileSpec.UnversionedSpecList(dests)); } //temp for now { settings.filesToMerge.Clear(); p4.Revert(Perforce.P4.FileSpec.UnversionedSpecList(dests)); } } return ret; } int MergeShaders( int changelistId, string branch ) { if( changelistId <= 0 ) { return 0; } if( currentP4 == null ) { return 0; } P4 p4 = currentP4; List sources = new List() { Path.Combine(p4root, @"gta5\titleupdate\" + branch + @"\common\shaders\sga_prospero"), Path.Combine(p4root, @"gta5\titleupdate\" + branch + @"\common\shaders\sga_prospero_debug"), Path.Combine(p4root, @"gta5\titleupdate\" + branch + @"\common\shaders\sga_prospero_final"), Path.Combine(p4root, @"gta5\titleupdate\" + branch + @"\common\shaders\sga_scarlett"), Path.Combine(p4root, @"gta5\titleupdate\" + branch + @"\common\shaders\sga_scarlett_debug"), Path.Combine(p4root, @"gta5\titleupdate\" + branch + @"\common\shaders\sga_scarlett_final"), Path.Combine(p4root, @"gta5\titleupdate\" + branch + @"\common\shaders\sga_win32_50"), Path.Combine(p4root, @"gta5\titleupdate\" + branch + @"\common\shaders\sga_win32_50_debug"), Path.Combine(p4root, @"gta5\titleupdate\" + branch + @"\common\shaders\sga_win32_50_final"), Path.Combine(p4root, @"gta5\titleupdate\" + branch + @"\common\shaders\sga_win32_60"), Path.Combine(p4root, @"gta5\titleupdate\" + branch + @"\common\shaders\sga_win32_60_debug"), Path.Combine(p4root, @"gta5\titleupdate\" + branch + @"\common\shaders\sga_win32_60_final"), }; List dests = new List() { Path.Combine(p4root, @"gta5\build\" + branch + @"\common\shaders"), }; p4.RevertUnchangedFiles( changelistId ); List files = new List(); foreach( string dst in dests ) { foreach( string src in sources ) { string dest = Path.Combine(dst, Path.GetFileName(src), "..."); files.AddRange(p4.Merge(new Perforce.P4.FileSpec(new Perforce.P4.LocalPath(Path.Combine(src, "..."))), new Perforce.P4.FileSpec(new Perforce.P4.LocalPath(dest)), changelistId)); } } if( files.Count > 0 ) { p4.Resolve(files, Perforce.P4.ResolveFilesCmdFlags.AutomaticTheirsMode, changelistId); } return 0; } 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; Settings settings = project.Settings(); SerializableDictionary branchStatuses = (SerializableDictionary)project.customSettings["branchStatus"]; if( branchStatuses != null ) { BranchStatus branchStatus; int cl = 0; if( branchStatuses.TryGetValue( branch, out branchStatus ) ) { cl = branchStatus.syncedChangelist; } cl = cl == 0 ? settings.lastCIChangelist : cl; if( cl != 0 ) { ret = UpdateVersion( cl, file, branch ) ? 0 : 1; } } return ret; } /****************************************************************************************************************** ** ******************************************************************************************************************/ 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 string CheckIB() { string installDir = ""; using( RegistryKey keyBase = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32) ) { if( null == keyBase ) { project.AddError("32-bit HKLM registry key open failed."); return "32-bit HKLM registry key open failed."; } using( RegistryKey key = keyBase.OpenSubKey("Software\\Xoreax\\IncrediBuild\\Builder") ) { string version = key.GetValue("VersionText") as string; if( string.IsNullOrEmpty(version) ) { return "IB not installed"; } installDir = key.GetValue("Folder") as string; if( string.IsNullOrEmpty(installDir) ) { return "IB not installed"; } } using( RegistryKey key = keyBase.OpenSubKey("Software\\Xoreax\\IncrediBuild\\BuildService") ) { if( key == null ) { return "Failed to open BuildService in registry"; } string coord = key.GetValue("CoordHost") as string; if( string.IsNullOrEmpty(coord) ) { return "Coordinator isn't set"; } } } bool gettingLicenses = false; List licenses = new List(); new BuildCommand(Directory.GetCurrentDirectory(), Path.Combine(installDir, "xgconsole.exe"), "/QUERYLICENSE") { OnOutput = (object sender, string output, OutputLevel level)=> { if( gettingLicenses ) { if( !string.IsNullOrEmpty(output) && !output.Contains("-------------------") && !output.Contains("- Helper") ) { licenses.Add(output.Trim()); } } else { if( output.Contains("Packages installed:") ) { gettingLicenses = true; } } } }.Execute(""); if( licenses.Count == 0 ) { return "Licenses need updating."; } return ""; } /****************************************************************************************************************** ** ******************************************************************************************************************/ public void Activate() { if( Oozy.continuous ) { Settings settings = project.Settings(); project.SetContinuous( new TimeSpan( 0, 5, 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( "TestShelve", BuildTypeFlags.TestingShelve, TestShelveQueueEntry ); project.builder.buildQueue.Register( "PackageBuild", BuildTypeFlags.Packaging, PackageBuild ); project.ipWatcher.OnIPStateChanged += IPStateChanged; } } /****************************************************************************************************************** ** ******************************************************************************************************************/ public void DeActivate() { if( Oozy.continuous ) { project.builder.buildQueue.Unregister( "TestShelve" ); project.builder.buildQueue.Unregister( "PackageBuild" ); project.SetContinuous( null ); project.SetNightbuild( null ); project.ipWatcher.OnIPStateChanged += IPStateChanged; } } 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 ) { Settings settings = project.Settings(); settings.pendingReboot = SystemUpdates.PendingReboot(); if( settings.pendingReboot ) { 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." ); } 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; VariableParser.GetValueDelegate getEnv = ( string var, List values ) => { if( string.Compare( var, "cleanpackage", true ) == 0 ) { values.Add("-clean"); } }; VariableParser.GetValue += getEnv; 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 ) ); } VariableParser.GetValue += getEnv; 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:\RSGDND\Tools\Utils\IB Fix 9.5.9\IBSetupConsole_9.5.9.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; Settings settings = project.Settings(); if( !settings.pendingReboot ) { bool pendingReboot = SystemUpdates.PendingReboot(); ; if( pendingReboot ) { settings.pendingReboot = true; project.PostBuildError( Environment.GetEnvironmentVariable( "COMPUTERNAME" ) + ": I have pending updates and stuff, looks like I need to be rebooted.If I do it myself I won't auto-restart." ); } } if( settings.incredibuild ) { string err = CheckIB(); if( !string.IsNullOrEmpty(err) ) { project.StartBuildThread( "IB is out of date, attempting an update" ); string err2 = InstallIB(); if( !string.IsNullOrEmpty( err2 ) ) { project.PostBuildError( "Oh noes, it's all bad, someone needs to take a look " + err2 ); err += err2; if( notifiedIBFailed != err ) { SlackMessage message = new SlackMessage(); string image = ImageSearch.instance.Search( "hamster+wheel+gif" ); if( !string.IsNullOrEmpty( image ) ) { SlackImage slackImage = new SlackImage(); slackImage.SetImage( image, "HAMSTER MAN IS UPSET" ); message.Add( "HALP!! IB agent check failed, aborting CI `" + err + "`.", slackImage ); } else { message.AddText( "HALP!! IB agent check failed, aborting CI `" + err + "`." ); } project.NotifySlackUsers( new List( 1 ) { "gen9coders" }, message ); notifiedIBFailed = err; project.StopBuildThreadError(); } project.AddError( "IB agent check failed \"" + err + "\", aborting." ); return true; //yup, badness, keep log } else { project.StopBuildThreadSuccess(); } } notifiedIBFailed = ""; } 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; } /****************************************************************************************************************** ** ******************************************************************************************************************/ int SubmitProjectGen() { Settings settings = project.Settings(); bool submitProjectFiles = true;// !builder.test; if( currentP4 != null && submitProjectFiles && settings.submitP4 ) { RemoveProjGen( currentP4, true ); } return 0; } /****************************************************************************************************************** ** ******************************************************************************************************************/ CIResult DoBuild(Builder builder, BuildParams buildParams) { Settings settings = project.Settings(); P4 p4 = SetupP4(); if( p4 == null ) { return null; } bool syncedFiles = false; int currentChangelist = -1; //int workingDataCL = 30794996; string dataLabelName = ""; //disable tagging if( settings.createLabels ) { dataLabelName = "Ruffian_Data"; } 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; if( nightbuild || package ) { project.PostBuildMessage( "Syncing..." ); } IList files = SyncBranches( p4, currentChangelist: currentChangelist, labelName: dataLabelName ); 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(); project.buildEnvironment["RunProjGen"] = "true"; 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 = ""; calendar.DownloadCalendars(); DayEvents today = calendar.GetTodayEvents(); List sendToUsers = new List(); if( nightbuild && !package && today.Count > 0 ) { foreach( CalendarEntry e in today ) { iCal.Locale locale = e.locale; if( locale.HasFlag( iCal.Locale.Scottish ) && locale.HasFlag( iCal.Locale.English ) && locale.HasFlag( iCal.Locale.Irish ) ) { msg += ":flag-gb:"; } else { if( locale.HasFlag( iCal.Locale.Scottish ) ) msg += ":flag-scotland:"; if( locale.HasFlag( iCal.Locale.English ) ) msg += ":flag-england:"; if( locale.HasFlag( iCal.Locale.Irish ) ) msg += ":flag-ie:"; } msg += " " + e.summary + "\n\n"; } } Stopwatch symbolTime = null; string tmpLabel = "Ruffian_QA_TMP"; string rollbackLabel = "Ruffian_QA_Rollback"; string QALabel = "Ruffian_QA"; 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 && createPackage; 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"; } } 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 ) { bool doRollbackLabel = true; //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( rollbackLabel ); if( doRollbackLabel ) { IList nextPrevious = p4.FindLabel( QALabel ); if( nextPrevious.Count > 0 ) { //We have a "current" build already, copy that to previous p4.CopyLabel( rollbackLabel, nextPrevious[0].Id ); } } p4.DeleteLabel( QALabel ); p4.CopyLabel( QALabel, label.Id ); p4.DeleteLabel( label.Id ); } } sendToUsers = QA; showReport = true; } if( settings.filesToMerge.Count > 0 ) { msg += "There are " + settings.filesToMerge.Count + " scripts to be merged.\n"; foreach( FileToMerge file in settings.filesToMerge ) { project.AddError( "Merge: " + file.source + " => " + file.dest ); } } 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 failed = settings.lastBuildStatus.Failed(); // settings.EnableRetryButton( failed ); } 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 ); } } project.buildEnvironment["RSG_AUTOMATION_CODEBUILDER_SRC_CL"] = ""; return result; } public CIResult TestShelve(int cl, bool rebuild) { Settings settings = project.Settings(); DateTime start = DateTime.Now; project.builder.buildStartingTime = start; string logPath = project.builder.GetBuilderLogPath(start); Program.PushLogPath(logPath); if(project.projectStatus == ProjectStatus.Failed) { project.PostBuildError( "Current build isn't in a good state, testing a shelf is a bad idea. Unless this will fix it, then cool." ); project.AddError( "Current build isn't in a good state, testing a shelf is a bad idea. Unless this will fix it, then cool." ); // Program.PopLogPath(); // return false; } string ini = project.p4IniFile; if(!File.Exists( ini )) { project.PostBuildError( "p4ini \"" + ini + "\" not found, aborting shelve test" ); project.AddLog( "p4ini \"" + ini + "\" not found, aborting shelve test" ); Program.PopLogPath(); return null; } P4 p4 = new P4( ini ); project.AddLog( "Connecting to " + p4.Port ); if(!p4.Connect()) { project.PostBuildError( "P4: Failed to connect host: " + p4.Port + ", user: " + p4.User + ", workspace: " + p4.Workspace ); project.AddError( "P4: Failed to connect host: " + p4.Port + ", user: " + p4.User + ", workspace: " + p4.Workspace ); Program.PopLogPath(); return null; } project.SetupSCCOutput( p4 ); Perforce.P4.Changelist changelist = p4.FindChangelist( "EMPTY" ); string description = "Test " + cl; if( changelist == null ) { changelist = p4.CreateChangelist( description ); } else { changelist.Description = description; p4.UpdateChangelist( changelist ); } Dictionary> localFilesShelved = new Dictionary>(); List filesToDelete = new List(); CIResult ret = null; bool testMode = project.builder.test; try { IList fstats = null; { IList shelved = p4.GetShelvedFiles( cl ); if( shelved.Count > 0 ) { IList tmp = Perforce.P4.FileSpec.UnversionedSpecList(shelved.Select(a => a.ToFileSpec()).ToList()); fstats = p4.FStat(tmp); foreach( var fstat in fstats ) { if( fstat.Action != Perforce.P4.FileAction.None ) { List clFiles; if( !localFilesShelved.TryGetValue(fstat.Change, out clFiles) ) { clFiles = new List(); localFilesShelved[fstat.Change] = clFiles; } clFiles.Add(fstat.DepotPath); } } if( !testMode ) { foreach( var pair in localFilesShelved ) { if( pair.Key != 0 ) { p4.rep.Connection.Client.ShelveFiles(pair.Value, new Perforce.P4.ShelveFilesCmdOptions(Perforce.P4.ShelveFilesCmdFlags.Force, null, pair.Key)); } } } } else { project.PostBuildError( "No files shelved in " + cl ); project.AddError( "No files shelved in " + cl ); } } IList files = p4.Unshelve( cl, changelist.Id, clobberWritable: true, force: true, preview: testMode ); if(files.Count > 0) { files = Perforce.P4.FileSpec.UnversionedSpecList( files ); //p4.Resolve( files ); fstats = p4.FStat( files ); if(fstats.Count == 0) { project.PostBuildError( "P4 didn't return any results for fstat when checking " + cl ); project.AddError( "P4 didn't return any results for fstat when checking " + cl ); } else { List> filesNotOnHead = new List>(); bool validated = true; foreach( var fstat in fstats ) { bool fileOnHead = (fstat.HeadAction == Perforce.P4.FileAction.Delete && fstat.HaveRev == -1) || (fstat.HaveRev >= fstat.HeadRev); if( !fileOnHead ) { filesNotOnHead.Add(new Tuple(fstat.DepotPath.Path, fstat.HaveRev, fstat.HeadRev)); } if( fstat.Action == Perforce.P4.FileAction.Add || fstat.Action == Perforce.P4.FileAction.MoveAdd ) { filesToDelete.Add(fstat.LocalPath.Path); } if( fstat.Unresolved ) { validated = false; project.PostBuildError( "P4: " + fstat.DepotPath.Path + " is unresolved." ); project.AddError( "P4: " + fstat.DepotPath.Path + " is unresolved." ); } } if(filesNotOnHead.Count > 0) { validated = false; foreach(Tuple path in filesNotOnHead) { project.PostBuildError( "P4: File not on head " + path.Item1 + ", is on rev " + path.Item2 + ", should be on " + path.Item3 ); project.AddError( "P4: File not on head " + path.Item1 + ", is on rev " + path.Item2 + ", should be on " + path.Item3 ); } } if( validated ) { //p4.Sync( files, autoResolve: true ); //Get their local paths IList where2 = p4.Where( files ); if(where2 != null && where2.Count == files.Count) { List buildInfo = new List(); List users = new List(); List paths = new List( files.Count ); Perforce.P4.Changelist shelvedCl = p4.GetChangelist( cl, mineOnly: false ); BuildChange bc = GetBuildInfo( shelvedCl, buildInfo, users ); foreach(var f in where2) { paths.Add( f.LocalPath.Path ); bc.files.Add( f.LocalPath.Path ); } SetupUserInfo( p4, buildInfo, users ); // project.builder.AddEnvironment( environment ); bool oldSubmit = settings.submitP4; settings.submitP4 = false; project.buildEnvironment["RSG_AUTOMATION_CODEBUILDER_SRC_CL"] = settings.lastCIChangelist.ToString(); project.buildEnvironment["RunProjGen"] = "true"; BuildParams buildParams = new BuildParams(); buildParams.rebuild = rebuild; buildParams.flags = BuildTypeFlags.TestingShelve; ret = project.HandleFileChanges( buildParams, false, p4, settings.lastCIChangelist, buildInfo, paths ); project.buildEnvironment["RSG_AUTOMATION_CODEBUILDER_SRC_CL"] = ""; settings.submitP4 = oldSubmit; // project.builder.RemoveEnvironment( environment ); } else { project.PostBuildError( "P4: Problem running where" ); project.AddError( "P4: Problem running where" ); } } } Perforce.P4.RevertFilesCmdFlags flags = testMode ? Perforce.P4.RevertFilesCmdFlags.Preview : Perforce.P4.RevertFilesCmdFlags.None; p4.Revert( files, flags ); files.SetVersion( new Perforce.P4.ChangelistIdVersion( settings.lastCIChangelist ) ); p4.Sync( files, autoResolve: true, clobberWritable: true, preview: testMode ); } } catch(Exception ex) { project.PostBuildError( "TestShelve " + ex.Message ); project.AddError( "TestShelve " + ex.Message ); } changelist.Description = "EMPTY"; p4.UpdateChangelist( changelist ); RemoveProjGen( p4, false ); if(!testMode) { foreach(var pair in localFilesShelved) { if(pair.Key != 0) { p4.rep.Connection.Client.UnshelveFiles( pair.Value, new Perforce.P4.UnshelveFilesCmdOptions( Perforce.P4.UnshelveFilesCmdFlags.Force, pair.Key, pair.Key ) ); } } foreach(string file in filesToDelete) { try { if(File.Exists( file )) { File.Delete( file ); } } catch(Exception) { } } } Program.PopLogPath(); return ret; } } }