Files
UnrealEngine/Engine/Source/Programs/UnrealBuildTool/Modes/ClReproMode.cs
2025-05-18 13:04:45 +08:00

167 lines
5.7 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using EpicGames.Core;
using Microsoft.Extensions.Logging;
using UnrealBuildBase;
namespace UnrealBuildTool.Modes
{
[ToolMode("ClRepro", ToolModeOptions.UseStartupTraceListener)]
class ClReproMode : ToolMode
{
[CommandLine("-InputCpp=", Required = true)]
public FileReference? InputCpp { get; set; } = null;
[CommandLine("-OutputDir=", Required = true)]
public DirectoryReference? OutputDir { get; set; } = null;
public class ClDepends
{
public class ClDependsData
{
public string? Source { get; init; }
public string? PCH { get; init; }
public HashSet<string>? Includes { get; init; }
}
public string? Version { get; init; }
public ClDependsData? Data { get; init; }
}
private static string GetCaseSensitivePath(string path)
{
try
{
string root = Path.GetPathRoot(path)?.ToUpperInvariant() ?? String.Empty;
foreach (string name in path.Substring(root.Length).Split(System.IO.Path.DirectorySeparatorChar))
{
root = System.IO.Directory.GetFileSystemEntries(root, name).First();
}
return root;
}
catch (Exception)
{
}
return path;
}
private Task<int> PrepareFullRepro(ILogger logger)
{
if (InputCpp == null || OutputDir == null)
{
return Task.FromResult(1);
}
FileReference inputDepFile = InputCpp.ChangeExtension(".cpp.dep.json");
ClDepends? depends;
using (System.IO.FileStream fstream = FileReference.Open(inputDepFile, System.IO.FileMode.Open))
{
depends = JsonSerializer.Deserialize<ClDepends>(fstream);
}
if (depends == null || depends.Data == null || String.IsNullOrEmpty(depends.Data.Source))
{
logger.LogError("Unable to deserialize {Dep}", inputDepFile);
return Task.FromResult(1);
}
if (!String.IsNullOrEmpty(depends.Data.PCH))
{
logger.LogError("PCH repro not currently supported, please use -NoPCH when compiling ({Dep})", inputDepFile);
return Task.FromResult(1);
}
FileReference inputRspFile = InputCpp.ChangeExtension(".cpp.obj.rsp");
FileReference inputSharedRspFile = DirectoryReference.EnumerateFiles(InputCpp.Directory, "*.Shared.rsp", SearchOption.TopDirectoryOnly).First();
SortedSet<FileReference> allFiles = new();
allFiles.Add(new FileReference(GetCaseSensitivePath(depends.Data.Source)));
if (depends.Data.Includes != null)
{
allFiles.UnionWith(depends.Data.Includes.Select(x => new FileReference(GetCaseSensitivePath(x))));
}
DirectoryReference.CreateDirectory(OutputDir);
List<string> rootPaths = new();
rootPaths.Add(Unreal.RootDirectory.FullName + "\\");
logger.LogInformation("Copying {Count} files to {OutputDir}...", allFiles.Count, OutputDir);
foreach (FileReference file in allFiles.Where(x => FileReference.Exists(x)))
{
FileReference dest;
if (file.IsUnderDirectory(Unreal.RootDirectory))
{
dest = FileReference.Combine(OutputDir, file.MakeRelativeTo(Unreal.RootDirectory));
if (!file.IsUnderDirectory(Unreal.EngineDirectory))
{
logger.LogWarning("{File} is not under Engine directory and may not be safe to share", dest);
}
}
else
{
DirectoryReference rootDir = new DirectoryReference(Path.GetPathRoot(file.FullName)!);
if (!rootPaths.Contains(rootDir.FullName))
{
rootPaths.Add(rootDir.FullName);
}
dest = FileReference.Combine(OutputDir, file.MakeRelativeTo(rootDir));
}
DirectoryReference.CreateDirectory(dest.Directory);
FileReference.Copy(file, dest);
logger.LogDebug("Copied {Input} to {Output}", file, dest);
}
FileReference batchFile = FileReference.Combine(OutputDir, "Run.bat");
FileReference outputRspFile = FileReference.Combine(OutputDir, inputRspFile.MakeRelativeTo(Unreal.RootDirectory));
FileReference outputSharedRspFile = FileReference.Combine(OutputDir, inputSharedRspFile.MakeRelativeTo(Unreal.RootDirectory));
string FixPaths(string line)
{
foreach (string root in rootPaths)
{
line = line.Replace($"\"{root}", "\"..\\..\\", StringComparison.InvariantCultureIgnoreCase);
}
return line;
}
// Update include paths in the rsp files
FileReference.WriteAllLines(outputRspFile, FileReference.ReadAllLines(inputRspFile).Select(x => FixPaths(x)));
logger.LogInformation("Rewrote absolute paths for {Input} to {Output}", inputRspFile, outputRspFile);
FileReference.WriteAllLines(outputSharedRspFile, FileReference.ReadAllLines(inputSharedRspFile).Select(x => FixPaths(x)));
logger.LogInformation("Rewrote absolute paths for {Input} to {Output}", inputSharedRspFile, outputSharedRspFile);
DirectoryReference outputEngineSourceDir = DirectoryReference.Combine(OutputDir, "Engine", "Source");
FileReference.WriteAllLines(batchFile, new string[]
{
$"@echo off",
$"setlocal",
$"call \"%ProgramFiles%\\Microsoft Visual Studio\\2022\\Professional\\Common7\\Tools\\VsDevCmd.bat\"",
$"cd /d \"%~dp0\\Engine\\Source\"",
$"cl @\"{outputRspFile.MakeRelativeTo(outputEngineSourceDir)}\"",
$"endlocal",
});
logger.LogInformation("Wrote batch file {Batch}", batchFile);
return Task.FromResult(0);
}
public override Task<int> ExecuteAsync(CommandLineArguments arguments, ILogger logger)
{
arguments.ApplyTo(this);
DirectoryReference logDirectory = DirectoryReference.Combine(Unreal.EngineProgramSavedDirectory, "UnrealBuildTool");
DirectoryReference.CreateDirectory(logDirectory);
FileReference logFile = FileReference.Combine(logDirectory, "Log_ClRepro.txt");
Log.AddFileWriter("DefaultLogTraceListener", logFile);
return PrepareFullRepro(logger);
}
}
}