Files
2025-09-29 00:52:08 +02:00

866 lines
31 KiB
Plaintext
Executable File

using P4API;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media;
using System.Xml.Serialization;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Runtime.CompilerServices;
using System.Diagnostics;
namespace OozyBuild
{
/******************************************************************************************************************
**
******************************************************************************************************************/
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 class BranchStatus
{
public int lastChangelist;
public int syncedChangelist;
public List<BuildInfo> current = new List<BuildInfo>();
public List<BuildInfo> sinceNightbuild = new List<BuildInfo>();
}
/******************************************************************************************************************
**
******************************************************************************************************************/
public class BranchSyncInfo
{
public bool storeChanges;
public List<string> syncPaths = new List<string>();
}
/******************************************************************************************************************
**
******************************************************************************************************************/
public class GTA5Extension
{
public Project project;
public P4 currentP4;
public string p4root;
public bool nightbuild;
public bool package;
protected virtual Dictionary<string, BranchSyncInfo> branchPaths { get; set; }
/******************************************************************************************************************
**
******************************************************************************************************************/
public GTA5Extension()
{
}
/******************************************************************************************************************
** Sets up an instance of p4
******************************************************************************************************************/
protected P4 SetupP4([CallerFilePath] string scriptFile = @"RSG.Leigh.Bird.Oozy.unknown" )
{
P4 p4 = currentP4;
if( p4 != null )
{
if( p4.rep != null && p4.rep.Connection != null && p4.rep.Connection.connectionEstablished() )
{
try
{
p4.rep.Connection.getP4Server().RunCommand( "help", 0, false, null, 0 );
return p4;
}
catch( Exception )
{
}
try
{
currentP4 = null;
p4.Disconnect();
}
catch( Exception )
{
}
}
}
string ini = project.p4IniFile;
if( !File.Exists( ini ) )
{
project.AddLog( "p4ini \"" + ini + "\" not found, aborting SetupP4" );
return null;
}
p4 = new P4( ini );
if( !string.IsNullOrEmpty( scriptFile ) )
{
p4.ProgramName = @"RSG.Leigh.Bird.Oozy." + Path.GetFileNameWithoutExtension( scriptFile );
p4.ProgramVersion = "1.0.0";
}
project.AddLog( "Connecting to " + p4.Port );
p4.OnConnected += ( P4 p ) =>
{
project.AddLog( "Connected" );
project.SetupSCCOutput( p4 );
};
if( !p4.Connect() )
{
project.AddError( "P4: Failed to connect host: " + p4.Port + ", user: " + p4.User + ", workspace: " + p4.Workspace );
return null;
}
p4root = p4.Root();
if( string.IsNullOrEmpty( p4root ) )
{
Console.WriteLine( "p4 not connected propery, will try again later" );
return null;
}
currentP4 = p4;
return p4;
}
/******************************************************************************************************************
**
******************************************************************************************************************/
protected BuildChange GetBuildInfo( Perforce.P4.Changelist changelist, List<BuildInfo> buildInfo, List<string> users )
{
string user = changelist.OwnerName;
BuildInfo build = buildInfo.FirstOrDefault( b => string.Compare( b.userName, user, true ) == 0 );
if( build == null )
{
build = new BuildInfo();
build.userName = user;
buildInfo.Add( build );
users.Add( user );
}
BuildChange bc = new BuildChange();
bc.id = changelist.Id;
bc.description = changelist.Description;
build.changes.Add( bc );
return bc;
}
/******************************************************************************************************************
**
******************************************************************************************************************/
protected bool SetupUserInfo( P4 p4, List<BuildInfo> buildInfo, List<string> users )
{
if( users.Count > 0 )
{
var p4Users = p4.Users( users );
foreach( var user in p4Users )
{
BuildInfo info = buildInfo.FirstOrDefault( a => a.userName == user.Id );
if( info != null )
{
info.email = user.EmailAddress;
info.fullName = user.FullName;
}
}
}
return true;
}
/******************************************************************************************************************
**
******************************************************************************************************************/
protected void RemoveProjGen( P4 p4, bool submit )
{
Perforce.P4.Changelist pgChangelist = p4.FindChangelist( "ProjectGenerator v3.2.0.0" );
if( pgChangelist != null )
{
p4.RevertUnchangedFiles( pgChangelist.Id );
List<Perforce.P4.FileMetaData> clFiles = p4.GetFilesInChangelist( pgChangelist.Id );
if( clFiles.Count > 0 )
{
if( submit )
{
p4.Submit( pgChangelist );
}
else
{
p4.Revert( clFiles.ToFileSpecs() );
}
}
}
}
/******************************************************************************************************************
** Given (depot) files, lastChangelist and currentChangelist, generate a list of local paths and return a list of user/changelist/file information
** Much simpler to go from last CL to latest CL, but with auto builders constantly checking in on multiple projects that becomes really bad
** to managed.
** So go through each synced file and get its history between last and current cl and work out changelists from there
******************************************************************************************************************/
protected List<BuildInfo> GetBuildInfoFromFiles( P4 p4, List<string> buildPaths, IList<Perforce.P4.FileSpec> files, int lastChangelist, int currentChangelist )
{
List<BuildInfo> buildInfo = new List<BuildInfo>();
List<string> users = new List<string>();
project.AddLog( "Synced:" );
foreach( Perforce.P4.FileSpec file in files )
{
project.AddLog( "\t" + file.DepotPath.Path );
}
Perforce.P4.VersionSpec changelistVersions = new ChangelistRange( lastChangelist + 1, currentChangelist );
buildPaths.AddRange( files.Select( a => a.LocalPath.Path ) );
Dictionary<int, List<Perforce.P4.FileSpec>> checkedIn = new Dictionary<int, List<Perforce.P4.FileSpec>>();
Dictionary<Perforce.P4.FileSpec, List<Perforce.P4.FileHistory>> histories = p4.FileLog( files, changelistVersions );
Dictionary<string, bool> foundClsFor = new Dictionary<string, bool>();
foreach( var pair in histories )
{
foundClsFor[pair.Key.DepotPath.Path] = true;
foreach( var history in pair.Value )
{
//should never be false due to the changelist range
if( (history.ChangelistId > lastChangelist && history.ChangelistId <= currentChangelist)
)
{
List<Perforce.P4.FileSpec> clFiles;
if( !checkedIn.TryGetValue( history.ChangelistId, out clFiles ) )
{
clFiles = new List<Perforce.P4.FileSpec>();
checkedIn[history.ChangelistId] = clFiles;
}
clFiles.Add( new Perforce.P4.FileSpec( new Perforce.P4.DepotPath( pair.Key.DepotPath.Path ), changelistVersions ) );
}
}
}
List<Perforce.P4.FileSpec> extraFiles = new List<Perforce.P4.FileSpec>();
foreach( var fs in files )
{
bool done;
if( !foundClsFor.TryGetValue( fs.DepotPath.Path, out done ) )
{
foundClsFor[fs.DepotPath.Path] = true;
extraFiles.Add( fs );
}
}
if( extraFiles.Count > 0 )
{
IList<Perforce.P4.FileMetaData> metas = p4.FStat( extraFiles );
if( metas.Count > 0 )
{
foreach( var meta in metas )
{
List<Perforce.P4.FileSpec> clFiles;
if( !checkedIn.TryGetValue( meta.HeadChange, out clFiles ) )
{
clFiles = new List<Perforce.P4.FileSpec>();
checkedIn[meta.HeadChange] = clFiles;
}
clFiles.Add( new Perforce.P4.FileSpec( new Perforce.P4.DepotPath( Perforce.P4.PathSpec.EscapePath( meta.DepotPath.Path ) ), new Perforce.P4.ChangelistIdVersion( currentChangelist ) ) );
}
}
}
if( checkedIn.Count > 0 )
{
//Same file can exist in multiple checkins, so remove duplicates
IList<Perforce.P4.FileSpec> clsToGet = checkedIn.Select( a => a.Value[0] ).Distinct().ToList();
IList<Perforce.P4.Changelist> changes = p4.GetChangelistsAtRevision( clsToGet, 0 ).GroupBy( a => a.Id ).Select( b => b.First() ).ToList();
foreach( var change in changes )
{
List<Perforce.P4.FileSpec> missedFiles = new List<Perforce.P4.FileSpec>();
BuildChange bc = GetBuildInfo( change, buildInfo, users );
List<Perforce.P4.FileMetaData> clFiles = p4.GetFilesInChangelist( change.Id );
foreach( Perforce.P4.FileMetaData spec in clFiles )
{
Perforce.P4.FileSpec file = files.FirstOrDefault( a => a.DepotPath.Path == spec.DepotPath.Path );
if( file != null )
{
bc.files.Add( file.LocalPath.Path );
}
else
{
Console.WriteLine( spec.DepotPath.Path + " in CL " + change.Id + " but not synced" );
missedFiles.Add( new Perforce.P4.FileSpec( spec.DepotPath, new Perforce.P4.Revision( spec.HeadRev ) ) );
}
}
if( missedFiles.Count > 0 )
{
IList<Perforce.P4.FileSpec> missed = p4.Where( missedFiles );
if( !missed.Empty() )
{
foreach( var spec in missed )
{
bc.files.Add( spec.LocalPath.Path );
files.Add( spec );
}
}
}
}
}
if( !SetupUserInfo( p4, buildInfo, users ) )
{
return null;
}
return buildInfo;
}
/******************************************************************************************************************
** Get all the paths to sync for all branches, or a specific branch
******************************************************************************************************************/
protected List<string> GetBranchPaths( string singleBranch = "" )
{
List<string> syncPaths = new List<string>();
if( !string.IsNullOrEmpty( singleBranch ) )
{
BranchSyncInfo branch;
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;
}
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 = "" )
{
SerializableDictionary<string, BranchStatus> branchStatuses = (SerializableDictionary<string, BranchStatus>)project.customSettings["branchStatus"];
if( branchStatuses != null )
{
if( !string.IsNullOrEmpty( singleBranch ) )
{
BranchStatus branch;
if( branchStatuses.TryGetValue( singleBranch, out branch ) )
{
branch.lastChangelist = branch.syncedChangelist;
branch.syncedChangelist = cl;
}
}
else
{
foreach( var pair in branchStatuses )
{
pair.Value.lastChangelist = pair.Value.syncedChangelist;
pair.Value.syncedChangelist = cl;
}
}
}
}
/******************************************************************************************************************
** Sync relevant paths for all or a single branch. Specify a changelist or it'll just use latest
******************************************************************************************************************/
protected IList<Perforce.P4.FileSpec> SyncBranches( P4 p4, string singleBranch = "", int currentChangelist = -1, bool autoResolve = true, string labelName = "" )
{
if( p4 == null )
{
p4 = SetupP4();
if( p4 == null )
{
return null;
}
}
List<string> syncPaths = GetBranchPaths( singleBranch );
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>();
IList<Perforce.P4.FileSpec> tagFiles = 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( !string.IsNullOrEmpty( labelName ) )
{
Perforce.P4.FileSpec tagSpec = new Perforce.P4.FileSpec( root );
if( root.Version == null )
{
tagSpec.Version = version;
}
else
{
tagSpec.Version = root.Version;
root.Version = new Perforce.P4.LabelNameVersion( labelName );
}
tagFiles.Add( tagSpec );
}
if( root.Version == null )
{
root.Version = version;
}
syncFiles.Add( root );
}
bool preview = project.builder.test;
if( !string.IsNullOrEmpty( labelName ) )
{
p4.Tag( labelName, tagFiles, preview: preview );
}
IList<Perforce.P4.FileSpec> files = new List<Perforce.P4.FileSpec>();
if( nightbuild || package )
{
project.PostBuildMessage( "Syncing..." );
}
#if true
files = p4.Sync( syncFiles, preview: preview, autoResolve: autoResolve, clobberWritable: true );
#else
{
//handy test code
string tstFile = @"//depot/gta5/src/dev_gen9_sga/game/script/commands_debug.cpp";
files.Add( p4.Where( tstFile ) );
files[0].Version = new Perforce.P4.Revision( 8 );
}
#endif
return files;
}
/******************************************************************************************************************
**
******************************************************************************************************************/
public int StartProcess( string fullPathToExe )
{
project.AddLog( "Starting Process: " + fullPathToExe );
string exeName = Path.GetFileNameWithoutExtension( fullPathToExe );
project.AddLog( "Checking if any process with name '" + exeName + "' is running..." );
bool isRunning = Process.GetProcessesByName( exeName ).Any();
if( isRunning )
{
project.AddLog( "Already running!" );
return 0;
}
project.AddLog( "Not currently running - Starting " + fullPathToExe + "..." );
Process.Start( fullPathToExe );
return 0;
}
/******************************************************************************************************************
** Called from .nom, parses the output from a batch file sync, hopefully deprecated but useful as a ref.
******************************************************************************************************************/
protected List<string> buildPaths;
protected List<BuildInfo> buildInfo;
public static bool HasProperty( dynamic settings, string name )
{
return settings.GetType().GetProperty( name ) != null || settings.GetType().GetField( name ) != null;
}
/******************************************************************************************************************
**
******************************************************************************************************************/
public int ParseCLInfo( string branchName )
{
dynamic settings = project.customSettings;
if( !HasProperty( settings, "branchStatus" ) )
{
Console.WriteLine( "Settings " + project.customSettings.GetType().Name + " doesn't have a property 'SerializableDictionary<string, BranchStatus> branchStatus { get; set; }'" );
return 1;
}
SerializableDictionary<string, BranchStatus> branchStatus = settings.branchStatus;
BranchStatus status;
if( !branchStatus.TryGetValue( branchName, out status ) )
{
Console.WriteLine( "Failed to find branch \"" + branchName + "\"" );
return 1;
}
P4 p4 = SetupP4();
if( p4 == null )
{
return 1;
}
Console.WriteLine( "Looking for BuildEntrty for \"Sync Latest Data for " + branchName + "\"" );
BuildEntry entry = project.builder.allBuilds.FirstOrDefault( a => a.displayName == "Sync Latest Data for " + branchName );
if( entry == null )
{
Console.WriteLine( "Failed" );
return 1;
}
List<string> log = entry.log.ToList();
IList<Perforce.P4.FileSpec> files = new List<Perforce.P4.FileSpec>( log.Count );
int clId = 0;
int currentChangelist = -1;
foreach( string file in log )
{
if( clId == 0 )
{
Match cl = Regex.Match( file, @"Syncing Latest Data to\s*(?<cl>[\d]+)", RegexOptions.IgnoreCase );
if( cl.Success )
{
if( int.TryParse( cl.Groups["cl"].Value, out clId ) && clId > 0 )
{
currentChangelist = branchStatus[branchName].syncedChangelist = clId;
Console.WriteLine( "Parsed currentChangelist=" + currentChangelist );
}
}
}
Match match = Regex.Match( file, @"^(?<depotPath>[\s\S]+)#(?<rev>\d+)\s*-\s*\w+\s*(?<localPath>[\s\S]+)" );
if( match.Success )
{
string localPath = match.Groups["localPath"].Value;
if( localPath.StartsWith( p4root, StringComparison.OrdinalIgnoreCase ) )
{
Perforce.P4.FileSpec fileSpec = new Perforce.P4.FileSpec();
fileSpec.DepotPath = new Perforce.P4.DepotPath( match.Groups["depotPath"].Value );
fileSpec.LocalPath = new Perforce.P4.LocalPath( localPath );
files.Add( fileSpec );
}
}
}
if( currentChangelist == -1 )
{
Console.WriteLine( "Failed to determine the changelist from the log" );
return 1;
}
status.syncedChangelist = currentChangelist;
if( !files.Empty() )
{
buildPaths = new List<string>();
buildInfo = GetBuildInfoFromFiles( p4, buildPaths, files, status.lastChangelist, currentChangelist );
AddBuildInfo( buildInfo );
}
return 0;
}
/******************************************************************************************************************
**
******************************************************************************************************************/
public void AddBuildInfo( StringBuilder sb, List<BuildInfo> changes, List<BuildInfo> excludes = null )
{
foreach( BuildInfo info in changes )
{
foreach( BuildChange change in info.changes )
{
if( excludes != null )
{
if( excludes.Exists( a => a.changes.Exists( b => b.id == change.id ) ) )
{
continue;
}
}
string[] descriptionLines = change.description.Replace( "\r\n", "\n" ).Split( '\n' );
string description = "";
foreach( string line in descriptionLines )
{
description += "\t" + line + "\n";
}
sb.Append( "**************************************************\n" + change.id.ToString() + " " + info.fullName + "\n" + description + "\n" );
}
}
}
/******************************************************************************************************************
**
******************************************************************************************************************/
public void AddBugstarInfo( StringBuilder sb, List<BuildInfo> changes, List<BuildInfo> excludes = null )
{
foreach( BuildInfo info in changes )
{
foreach( BuildChange change in info.changes )
{
if( excludes != null )
{
if( excludes.Exists( a => a.changes.Exists( b => b.id == change.id ) ) )
{
continue;
}
}
foreach( Match m in Regex.Matches( change.description, @"bugstar\s*:\s*(?<bugstar>\s*\d+)", RegexOptions.IgnoreCase ) )
{
if( m.Success )
{
sb.Append( "url:bugstar:" + m.Groups["bugstar"].Value + " - " + info.fullName + "\n" );
}
}
}
}
}
/******************************************************************************************************************
**
******************************************************************************************************************/
public void AddBuildInfo( List<BuildInfo> current )
{
dynamic settings = project.customSettings;
if( !HasProperty( settings, "branchStatus" ) )
{
Console.WriteLine( "Settings " + project.customSettings.GetType().Name + " doesn't have a property 'SerializableDictionary<string, BranchStatus> branchStatus { get; set; }'" );
}
SerializableDictionary<string, BranchStatus> branchStatus = settings.branchStatus;
//go through known branches and their sync paths
foreach( var pair in branchPaths )
{
string branchName = pair.Key;
if( !pair.Value.storeChanges )
{
//flag may have changed, ensure old information is cleared
branchStatus[branchName].current.Clear();
branchStatus[branchName].sinceNightbuild.Clear();
continue;
}
BranchStatus status = branchStatus[branchName];
List<BuildInfo> currentInfos = status.current;
List<BuildInfo> nightbuildInfos = status.sinceNightbuild;
foreach( string p4Path in pair.Value.syncPaths )
{
string path = Path.Combine( p4root, p4Path ).Replace( "...", "*" ).Replace( Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar );
path = Program.WildCardToRegular( path );
//go through the changelist info and check each related file to see if is relevant to this branch
foreach( BuildInfo buildInfo in current )
{
BuildInfo currentInfo = currentInfos.FirstOrDefault( a => a.userName == buildInfo.userName );
BuildInfo nightbuildInfo = nightbuildInfos.FirstOrDefault( a => a.userName == buildInfo.userName );
foreach( BuildChange buildChange in buildInfo.changes )
{
foreach( string file in buildChange.files )
{
if( Regex.IsMatch( file, path, RegexOptions.IgnoreCase ) )
{
//Have a match
//add a BuildInfo if it doesn't already exist
if( currentInfo == null )
{
currentInfo = new BuildInfo() { userName = buildInfo.userName, email = buildInfo.email, fullName = buildInfo.fullName };
currentInfos.Add( currentInfo );
}
if( nightbuildInfo == null )
{
nightbuildInfo = new BuildInfo() { userName = buildInfo.userName, email = buildInfo.email, fullName = buildInfo.fullName };
nightbuildInfos.Add( nightbuildInfo );
}
//As we are going through files, it's possible to get the same CL more than once
//Create a new version of BuildChange containing minimal info, don't need the list of files
BuildChange storedBuildChange = new BuildChange() { id = buildChange.id, description = buildChange.description };
if( !currentInfo.changes.Exists( a => a.id == storedBuildChange.id ) )
{
currentInfo.changes.Add( storedBuildChange );
}
if( !nightbuildInfo.changes.Exists( a => a.id == storedBuildChange.id ) )
{
nightbuildInfo.changes.Add( storedBuildChange );
}
}
}
}
}
}
}
}
/******************************************************************************************************************
** Writes out bugs.log and changes.log to show changes since the last build
******************************************************************************************************************/
public int WriteCLInfo( string branchName, string outDir )
{
//Generate bugs.log and changes.log
dynamic settings = project.customSettings;
if( !HasProperty( settings, "branchStatus" ) )
{
Console.WriteLine( "Settings " + project.customSettings.GetType().Name + " doesn't have a property 'SerializableDictionary<string, BranchStatus> branchStatus { get; set; }'" );
}
SerializableDictionary<string, BranchStatus> branchStatus = settings.branchStatus;
StringBuilder sb = new StringBuilder();
StringBuilder changes = new StringBuilder();
StringBuilder bugs = new StringBuilder();
outDir = Path.Combine( project.enginePath, outDir );
if( nightbuild )
{
project.PostBuildMessage( branchName + "has " + branchStatus[branchName].sinceNightbuild.Count + " changelists since last nightbuild" );
changes.Append( "Changelist since last nightbuild:\n" );
AddBuildInfo( changes, branchStatus[branchName].sinceNightbuild );
bugs.Append( "Bugs since last nightbuild:\n" );
AddBugstarInfo( bugs, branchStatus[branchName].sinceNightbuild );
sb.Append( bugs );
sb.Append( changes );
}
else if( package )
{
project.PostBuildMessage( branchName + "has " + branchStatus[branchName].current.Count + " changelists since last package, and there are " + branchStatus[branchName].sinceNightbuild.Count + " changelists since last nightbuild" );
changes.Append( "Changelist since last package:\n" );
AddBuildInfo( changes, branchStatus[branchName].current );
changes.Append( "\nChangelist since last nightbuild:\n" );
AddBuildInfo( changes, branchStatus[branchName].sinceNightbuild, branchStatus[branchName].current );
bugs.Append( "Bugs since last package:\n" );
AddBugstarInfo( bugs, branchStatus[branchName].current );
bugs.Append( "\nBugs since last nightbuild:\n" );
AddBugstarInfo( bugs, branchStatus[branchName].sinceNightbuild, branchStatus[branchName].current );
sb.Append( bugs );
sb.Append( changes );
}
else
{
sb.Append( "No idea what's gone on here. Hopefully I'll never see this message" );
}
string bugsFile = Path.Combine( outDir, "bugs.log" );
Util.MakeDirectory( Path.GetDirectoryName( bugsFile ) );
try
{
File.WriteAllText( bugsFile, bugs.ToString() );
}
catch( Exception ex )
{
sb.Append( "Failed to write to " + bugsFile + "\n" + App.LogException( ex ) );
}
string changesFile = Path.Combine( outDir, "changes.log" );
Util.MakeDirectory( Path.GetDirectoryName( changesFile ) );
try
{
File.WriteAllText( changesFile, changes.ToString() );
}
catch( Exception ex )
{
sb.Append( "Failed to write to " + changesFile + "\n" + App.LogException( ex ) );
}
project.AddLog( sb.ToString() );
return 0;
}
/******************************************************************************************************************
** OUTPUT ERROR CAPTURING
******************************************************************************************************************/
public string GetProjgenErrors( string log, int maxErrors )
{
string[] lines = log.Split( new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries );
int errors = 0;
int endLine = -1;
int startLine = -1;
for( int i = lines.Length - 1; i >= 0; --i )
{
string line = lines[i];
if( endLine == -1 )
{
if( line.Contains( "[error]" ) )
{
endLine = i;
continue;
}
}
else if( startLine == -1 )
{
if( !line.Contains( "[error]" ) )
{
startLine = i + 1;
break;
}
}
}
StringBuilder sb = new StringBuilder( 1024 );
for( int i = startLine; i < endLine && i < lines.Length && (maxErrors <= 0 || errors < maxErrors); ++i, ++errors )
{
sb.Append( lines[i] );
sb.Append( "\r\n" );
}
return sb.ToString();
}
public string GetSlnErrors( string log, int maxErrors )
{
return NomProject.CaptureSlnErrors( log.Split( new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries ), maxErrors );
}
}
}