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);
        }
Пример #4
0
        /// <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);
    }