public override void ExecuteBuild() { var CommandParam = ParseParamValue("CommandParam", "//depot/UE4-LauncherReleases/*/Source/...@2091742,2091950 //depot/UE4-LauncherReleases/*/Build/...@2091742,2091950"); { List <P4Connection.ChangeRecord> ChangeRecords; if (!P4.Changes(out ChangeRecords, CommandParam, true, true, LongComment: true)) { throw new AutomationException("failed"); } foreach (var Record in ChangeRecords) { LogInformation("{0} {1} {2}", Record.CL, Record.UserEmail, Record.Summary); } } { List <P4Connection.ChangeRecord> ChangeRecords; if (!P4.Changes(out ChangeRecords, "-L " + CommandParam, true, true, LongComment: false)) { throw new AutomationException("failed"); } foreach (var Record in ChangeRecords) { LogInformation("{0} {1} {2}", Record.CL, Record.UserEmail, Record.Summary); } } }
private int GetLatestCodeChange() { List <P4Connection.ChangeRecord> ChangeRecords; if (!P4.Changes(out ChangeRecords, String.Format("-m 1 //{0}/....cpp@<{1} //{0}/....h@<{1} //{0}/....cs@<{1} //{0}/....usf@<{1} //{0}/....ush@<{1}", P4Env.Client, P4Env.Changelist), WithClient: true)) { throw new AutomationException("Couldn't enumerate latest change from branch"); } return(ChangeRecords.Max(x => x.CL)); }
/// <summary> /// Execute the command /// </summary> public override void ExecuteBuild() { // Get the source and target streams string Stream = ParseParamValue("Stream", P4Env.BuildRootP4); string Changes = ParseParamValue("Changes", null); string Lockdown = ParseParamValue("Lockdown", "Nick.Penwarden"); // Get changes which haven't been copied up from the current stream string Descriptions; string SourceStream; int LastCl; if (Changes == null) { IProcessResult Result = P4.P4(String.Format("interchanges -l -S {0}", Stream), AllowSpew: false); Descriptions = Result.Output.Replace("\r\n", "\n"); SourceStream = Stream; // Get the last submitted change in the source stream List <P4Connection.ChangeRecord> ChangeRecords; if (!P4.Changes(out ChangeRecords, String.Format("-m1 {0}/...", SourceStream), AllowSpew: false)) { throw new AutomationException("Couldn't get changes for this branch"); } LastCl = ChangeRecords[0].CL; } else { IProcessResult Result = P4.P4(String.Format("changes -l {0}", Changes), AllowSpew: false); Descriptions = Result.Output.Replace("\r\n", "\n"); SourceStream = Regex.Replace(Changes, @"(\/(?:\/[^\/]*){2}).*", "$1"); LastCl = Int32.Parse(Regex.Replace(Changes, ".*,", "")); } // Clean any workspace names that may reveal internal information Descriptions = Regex.Replace(Descriptions, "(Change[^@]*)@.*", "$1", RegexOptions.Multiline); // Remove changes by the build machine Descriptions = Regex.Replace(Descriptions, "[^\n]*buildmachine\n(\n|\t[^\n]*\n)*", ""); // Figure out the target stream IProcessResult StreamResult = P4.P4(String.Format("stream -o {0}", Stream), AllowSpew: false); if (StreamResult.ExitCode != 0) { throw new AutomationException("Couldn't get stream description for {0}", Stream); } string Target = P4Spec.FromString(StreamResult.Output).GetField("Parent"); if (Target == null) { throw new AutomationException("Couldn't get parent stream for {0}", Stream); } // Write the output file string OutputDirName = Path.Combine(CommandUtils.CmdEnv.LocalRoot, "Engine", "Intermediate"); CommandUtils.CreateDirectory(OutputDirName); string OutputFileName = Path.Combine(OutputDirName, "Changes.txt"); using (StreamWriter Writer = new StreamWriter(OutputFileName)) { Writer.WriteLine("Copying {0} to {1} (Source: {2} @ {3})", Stream, Target.Trim(), SourceStream, LastCl); Writer.WriteLine("#lockdown {0}", Lockdown); Writer.WriteLine(); Writer.WriteLine("=========================="); Writer.WriteLine("MAJOR FEATURES + CHANGES"); Writer.WriteLine("=========================="); Writer.WriteLine(); foreach (string Line in Descriptions.Split('\n')) { string TrimLine = Line.TrimStart(); if (!TrimLine.StartsWith("#codereview", StringComparison.OrdinalIgnoreCase) && !TrimLine.StartsWith("#rb", StringComparison.OrdinalIgnoreCase) && !TrimLine.StartsWith("#lockdown", StringComparison.OrdinalIgnoreCase)) { Writer.WriteLine(Line); } } } Log("Written {0}.", OutputFileName); // Open it with the default text editor Process.Start(OutputFileName); }
/// <summary> /// Execute the command /// </summary> public override void ExecuteBuild() { // Get the source and target streams string Stream = ParseParamValue("Stream", P4Env.BuildRootP4); string Changes = ParseParamValue("Changes", null); string Lockdown = ParseParamValue("Lockdown", "Nick.Penwarden"); bool bPlatformSplit = !ParseParam("CombinePlatforms"); // Get changes which haven't been copied up from the current stream string AllDescriptions; string SourceStream; string VerbosityFlag = bPlatformSplit ? "" : "-l"; int LastCl; string[] Platforms = GetPlatformNames(); StringBuilder[] DescriptionBuilders = new StringBuilder[Platforms.Length]; for (int Index = 0; Index < Platforms.Length; Index++) { DescriptionBuilders[Index] = new StringBuilder(); } if (Changes == null) { IProcessResult Result = P4.P4(String.Format("interchanges {0} -S {1}", VerbosityFlag, Stream), AllowSpew: false); AllDescriptions = Result.Output; SourceStream = Stream; // Get the last submitted change in the source stream List <P4Connection.ChangeRecord> ChangeRecords; if (!P4.Changes(out ChangeRecords, String.Format("-m1 {0}/...", SourceStream), AllowSpew: false)) { throw new AutomationException("Couldn't get changes for this branch"); } LastCl = ChangeRecords[0].CL; } else { IProcessResult Result = P4.P4(String.Format("changes {0} {1}", VerbosityFlag, Changes), AllowSpew: false); AllDescriptions = Result.Output; SourceStream = Regex.Replace(Changes, @"(\/(?:\/[^\/]*){2}).*", "$1"); LastCl = Int32.Parse(Regex.Replace(Changes, ".*,", "")); } if (bPlatformSplit) { string[] Lines = AllDescriptions.Split("\n".ToCharArray()); foreach (string Line in Lines) { // @todo replace with regexes!! string[] Tokens = Line.Split(" ".ToCharArray()); if (Tokens.Length > 2 && Tokens[0] == "Change") { IProcessResult Result = P4.P4(String.Format("describe -s {0}", Tokens[1]), AllowSpew: false); // Affected files ... is the splitting point int AffectedFilesPos = Result.Output.IndexOf("Affected files ..."); string Description = Result.Output.Substring(0, AffectedFilesPos); string Files = Result.Output.Substring(AffectedFilesPos); // look for the NDA platforms in the list of files (skipping over the "no" and "multi" platforms int WhichPlatform = 0; for (int Index = 2; Index < Platforms.Length; Index++) { // we search by directory in the form of /Platform/ if (Files.Contains("/" + Platforms[Index] + "/")) { // if we contained multiple files, then we put into the Multi file, and someone will have to manually deal with it!! if (WhichPlatform == 0) { WhichPlatform = Index; } else { WhichPlatform = 1; } } } // add this description to the proper platform DescriptionBuilders[WhichPlatform].AppendLine(Description); } } } else { DescriptionBuilders[0].Append(AllDescriptions); } for (int PlatformIndex = 0; PlatformIndex < Platforms.Length; PlatformIndex++) { string Desc = DescriptionBuilders[PlatformIndex].ToString().Replace("\r\n", "\n"); // Clean any workspace names that may reveal internal information Desc = Regex.Replace(Desc, "(Change[^@]*)@.*", "$1", RegexOptions.Multiline); // Remove changes by the build machine Desc = Regex.Replace(Desc, "[^\n]*buildmachine\n(\n|\t[^\n]*\n)*", ""); // Remove all the tags we don't care about Desc = Regex.Replace(Desc, "^[ \t]*#(rb|fyi|codereview|lockdown)\\s.*$", "", RegexOptions.Multiline); // Empty out lines which just contain whitespace Desc = Regex.Replace(Desc, "^[ \t]+$", "", RegexOptions.Multiline); // Remove multiple consecutive blank lines Desc = Regex.Replace(Desc, "\n\n+", "\n\n"); // Only include one newline at the end of each description Desc = Regex.Replace(Desc, "\n+Change", "\n\nChange"); // Remove merge-only changelists Desc = Regex.Replace(Desc, "(?<=(^|\\n))Change .*\\s*Merging .* to .*\\s*\\n(?=(Change|$))", ""); if (string.IsNullOrEmpty(Desc)) { continue; } // Figure out the target stream IProcessResult StreamResult = P4.P4(String.Format("stream -o {0}", Stream), AllowSpew: false); if (StreamResult.ExitCode != 0) { throw new AutomationException("Couldn't get stream description for {0}", Stream); } string Target = P4Spec.FromString(StreamResult.Output).GetField("Parent"); if (Target == null) { throw new AutomationException("Couldn't get parent stream for {0}", Stream); } // Write the output file string OutputDirName = Path.Combine(CommandUtils.CmdEnv.LocalRoot, "Engine", "Intermediate"); CommandUtils.CreateDirectory(OutputDirName); string OutputFileName = Path.Combine(OutputDirName, string.Format("Changes{0}.txt", Platforms[PlatformIndex])); using (StreamWriter Writer = new StreamWriter(OutputFileName)) { if (PlatformIndex == 1) { Writer.WriteLine("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); Writer.WriteLine("CHANGES WITH MULTIPLE PLATFORMS!!! YOU MUST COPY THESE INTO THE OTHER ONES AS MAKES SENSE!!"); Writer.WriteLine("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); Writer.WriteLine(); Writer.WriteLine(); } else { Writer.WriteLine("Copying {0} to {1} (Source: {2} @ {3})", Stream, Target.Trim(), SourceStream, LastCl); Writer.WriteLine("#lockdown {0}", Lockdown); Writer.WriteLine(); Writer.WriteLine("====================================="); Writer.WriteLine("{0} MAJOR FEATURES + CHANGES", Platforms[PlatformIndex]); Writer.WriteLine("====================================="); Writer.WriteLine(); } foreach (string Line in Desc.Split('\n')) { Writer.WriteLine(Line); } Writer.WriteLine("DONE!"); } Log("Written {0}.", OutputFileName); // Open it with the default text editor Process.Start(OutputFileName); } }
public override ExitCode Execute() { LogInformation("************************* SyncProject"); // These are files that should always be synced because tools update them string[] ForceSyncFiles = new string[] { "Engine/Build/Build.version", "Engine/Source/Programs/DotNETCommon/MetaData.cs" }; // Parse the project filename (as a local path) string ProjectArg = ParseParamValue("Project", null); // Parse the changelist to sync to. -1 will sync to head. int CL = ParseParamInt("CL", -1); int Threads = ParseParamInt("threads", 2); bool ForceSync = ParseParam("force"); bool PreviewOnly = ParseParam("preview"); bool ProjectOnly = ParseParam("projectonly"); int Retries = ParseParamInt("retries", 3); bool Unversioned = ParseParam("unversioned"); bool BuildProject = ParseParam("build"); bool OpenProject = ParseParam("open"); bool GenerateProject = ParseParam("generate"); string PathArg = ParseParamValue("paths", ""); IEnumerable <string> ExplicitPaths = PathArg.Split(new char[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries).Select(S => S.Trim()); if (CL == 0) { if (!ForceSync) { throw new AutomationException("If using 0 CL to remove files -force must also be specified"); } // Cannot unsync the engine folder ProjectOnly = true; } else if (CL == -1) { // find most recent change List <P4Connection.ChangeRecord> Records; string Cmd = string.Format("-s submitted -m1 //{0}/...", P4Env.Client); if (!P4.Changes(out Records, Cmd, false)) { throw new AutomationException("p4 changes failed. Cannot find most recent CL"); } CL = Records.First().CL; } bool EngineOnly = string.IsNullOrEmpty(ProjectArg); // Will be the full local path to the project file once resolved FileReference ProjectFile = null; // Will be the full workspace path to the project in P4 (if a project was specified) string P4ProjectPath = null; // Will be the full workspace path to the engine in P4 (If projectonly wasn't specified); string P4EnginePath = null; // If we're syncing a project find where it is in P4 if (!EngineOnly) { ProjectFile = ProjectUtils.FindProjectFileFromName(ProjectArg); if (ProjectFile != null) { // get the path in P4 P4WhereRecord Record = P4.Where(ProjectFile.FullName, AllowSpew: false).FirstOrDefault(x => x.DepotFile != null && !x.bUnmap); P4ProjectPath = Record.ClientFile; } else { // if they provided a name and not a path then find the file (requires that it's synced). string RelativePath = ProjectArg; if (Path.GetExtension(RelativePath).Length == 0) { RelativePath = Path.ChangeExtension(RelativePath, "uproject"); } if (!RelativePath.Contains(Path.DirectorySeparatorChar) && !RelativePath.Contains(Path.AltDirectorySeparatorChar)) { RelativePath = CommandUtils.CombinePaths(PathSeparator.Slash, Path.GetFileNameWithoutExtension(RelativePath), RelativePath); } Log.TraceInformation("{0} not on disk. Searching P4 for {1}", ProjectArg, RelativePath); List <string> SearchPaths = new List <string>(); SearchPaths.Add(""); string ProjectDirsFile = Directory.EnumerateFiles(CommandUtils.CombinePaths(CmdEnv.LocalRoot), "*.uprojectdirs").FirstOrDefault(); if (ProjectDirsFile != null) { foreach (string FilePath in File.ReadAllLines(ProjectDirsFile)) { string Trimmed = FilePath.Trim(); if (!Trimmed.StartsWith("./", StringComparison.OrdinalIgnoreCase) && !Trimmed.StartsWith(";", StringComparison.OrdinalIgnoreCase) && Trimmed.IndexOfAny(Path.GetInvalidPathChars()) < 0) { SearchPaths.Add(Trimmed); } } } // Get the root of the branch containing the selected file foreach (string SearchPath in SearchPaths) { string P4Path = CommandUtils.CombinePaths(PathSeparator.Slash, P4Env.ClientRoot, SearchPath, RelativePath); if (P4.FileExistsInDepot(P4Path)) { P4WhereRecord Record = P4.Where(P4Path, AllowSpew: false).FirstOrDefault(x => x.DepotFile != null && !x.bUnmap); // make sure to sync with //workspace/path as it cleans up files if the user has stream switched P4ProjectPath = Record.ClientFile; Log.TraceInformation("Found project at {0}", P4ProjectPath); break; } } } if (string.IsNullOrEmpty(P4ProjectPath)) { throw new AutomationException("Could not find project file for {0} locally or in P4. Provide a full path or check the subdirectory is listed in UE4Games.uprojectdirs", ProjectArg); } Log.TraceVerbose("Resolved {0} to P4 Path {1}", ProjectArg, P4ProjectPath); } // Build the list of paths that need syncing List <string> SyncPaths = new List <string>(); // if (ExplicitPaths.Any()) { // Add all explicit paths as <root>/Path/... ExplicitPaths.ToList().ForEach(P => SyncPaths.Add(CommandUtils.CombinePaths(PathSeparator.Slash, P4Env.ClientRoot, P, "..."))); } else { // See if the engine is in P4 too by checking the p4 location of a local file string LocalEngineFile = CommandUtils.CombinePaths(CmdEnv.LocalRoot, "Engine", "Source", "UE4Editor.target.cs"); P4WhereRecord EngineRecord = P4.Where(LocalEngineFile, AllowSpew: false).FirstOrDefault(x => x.DepotFile != null && !x.bUnmap); if (!ProjectOnly && P4.FileExistsInDepot(EngineRecord.DepotFile)) { // make sure to sync with //workspace/path as it cleans up files if the user has stream switched P4EnginePath = EngineRecord.ClientFile.Replace("Engine/Source/UE4Editor.target.cs", ""); SyncPaths.Add(CommandUtils.CombinePaths(PathSeparator.Slash, P4EnginePath + "*")); SyncPaths.Add(CommandUtils.CombinePaths(PathSeparator.Slash, P4EnginePath, "Engine", "...")); } if (!string.IsNullOrEmpty(P4ProjectPath)) { string P4ProjectDir = Regex.Replace(P4ProjectPath, @"[^/]+\.uproject", "...", RegexOptions.IgnoreCase); SyncPaths.Add(P4ProjectDir); } } // Force these files as they can be overwritten by tools if (!PreviewOnly && !string.IsNullOrEmpty(P4EnginePath) && !ProjectOnly) { IEnumerable <string> ForceSyncList = ForceSyncFiles.Select(F => CommandUtils.CombinePaths(PathSeparator.Slash, P4EnginePath, F)); foreach (var F in ForceSyncList) { LogInformation("Force-updating {0}", F); string SyncCommand = string.Format("-f {0}@{1}", F, CL); // sync with retries P4.Sync(SyncCommand, true, false, Retries); } } // Sync all the things foreach (string SyncPath in SyncPaths) { string SyncCommand = ""; if (Threads > 1) { SyncCommand = string.Format(" --parallel \"threads={0}\" {1}", Threads, SyncCommand); } if (ForceSync) { SyncCommand = SyncCommand + " -f"; } SyncCommand += string.Format(" {0}@{1}", SyncPath, CL); if (!PreviewOnly) { // sync with retries P4.Sync(SyncCommand, true, false, Retries); } else { LogInformation("sync {0}", SyncCommand); } } // P4 utils don't return errors :( ExitCode ExitStatus = ExitCode.Success; // Sync is complete so do the actions if (!PreviewOnly && CL > 0) { // Argument to pass to the editor (could be null with no project). string ProjectArgForEditor = ""; if (!string.IsNullOrEmpty(ProjectArg)) { // If we synced the project from P4 we couldn't resolve this earlier if (ProjectFile == null) { NativeProjects.ClearCache(); ProjectFile = ProjectUtils.FindProjectFileFromName(ProjectArg); } ProjectArgForEditor = ProjectFile.FullName; } if (GenerateProject) { Log.TraceVerbose("Generating project files for {0}", ProjectArgForEditor); if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Win64 || BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Win32) { CommandUtils.Run("GenerateProjectFiles.bat", ProjectArgForEditor); } else { CommandUtils.Run("GenerateProjectFiles.sh", ProjectArgForEditor); } } UE4Build Build = new UE4Build(this); if (!Unversioned && !ProjectOnly) { Build.UpdateVersionFiles(ActuallyUpdateVersionFiles: true, ChangelistNumberOverride: CL, IsPromotedOverride: false); } // Build everything if (BuildProject && ExitStatus == ExitCode.Success) { Log.TraceVerbose("Building Editor for {0}", ProjectArgForEditor); BuildEditor BuildCmd = new BuildEditor(); BuildCmd.Clean = ParseParam("clean"); BuildCmd.ProjectName = ProjectArgForEditor; ExitStatus = BuildCmd.Execute(); } if (OpenProject && ExitStatus == ExitCode.Success) { Log.TraceVerbose("Opening Editor for {0}", ProjectArgForEditor); OpenEditor OpenCmd = new OpenEditor(); OpenCmd.ProjectName = ProjectArgForEditor; ExitStatus = OpenCmd.Execute(); } } return(ExitCode.Success); }
public override ExitCode Execute() { // Parse the project filename (as a local path) ProjectArg = ParseParamValue("Project", ProjectArg); Preview = Preview || ParseParam("Preview"); // Will be the full local path to the project file once resolved FileReference ProjectFile = ProjectUtils.FindProjectFileFromName(ProjectArg); if (ProjectFile == null) { throw new AutomationException("Could not find project file for {0}. Provide a full path or check the subdirectory is listed in UE4Games.uprojectdirs", ProjectArg); } // Will be the full workspace path to the project in P4 P4WhereRecord ProjectRecord = GetP4RecordForPath(ProjectFile.FullName); if (ProjectRecord == null) { throw new AutomationException("Could not find a P4 record for {0} with the current workspace settings. use RunUAT P4WriteConfig to write a config file with the relevant info", ProjectFile.FullName); } // get the path to binaries in the depot string DepotBinaryPath = GetZippedBinaryPathFromUGSConfig(ProjectFile.Directory, ProjectRecord.DepotFile); // get the CL int CompatibleChangeList = 0; int CurrentChangeList = 0; GetCurrentAndCompatibleChangeLists(ProjectRecord.DepotFile, out CurrentChangeList, out CompatibleChangeList); LogInformation("Current CL: {0}, Required Compatible CL: {1}", CurrentChangeList, CompatibleChangeList); List <P4Connection.ChangeRecord> BinaryChanges; P4.Changes(out BinaryChanges, DepotBinaryPath, AllowSpew: false); // changes will be in ascending order, so pull out the CL's they were submitted for and return the first CL that is greater than // the change we need. This lets people regress issues if needed. int CheckedInBinaryCL = BinaryChanges.Where(R => { // use a regex to pull the CL the binaries were built from. The description will be something like "[CL 21611] Updated..", // where 21611 is the change they were generated from Match M = Regex.Match(R.Summary, @"\[CL\s+(\d+)\]"); if (M == null) { LogWarning("Change description for {0} did not include expected format of [CL xxxx]", R.CL); return(false); } int SourceCL = Convert.ToInt32(M.Groups[1].ToString()); // Only interested in records greater than the code CL return(SourceCL >= CompatibleChangeList); }) .Select(R => R.CL) // We want the CL of that record .FirstOrDefault(); // And we only care about the first match if (CheckedInBinaryCL == 0) { throw new AutomationException("Could not find a submitted CL for {0} that was for source code >= CL {1}", DepotBinaryPath, CompatibleChangeList); } string VersionedFile = string.Format("{0}@{1}", DepotBinaryPath, CheckedInBinaryCL); LogInformation("Will sync and extract binaries from {0}", VersionedFile); if (!Preview) { string TmpFile = Path.Combine(Path.GetTempPath(), "UGS.zip"); try { P4.PrintToFile(VersionedFile, TmpFile); LogInformation("Unzipping to {0}", CommandUtils.RootDirectory); // we can't use helpers as unlike UGS we don't want to extract anything for UAT, since that us running us.... // That should be fine since we are either being run from the source CL that has already been syned to, or the // next time UAT is run we'll be rebuilt with that CL... int FileCount = 0; string UATDirectory = "Engine/Binaries/DotNET"; using (Ionic.Zip.ZipFile Zip = new Ionic.Zip.ZipFile(TmpFile)) { foreach (Ionic.Zip.ZipEntry Entry in Zip.Entries.Where(x => !x.IsDirectory)) { string OutputFileName = Path.Combine(CommandUtils.RootDirectory.FullName, Entry.FileName); if (Entry.FileName.Replace("\\", "/").StartsWith(UATDirectory, StringComparison.OrdinalIgnoreCase)) { LogInformation("Skipping {0} as UAT is running", OutputFileName); } else { Directory.CreateDirectory(Path.GetDirectoryName(OutputFileName)); using (FileStream OutputStream = new FileStream(OutputFileName, FileMode.Create, FileAccess.Write)) { Entry.Extract(OutputStream); } LogInformation("Extracted {0}", OutputFileName); FileCount++; } } } LogInformation("Unzipped {0} files", FileCount); } catch (Exception Ex) { throw new AutomationException("Failed to uinzip files: {0}", Ex.Message); } finally { try { if (File.Exists(TmpFile)) { File.Delete(TmpFile); } } catch { LogInformation("Failed to remove tmp file {0}", TmpFile); } } // Update version files with our current and compatible CLs LogInformation("Updating Version files to CL: {0} CompatibleCL: {1}", CurrentChangeList, CompatibleChangeList); UE4Build Build = new UE4Build(this); Build.UpdateVersionFiles(ActuallyUpdateVersionFiles: true, ChangelistNumberOverride: CurrentChangeList, CompatibleChangelistNumberOverride: CompatibleChangeList, IsPromotedOverride: false); } return(ExitCode.Success); }
bool GetCurrentAndCompatibleChangeLists(string P4ProjectFilePath, out int OutCurrentChangeList, out int OutCompatibleChangeList) { OutCurrentChangeList = 0; OutCompatibleChangeList = 0; string[] CurrentChangeListPaths = { "<project>/...#have", "<engine>/...#have" }; string[] CompatibleChangeListPaths = { "<project>/Plugins/...#have", "<project>/Source/...#have", "<engine>/...#have" }; string P4ProjectRoot = null; if (P4ProjectFilePath.IndexOf(".uproject", StringComparison.CurrentCultureIgnoreCase) < 0) { throw new AutomationException("P4ProjectFilePath should end in .uproject"); } P4WhereRecord Record = P4.Where(P4ProjectFilePath, AllowSpew: false).FirstOrDefault(x => x.DepotFile != null && !x.bUnmap); P4ProjectRoot = Record.DepotFile.Substring(0, Record.DepotFile.LastIndexOf("/")); // assume for now that if the project is in //depot/<some_path> that the engine is in //Engine; string P4EngineRoot = "//" + P4ProjectRoot.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries).First() + "/Engine"; // Transform the compatible changelist paths queries with the now-known project and engine paths CompatibleChangeListPaths = CompatibleChangeListPaths.Select(S => { string Result = S.Replace("<project>", P4ProjectRoot); Result = Result.Replace("<engine>", P4EngineRoot); return(Result); }) .ToArray(); // Transform the current changelist paths queries with the now-known project and engine paths CurrentChangeListPaths = CurrentChangeListPaths.Select(S => { string Result = S.Replace("<project>", P4ProjectRoot); Result = Result.Replace("<engine>", P4EngineRoot); return(Result); }) .ToArray(); // get the most recent changes for all the compatible paths List <P4Connection.ChangeRecord> ChangeRecords; string CompatiblePathArg = string.Join(" ", CompatibleChangeListPaths); P4.Changes(out ChangeRecords, "-m1 " + CompatiblePathArg); if (ChangeRecords.Any() == false) { throw new AutomationException("No changes returned for P4 paths {0}", CompatiblePathArg); } int SortRecordsDescending(P4Connection.ChangeRecord lhs, P4Connection.ChangeRecord rhs) { return(lhs.CL.CompareTo(rhs.CL) * -1); }; // sort descending to get the most recent CL first ChangeRecords.Sort(SortRecordsDescending); // this is the CL we need for compatibility OutCompatibleChangeList = ChangeRecords.First().CL; // get records for the entire project and engine paths ChangeRecords.Clear(); string CurrentPathArg = string.Join(" ", CurrentChangeListPaths); P4.Changes(out ChangeRecords, "-m1 " + CurrentPathArg); if (ChangeRecords.Any() == false) { throw new AutomationException("No changes returned for P4 paths {0}", CurrentPathArg); } ChangeRecords.Sort(SortRecordsDescending); OutCurrentChangeList = ChangeRecords.First().CL; return(OutCompatibleChangeList != 0 && OutCurrentChangeList != 0); }