Files
UnrealEngine/Engine/Extras/P4VUtils/Commands/CompileCommand.cs
2025-05-18 13:04:45 +08:00

201 lines
6.7 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using EpicGames.Core;
using EpicGames.Perforce;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace P4VUtils.Commands
{
[Command("compile", CommandCategory.Horde, 0)]
class CompileCommand : Command
{
public override string Description => "Compiles files in the selected changelist";
public override CustomToolInfo CustomTool => new CustomToolInfo("Compile Files (Non-Unity)", "%c") { ShowConsole = true };
public override async Task<int> Execute(string[] Args, IReadOnlyDictionary<string, string> ConfigValues, ILogger Logger)
{
int Change = -1;
if (Args.Length < 2)
{
Logger.LogError("Missing changelist number");
return 1;
}
else if (!Args[1].Equals("default", StringComparison.Ordinal) && !int.TryParse(Args[1], out Change))
{
Logger.LogError("'{Argument}' is not a numbered changelist", Args[1]);
return 1;
}
using PerforceConnection Perforce = new PerforceConnection(null, null, null, Logger);
bool Result = await BuildAsync(Perforce, Change, Args.Skip(2), Logger);
return Result ? 0 : 1;
}
public static async Task<bool> BuildAsync(PerforceConnection Perforce, int Change, IEnumerable<string> AdditionalArguments, ILogger Logger)
{
ChangeRecord ChangeRecord = await Perforce.GetChangeAsync(GetChangeOptions.None, Change, CancellationToken.None);
if (ChangeRecord.Files.Count == 0)
{
Logger.LogError("No files in selected changelist");
return false;
}
List<FileReference> LocalFiles = new List<FileReference>();
List<WhereRecord> WhereRecords = await Perforce.WhereAsync(ChangeRecord.Files.ToArray(), CancellationToken.None).ToListAsync();
foreach (WhereRecord WhereRecord in WhereRecords)
{
if(WhereRecord.Path != null)
{
FileReference LocalFile = new FileReference(WhereRecord.Path);
if (FileReference.Exists(LocalFile))
{
if (LocalFile.HasExtension(".cpp") || LocalFile.HasExtension(".h") || LocalFile.HasExtension(".c"))
{
LocalFiles.Add(LocalFile);
}
}
}
}
if (LocalFiles.Count == 0)
{
Logger.LogError("No files to build in this changelist");
return false;
}
HashSet<DirectoryReference> RootDirs = new HashSet<DirectoryReference>();
HashSet<FileReference> ProjectFiles = new HashSet<FileReference>();
HashSet<DirectoryReference> CheckedDirectories = new HashSet<DirectoryReference>();
foreach (FileReference LocalFile in LocalFiles)
{
DirectoryReference CurrentDir = LocalFile.Directory;
while (CheckedDirectories.Add(CurrentDir))
{
ProjectFiles.UnionWith(DirectoryReference.EnumerateFiles(CurrentDir, "*.uproject"));
FileReference BatchFile = FileReference.Combine(CurrentDir, "Engine/Build/BatchFiles/RunUBT.bat");
if (FileReference.Exists(BatchFile))
{
RootDirs.Add(CurrentDir);
break;
}
DirectoryReference? NextDir = CurrentDir.ParentDirectory;
if (NextDir == null)
{
Logger.LogError("Unable to find engine directory for {FileName}", LocalFile);
return false;
}
CurrentDir = NextDir;
}
}
if (RootDirs.Count == 0)
{
Logger.LogError("Unable to find engine root directory");
return false;
}
if (RootDirs.Count > 1)
{
Logger.LogError("Found multiple engine root directories for files in changelist");
return false;
}
DirectoryReference RootDir = RootDirs.First();
DirectoryReference EngineDir = DirectoryReference.Combine(RootDir, "Engine");
List<string> Targets = new List<string>();
DirectoryReference EngineIntermediateDir = DirectoryReference.Combine(EngineDir, "Intermediate");
FileReference EngineFileList = FileReference.Combine(EngineIntermediateDir, "P4VUtils-Files.txt");
if (WriteFilteredFileList(EngineFileList, LocalFiles, EngineDir, Logger))
{
Targets.Add($"Win64 Development -TargetType=Editor -FileList={EngineFileList.FullName.QuoteArgument()}");
}
foreach (FileReference ProjectFile in ProjectFiles)
{
DirectoryReference ProjectDir = ProjectFile.Directory;
FileReference ProjectFileList = FileReference.Combine(ProjectDir, "Intermediate", "P4VUtils-Files.txt");
if (WriteFilteredFileList(ProjectFileList, LocalFiles, ProjectDir, Logger))
{
Targets.Add($"Win64 Development -TargetType=Editor -FileList={ProjectFileList.FullName.QuoteArgument()} -Project={ProjectFile.FullName.QuoteArgument()}");
}
}
FileReference TargetListFile = FileReference.Combine(EngineIntermediateDir, "P4VUtils-Targets.txt");
WriteLines(TargetListFile, Targets);
FileReference BuildBatchFile = FileReference.Combine(RootDir, @"Engine\Build\BatchFiles\RunUBT.bat");
StringBuilder Arguments = new StringBuilder($"{BuildBatchFile.FullName.QuoteArgument()} -TargetList={TargetListFile.FullName.QuoteArgument()}");
foreach (string AdditionalArgument in AdditionalArguments)
{
Arguments.AppendFormat(" {0}", AdditionalArgument.QuoteArgument());
}
Logger.LogInformation("Running {Arguments}", Arguments);
Logger.LogInformation("");
string ShellFileName = Environment.GetEnvironmentVariable("COMSPEC") ?? "C:\\Windows\\System32\\cmd.exe";
string ShellArguments = $"/C \"{Arguments}\"";
using (ManagedProcessGroup Group = new ManagedProcessGroup())
using (ManagedProcess Process = new ManagedProcess(Group, ShellFileName, ShellArguments, null, null, System.Diagnostics.ProcessPriorityClass.Normal))
{
#pragma warning disable CA2000 // Dispose objects before losing scope
await Process.CopyToAsync(Console.OpenStandardOutput(), CancellationToken.None);
if (Process.ExitCode != 0)
{
return false;
}
#pragma warning restore CA2000 // Dispose objects before losing scope
}
return true;
}
static bool WriteFilteredFileList(FileReference FileList, List<FileReference> LocalFiles, DirectoryReference BaseDir, ILogger Logger)
{
List<FileReference> FilteredFiles = new List<FileReference>();
foreach (FileReference LocalFile in LocalFiles)
{
if (LocalFile.IsUnderDirectory(BaseDir))
{
FilteredFiles.Add(LocalFile);
}
}
if (FilteredFiles.Count > 0)
{
Logger.LogInformation("Files under {BaseDir}:", BaseDir);
foreach (FileReference FilteredFile in FilteredFiles)
{
Logger.LogInformation(" {File}", FilteredFile);
}
Logger.LogInformation("");
WriteLines(FileList, FilteredFiles.Select(x => x.FullName));
return true;
}
return false;
}
static void WriteLines(FileReference File, IEnumerable<string> Lines)
{
DirectoryReference.CreateDirectory(File.Directory);
FileReference.WriteAllLines(File, Lines);
}
}
}