Files
gtav-src/tools_ng/etc/Oozybuild/dev_ng_prebuild.csScript
2025-09-29 00:52:08 +02:00

1338 lines
43 KiB
Plaintext
Executable File

//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.Windows.Media;
using System.Xml.Serialization;
namespace dev_ng_prebuild
{
/******************************************************************************************************************
**
******************************************************************************************************************/
public static class ProjectExtension
{
public static void AddLog(this OozyBuild.Project project, string log, Brush colour = null)
{
if (project.log.Add != null)
{
project.log.Add(log);
}
}
public static void AddWarning(this OozyBuild.Project project, string log)
{
if (project.log.AddWarning != null)
{
project.log.AddWarning(log);
}
}
public static void AddError(this OozyBuild.Project project, string log)
{
if (project.log.AddError != null)
{
project.log.AddError(log);
}
}
public static void AddSuccess(this OozyBuild.Project project, string log)
{
if (project.log.AddSuccess != null)
{
project.log.AddSuccess(log);
}
}
public static void RewindLog(this OozyBuild.Project project, int count)
{
if (project.log.RewindLog != null)
{
project.log.RewindLog.Invoke(count);
}
}
public static DevNgPrebuildSettings Settings(this OozyBuild.Project project)
{
return project.customSettings as DevNgPrebuildSettings;
}
}
/******************************************************************************************************************
**
******************************************************************************************************************/
public class DevNgPrebuildSettings : OozyBuild.CustomSettings
{
public DevNgPrebuildSettings()
{
//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<Guid, List<BuildBreaker>>();
packageDir = "";
packageLimit = 5;
transferDir = @"";
logDir = @"tools_ng\script\util\BuildDept\GTA5_NG_BuildTool_V2\BuildTool\CI_Logs";
lastCIChangelist = -1;
obscureSlackMessages = true;
lastResult = new CIResult();
lastNightbuildResult = new CIResult();
branchStatus = new SerializableDictionary<string, BranchStatus>();
}
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=<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<Guid, List<BuildBreaker>> buildBreakers { get; set; }
public int filesSyncedToday { get; set; }
public SerializableDictionary<string, BranchStatus> branchStatus { get; set; }
public SerializableDictionary<Guid, BuiltInfo> 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<string, BranchSyncInfo> branchPaths { get; set; }
//Dictionary of DLC branches
protected Dictionary<string, BranchSyncInfo> mpDlcBranchPaths { get; set; }
//Dictionary of DLC branches
protected Dictionary<string, BranchSyncInfo> spDlcBranchPaths { get; set; }
/******************************************************************************************************************
**
******************************************************************************************************************/
public static BuildExtension Init(Project inProject)
{
return new BuildExtension(inProject);
}
/******************************************************************************************************************
**
******************************************************************************************************************/
public BuildExtension(Project inProject)
{
project = inProject;
DevNgPrebuildSettings settings = new DevNgPrebuildSettings();
settings.Setup(inProject);
settings.extension = this;
branchPaths = new Dictionary<string, BranchSyncInfo>()
{
{ "dev_ng", new BranchSyncInfo() { storeChanges = true, syncPaths = new List<string>()
{
@"gta5\src\dev_ng\...",
@"gta5\src\dev_ng\rage\...",
@"gta5\script\dev_ng\...",
@"gta5\assets_ng\GameText\dev_ng\...",
@"gta5\titleupdate\dev_ng\...",
@"gta5\assets_ng\metadata\...",
@"gta5\assets_ng\titleupdate\dev_ng\...",
@"gta5\titleupdate\dev_ng\x64\audio\...",
@"gta5_dlc/mpPacks/mpChristmas3/build/dev_ng/...",
}
}
},
};
mpDlcBranchPaths = new Dictionary<string, BranchSyncInfo>()
{
{ "dev_ng", new BranchSyncInfo() { storeChanges = false, syncPaths = new List<string>()
{
@"gta5_dlc/mpPacks/mpChristmas3/build/dev_ng/...",
@"gta5_dlc/mpPacks/mpChristmas3/assets_ng/gametext/...",
@"gta5_dlc/mpPacks/mpChristmas3/*.xml",
@"gta5_dlc/mpPacks/mpChristmas3_G9EC/build/dev_ng/...",
@"gta5_dlc/mpPacks/mpChristmas3_G9EC/assets_ng/gametext/...",
@"gta5_dlc/mpPacks/mpChristmas3_G9EC/*.xml",
@"gta5_dlc/patchPacks/patchday28NG/build/dev_ng/...",
@"gta5_dlc/patchPacks/patchday28NG/*.xml",
@"gta5_dlc/patchPacks/patchday28G9ECNG/build/dev_ng/...",
@"gta5_dlc/patchPacks/patchday28G9ECNG/*.xml",
}
}
},
};
spDlcBranchPaths = new Dictionary<string, BranchSyncInfo>()
{
{ "dev_ng", new BranchSyncInfo() { storeChanges = false, syncPaths = new List<string>()
{
@"gta5_dlc/spPacks/*/*/Gametext/...",
@"gta5_dlc/spPacks/dlc_agentTrevor/build/dev_ng/...",
@"gta5_dlc/spPacks/spUpgrade/build/dev_ng/...",
@"gta5_dlc/spPacks/dlc_agentTrevor/*.*",
@"gta5_dlc/spPacks/spUpgrade/*.*",
}
}
},
};
//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<string, BranchStatus>();
}
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("dev_ng_prebuild loaded");
}
object PackageBuild(QueueEntry queueEntry)
{
DevNgPrebuildSettings 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<string> 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<int> 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$\";
//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;
// DevNgPrebuildSettings 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<Perforce.P4.Changelist> GetChangelistsAtRevision(P4 p4, IList<Perforce.P4.FileSpec> 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<Perforce.P4.Changelist> changes = p4.rep.GetChangelists(options, specs.ToArray());
return changes;
}
int SimpleSync(string filePath)
{
P4 p4 = SetupP4();
if (p4 == null)
{
return 1;
}
project.PostBuildMessage("Syncing: " + filePath);
Perforce.P4.SyncFilesCmdOptions syncOpts = new Perforce.P4.SyncFilesCmdOptions(Perforce.P4.SyncFilesCmdFlags.None, 1);
Perforce.P4.FileSpec depotFile = new Perforce.P4.FileSpec(new Perforce.P4.DepotPath(filePath), null, null, null);
IList<Perforce.P4.FileSpec> files = p4.Sync(depotFile, preview: true, autoResolve: true, clobberWritable: true);
return 0;
}
/******************************************************************************************************************
** Called from .nom. Syncs files relevant to the branch
******************************************************************************************************************/
int SyncBranch(string branchName, string outputFile = "", string pathsCollection = "")
{
P4 p4 = SetupP4();
if (p4 == null)
{
return 1;
}
IList<Perforce.P4.FileSpec> files = SyncBranches(p4, branchName, -1, false, pathsCollection);
if (files == null)
{
return 1;
}
DevNgPrebuildSettings settings = project.Settings();
BranchStatus branchStatus = settings.branchStatus[branchName];
if (files.Count > 0)
{
List<string> buildPaths = new List<string>();
AddBuildInfo(GetBuildInfoFromFiles(p4, buildPaths, files, branchStatus.lastChangelist, branchStatus.syncedChangelist));
}
//Don't need to do anymore if no outputFile
if (string.IsNullOrEmpty(outputFile))
{
return 0;
}
project.PostBuildMessage("Getting paths for: " + pathsCollection);
List<string> paths = GetBranchPaths(branchName, pathsCollection);
IList<Perforce.P4.FileSpec> specs = new List<Perforce.P4.FileSpec>(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<Perforce.P4.Changelist> 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;
}
/******************************************************************************************************************
** Get all the paths to sync for all branches, or a specific branch
******************************************************************************************************************/
List<string> GetBranchPaths(string singleBranch = "", string paths = "")
{
List<string> syncPaths = new List<string>();
if (!string.IsNullOrEmpty(singleBranch))
{
BranchSyncInfo branch;
switch (paths)
{
case "mpdlc":
if (!mpDlcBranchPaths.TryGetValue(singleBranch, out branch))
{
project.AddError("Failed to find paths for branch " + singleBranch + ", it doesn't appear to be setup");
project.PostBuildError("Failed to find paths branch " + singleBranch + ", it doesn't appear to be setup");
return null;
}
break;
case "spdlc":
if (!spDlcBranchPaths.TryGetValue(singleBranch, out branch))
{
project.AddError("Failed to find paths for branch " + singleBranch + ", it doesn't appear to be setup");
project.PostBuildError("Failed to find paths branch " + singleBranch + ", it doesn't appear to be setup");
return null;
}
break;
case "base":
default:
if (!branchPaths.TryGetValue(singleBranch, out branch))
{
project.AddError("Failed to find paths for branch " + singleBranch + ", it doesn't appear to be setup");
project.PostBuildError("Failed to find paths branch " + singleBranch + ", it doesn't appear to be setup");
return null;
}
break;
}
syncPaths = branch.syncPaths;
}
else
{
foreach (var dirs in branchPaths.Select(a => a.Value.syncPaths))
{
syncPaths.AddRange(dirs);
}
}
//a branch might reference the same paths as another branch, so remove duplicates
syncPaths = syncPaths.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
return syncPaths;
}
/******************************************************************************************************************
** Set the synced changelist number for all or a specified branch
******************************************************************************************************************/
void SetBranchVersion(int cl, string singleBranch = "")
{
DevNgPrebuildSettings settings = project.Settings();
if (!string.IsNullOrEmpty(singleBranch))
{
BranchStatus branch;
if (settings.branchStatus.TryGetValue(singleBranch, out branch))
{
branch.syncedChangelist = cl;
}
}
else
{
foreach (var pair in settings.branchStatus)
{
pair.Value.syncedChangelist = cl;
}
}
}
/******************************************************************************************************************
** Sync relevant paths for all or a single branch. Specify a changelist or it'll just use latest
******************************************************************************************************************/
new IList<Perforce.P4.FileSpec> SyncBranches(P4 p4, string singleBranch = "", int currentChangelist = -1, bool autoResolve = true, string pathCollection = "")
{
if (p4 == null)
{
p4 = SetupP4();
if (p4 == null)
{
return null;
}
}
List<string> syncPaths = GetBranchPaths(singleBranch, pathCollection);
if (syncPaths == null)
{
return null;
}
Perforce.P4.VersionSpec version = null;
if (currentChangelist == -1)
{
IList<Perforce.P4.Changelist> latest = p4.GetChangelists(1, Perforce.P4.ChangeListStatus.Submitted);
if (latest.Count > 0)
{
currentChangelist = latest[0].Id;
}
}
if (currentChangelist != -1)
{
version = new Perforce.P4.ChangelistIdVersion(currentChangelist);
SetBranchVersion(currentChangelist, singleBranch);
}
else
{
//Something has to have failed by here and it really shouldn't happen with the previous p4 checks happening but just in case, the show must go on
version = new P4API.HeadChangelist();
}
IList<Perforce.P4.FileSpec> syncFiles = new List<Perforce.P4.FileSpec>();
foreach (string path in syncPaths)
{
Perforce.P4.FileSpec root = p4.GetFileSpecFromPath(Path.Combine(p4root, path));
if (root == null)
{
project.AddError("P4: Problem finding root paths for " + path);
project.PostBuildError("P4: Problem finding root paths for " + path);
return null;
}
if (root.Version == null)
{
root.Version = version;
}
syncFiles.Add(root);
}
IList<Perforce.P4.FileSpec> files = new List<Perforce.P4.FileSpec>();
bool preview = project.builder.test;
if (nightbuild || package)
{
project.PostBuildMessage("Syncing...");
}
files = p4.Sync(syncFiles, preview: preview, autoResolve: autoResolve, clobberWritable: true);
return files;
}
/******************************************************************************************************************
**
******************************************************************************************************************/
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)
{
DevNgPrebuildSettings 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<string>() { user }, "Your PC lives.");
}
else if (oldState == IPInfo.State.Alive && newState == IPInfo.State.Dead)
{
project.PostSlackMessage(new List<string>() { 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<string>() { user }, "Your PC is currently alive.");
}
else if (oldState == IPInfo.State.Unknown && newState == IPInfo.State.Dead)
{
project.PostSlackMessage(new List<string>() { 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.");
}
DevNgPrebuildSettings 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();
{
OozyBuild.Email email = ((NomProject)project).CreateEmail();
email.To("andy.herd@rockstardundee.com");
email.subject = "GTAV DEV_NG Prebuild";
email.body = "A new Prebuild is deploying to Resilio and North Distro. \nExternal Studios should check the Resilio portal to ensure the package has finished transfering before deploying to console. Boot checks can be carried out and full prebuild mail will follow once packages are verified. \n\nNorth: \nN:/RSGEDI/Distribution/QA_Build/gta5/dev_ps4/pre-build/dev_ng \nN:/RSGEDI/Distribution/QA_Build/gta5/dev_xbone/PREBUILD/dev_ng \n\nAll Studios: \nN:/transfer/[STUDIO]/Resilio/gta5/packaged_builds/Dev_ng/PREBUILD";
email.Send();
}
}
else
{
project.StopBuildThreadError();
{
OozyBuild.Email email = ((NomProject)project).CreateEmail();
email.To("andy.herd@rockstardundee.com");
email.subject = "GTAV DEV_NG Prebuild Failed";
email.body = "The latest prebuild generation failed and will need investigated. This will delay the inital prebuild release.";
email.Send();
}
}
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;
DevNgPrebuildSettings 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)
{
DevNgPrebuildSettings 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<Perforce.P4.FileSpec> syncFiles = new List<Perforce.P4.FileSpec>();
IList<Perforce.P4.Changelist> latest = p4.GetChangelists(1, Perforce.P4.ChangeListStatus.Submitted);
List<BuildInfo> buildInfo = null;
List<string> buildPaths = new List<string>();
if (latest.Count > 0)
{
currentChangelist = latest[0].Id;
bool preview = project.builder.test;
IList<Perforce.P4.FileSpec> 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;
}
if (syncedFiles)
{
AddBuildInfo(buildInfo);
}
else
{
buildInfo = new List<BuildInfo>();
}
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<string> sendToUsers = new List<string>();
Stopwatch symbolTime = null;
string QALabel = "GTA5_DEV_NG_PREBUILD";
if (result.ranNormally)
{
if (nightbuild || package)
{
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();
}
}
else if (createPackage)
{
showReport = true;
}
}
else
{
if (DateTime.Now.DayOfWeek == DayOfWeek.Monday)
{
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";
}
}
else
{
if (package)
{
if (!string.IsNullOrEmpty(packageUser))
{
sendToUsers = new List<string>(1) { packageUser };
msg += "`Sorry, package failed, no new build for you yet.`";
}
}
else if (nightbuild)
{
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<SlackMessageResponse> 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;
}
}
}