// Copyright Epic Games, Inc. All Rights Reserved. using EpicGames.Core; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using UnrealBuildBase; namespace UnrealBuildTool { abstract class UEToolChain { protected readonly ILogger Logger; // Return the extension for response files public static string ResponseExt => ".rsp"; public UEToolChain(ILogger InLogger) { Logger = InLogger; } public virtual void SetEnvironmentVariables() { } public virtual void GetVersionInfo(List Lines) { } public virtual void GetExternalDependencies(HashSet ExternalDependencies) { } /// /// Normalize a path for use in a command line, making it relative to Engine/Source if under the root directory /// /// The FileSystemReference to normalize /// Normalized path as a string protected static string NormalizeCommandLinePath(FileSystemReference Reference) { string path = Reference.FullName; // Try to use a relative path to shorten command line length. if (Reference.IsUnderDirectory(Unreal.RootDirectory)) { path = Reference.MakeRelativeTo(Unreal.EngineSourceDirectory); } if (Path.DirectorySeparatorChar == '\\') { path = path.Replace("\\", "/"); } return path; } /// /// Normalize a path for use in a command line, making it relative if under the Root Directory /// /// The FileItem to normalize /// Normalized path as a string protected static string NormalizeCommandLinePath(FileItem Item) => NormalizeCommandLinePath(Item.Location); /// /// Normalize a path for use in a command line, making it relative to Engine/Source if under the root directory /// /// The FileSystemReference to normalize /// The root paths to use for normalization /// Normalized path as a string public virtual string NormalizeCommandLinePath(FileSystemReference Reference, CppRootPaths RootPaths) => RootPaths.GetVfsOverlayPath(Reference, out string? vfsPath) ? vfsPath : NormalizeCommandLinePath(Reference); /// /// Normalize a path for use in a command line, making it relative if under the Root Directory /// /// The FileItem to normalize /// The root paths to use for normalization /// Normalized path as a string public virtual string NormalizeCommandLinePath(FileItem Item, CppRootPaths RootPaths) => NormalizeCommandLinePath(Item.Location, RootPaths); public static DirectoryReference GetModuleInterfaceDir(DirectoryReference OutputDir) { return DirectoryReference.Combine(OutputDir, "Ifc"); } // Return the path to the cpp compiler that will be used by this toolchain. public virtual FileReference? GetCppCompilerPath() { return null; } public virtual FileItem? CopyDebuggerVisualizer(FileItem SourceFile, DirectoryReference IntermediateDirectory, IActionGraphBuilder Graph) { return null; } public virtual FileItem? LinkDebuggerVisualizer(FileItem SourceFile, DirectoryReference IntermediateDirectory) { return null; } protected abstract CPPOutput CompileCPPFiles(CppCompileEnvironment CompileEnvironment, IEnumerable InputFiles, DirectoryReference OutputDir, string ModuleName, IActionGraphBuilder Graph); public CPPOutput CompileAllCPPFiles(CppCompileEnvironment CompileEnvironment, IEnumerable InputFiles, DirectoryReference OutputDir, string ModuleName, IActionGraphBuilder Graph) { CPPOutput Result; UnrealArchitectureConfig ArchConfig = UnrealArchitectureConfig.ForPlatform(CompileEnvironment.Platform); // compile architectures separately if needed if (ArchConfig.Mode == UnrealArchitectureMode.SingleTargetCompileSeparately || ArchConfig.Mode == UnrealArchitectureMode.SingleTargetLinkSeparately) { Result = new CPPOutput(); foreach (UnrealArch Arch in CompileEnvironment.Architectures.Architectures) { // determine the output location of intermediates (so, if OutputDir had the arch name in it, like Intermediate/x86+arm64, we would replace it with either emptry string // or a single arch name depending on if the platform uses architecture directories for the architecture) // @todo Add ArchitectureConfig.RequiresArchitectureFilenames but for directory -- or can we just use GetFolderNameForArch?!?!? // string ArchReplacement = (Arch == ArchitectureWithoutMarkup()) ? "" : ArchConfig.GetFolderNameForArchitecture(Arch); string PlatformArchitecturesString = ArchConfig.GetFolderNameForArchitectures(CompileEnvironment.Architectures); DirectoryReference ArchOutputDir = new(OutputDir.FullName.Replace(PlatformArchitecturesString, ArchConfig.GetFolderNameForArchitecture(Arch))); CppCompileEnvironment ArchEnvironment = new(CompileEnvironment, Arch); if (ArchEnvironment.UserIncludePaths.Contains(OutputDir)) { ArchEnvironment.UserIncludePaths.Remove(OutputDir); ArchEnvironment.UserIncludePaths.Add(ArchOutputDir); } CPPOutput ArchResult = CompileCPPFiles(ArchEnvironment, InputFiles, ArchOutputDir, ModuleName, Graph); Result.Merge(ArchResult, Arch); } } else { Result = CompileCPPFiles(CompileEnvironment, InputFiles, OutputDir, ModuleName, Graph); } return Result; } public virtual CPPOutput CompileRCFiles(CppCompileEnvironment CompileEnvironment, IEnumerable InputFiles, DirectoryReference OutputDir, IActionGraphBuilder Graph) { CPPOutput Result = new CPPOutput(); return Result; } protected abstract CPPOutput CompileISPCFiles(CppCompileEnvironment CompileEnvironment, IEnumerable InputFiles, DirectoryReference OutputDir, IActionGraphBuilder Graph); public CPPOutput CompileAllISPCFiles(CppCompileEnvironment CompileEnvironment, IEnumerable InputFiles, DirectoryReference OutputDir, IActionGraphBuilder Graph) { CPPOutput Result; UnrealArchitectureConfig ArchConfig = UnrealArchitectureConfig.ForPlatform(CompileEnvironment.Platform); // compile architectures separately if needed if (ArchConfig.Mode == UnrealArchitectureMode.SingleTargetCompileSeparately || ArchConfig.Mode == UnrealArchitectureMode.SingleTargetLinkSeparately) { Result = new CPPOutput(); foreach (UnrealArch Arch in CompileEnvironment.Architectures.Architectures) { string PlatformArchitecturesString = ArchConfig.GetFolderNameForArchitectures(CompileEnvironment.Architectures); DirectoryReference ArchOutputDir = new(OutputDir.FullName.Replace(PlatformArchitecturesString, ArchConfig.GetFolderNameForArchitecture(Arch))); CppCompileEnvironment ArchEnvironment = new(CompileEnvironment, Arch); CPPOutput ArchResult = CompileISPCFiles(ArchEnvironment, InputFiles, ArchOutputDir, Graph); Result.Merge(ArchResult, Arch); } } else { Result = CompileISPCFiles(CompileEnvironment, InputFiles, OutputDir, Graph); } return Result; } protected abstract CPPOutput GenerateISPCHeaders(CppCompileEnvironment CompileEnvironment, IEnumerable InputFiles, DirectoryReference OutputDir, IActionGraphBuilder Graph); public CPPOutput GenerateAllISPCHeaders(CppCompileEnvironment CompileEnvironment, IEnumerable InputFiles, DirectoryReference OutputDir, IActionGraphBuilder Graph) { CPPOutput Result; UnrealArchitectureConfig ArchConfig = UnrealArchitectureConfig.ForPlatform(CompileEnvironment.Platform); // compile architectures separately if needed if (ArchConfig.Mode == UnrealArchitectureMode.SingleTargetCompileSeparately || ArchConfig.Mode == UnrealArchitectureMode.SingleTargetLinkSeparately) { Result = new CPPOutput(); foreach (UnrealArch Arch in CompileEnvironment.Architectures.Architectures) { string PlatformArchitecturesString = ArchConfig.GetFolderNameForArchitectures(CompileEnvironment.Architectures); DirectoryReference ArchOutputDir = new(OutputDir.FullName.Replace(PlatformArchitecturesString, ArchConfig.GetFolderNameForArchitecture(Arch))); CppCompileEnvironment ArchEnvironment = new(CompileEnvironment, Arch); CPPOutput ArchResult = GenerateISPCHeaders(ArchEnvironment, InputFiles, ArchOutputDir, Graph); Result.Merge(ArchResult, Arch); } } else { Result = GenerateISPCHeaders(CompileEnvironment, InputFiles, OutputDir, Graph); } return Result; } public virtual void GenerateTypeLibraryHeader(CppCompileEnvironment CompileEnvironment, ModuleRules.TypeLibrary TypeLibrary, FileReference OutputFile, FileReference? OutputHeader, IActionGraphBuilder Graph) { throw new NotSupportedException("This platform does not support type libraries."); } /// /// Allows a toolchain to decide to create an import library if needed for this Environment /// /// /// /// public virtual FileItem[] LinkImportLibrary(LinkEnvironment LinkEnvironment, IActionGraphBuilder Graph) { // by default doing nothing return Array.Empty(); } public abstract FileItem? LinkFiles(LinkEnvironment LinkEnvironment, bool bBuildImportLibraryOnly, IActionGraphBuilder Graph); public virtual FileItem[] LinkAllFiles(LinkEnvironment LinkEnvironment, bool bBuildImportLibraryOnly, IActionGraphBuilder Graph) { List Result = new(); // compile architectures separately if needed UnrealArchitectureConfig ArchConfig = UnrealArchitectureConfig.ForPlatform(LinkEnvironment.Platform); if (ArchConfig.Mode == UnrealArchitectureMode.SingleTargetLinkSeparately) { foreach (UnrealArch Arch in LinkEnvironment.Architectures.Architectures) { LinkEnvironment ArchEnvironment = new LinkEnvironment(LinkEnvironment, Arch); // determine the output location of intermediates (so, if OutputDir had the arch name in it, like Intermediate/x86+arm64, we would replace it with either emptry string // or a single arch name //string ArchReplacement = Arch == ArchitectureWithoutMarkup() ? "" : ArchConfig.GetFolderNameForArchitecture(Arch); string PlatformArchitecturesString = ArchConfig.GetFolderNameForArchitectures(LinkEnvironment.Architectures); ArchEnvironment.OutputFilePaths = LinkEnvironment.OutputFilePaths.Select(x => new FileReference(x.FullName.Replace(PlatformArchitecturesString, ArchConfig.GetFolderNameForArchitecture(Arch)))).ToList(); FileItem? LinkFile = LinkFiles(ArchEnvironment, bBuildImportLibraryOnly, Graph); if (LinkFile != null) { Result.Add(LinkFile); } } } else { FileItem? LinkFile = LinkFiles(LinkEnvironment, bBuildImportLibraryOnly, Graph); if (LinkFile != null) { Result.Add(LinkFile); } } return Result.ToArray(); } public virtual IEnumerable GetGlobalCommandLineArgs(CppCompileEnvironment CompileEnvironment) { return Array.Empty(); } public virtual IEnumerable GetCPPCommandLineArgs(CppCompileEnvironment CompileEnvironment) { return Array.Empty(); } public virtual IEnumerable GetCCommandLineArgs(CppCompileEnvironment CompileEnvironment) { return Array.Empty(); } public virtual CppCompileEnvironment CreateSharedResponseFile(CppCompileEnvironment CompileEnvironment, FileReference OutResponseFile, IActionGraphBuilder Graph) { return CompileEnvironment; } public virtual void CreateSpecificFileAction(CppCompileEnvironment CompileEnvironment, DirectoryReference SourceDir, DirectoryReference OutputDir, IActionGraphBuilder Graph) { } /// /// Get the name of the response file for the current compile environment and output file /// /// /// /// public virtual FileReference GetResponseFileName(CppCompileEnvironment CompileEnvironment, FileItem OutputFile) { // Construct a relative path for the intermediate response file return OutputFile.Location.ChangeExtension(OutputFile.Location.GetExtension() + ResponseExt); } /// /// Get the name of the response file for the current linker environment and output file /// /// /// /// public virtual FileReference GetResponseFileName(LinkEnvironment LinkEnvironment, FileItem OutputFile) { // Construct a relative path for the intermediate response file return FileReference.Combine(LinkEnvironment.IntermediateDirectory!, OutputFile.Location.GetFileName() + ResponseExt); } public virtual ICollection PostBuild(ReadOnlyTargetRules Target, FileItem Executable, LinkEnvironment ExecutableLinkEnvironment, IActionGraphBuilder Graph) { return new List(); } public virtual ICollection PostBuild(ReadOnlyTargetRules Target, IEnumerable Executables, LinkEnvironment ExecutableLinkEnvironment, IActionGraphBuilder Graph) { // by default, run PostBuild for exe Exe and merge results return Executables.SelectMany(x => PostBuild(Target, x, ExecutableLinkEnvironment, Graph)).ToList(); } public virtual void SetUpGlobalEnvironment(ReadOnlyTargetRules Target, CppCompileEnvironment GlobalCompileEnvironment, LinkEnvironment GlobalLinkEnvironment) { GlobalCompileEnvironment.RootPaths.bUseVfs = Target.bUseVFS; GlobalLinkEnvironment.RootPaths.bUseVfs = Target.bUseVFS; GlobalCompileEnvironment.RootPaths[CppRootPathFolder.Root] = Unreal.RootDirectory; GlobalLinkEnvironment.RootPaths[CppRootPathFolder.Root] = Unreal.RootDirectory; } public virtual void ModifyBuildProducts(ReadOnlyTargetRules Target, UEBuildBinary Binary, IEnumerable Libraries, IEnumerable BundleResources, Dictionary BuildProducts) { } public virtual void ModifyTargetReceipt(ReadOnlyTargetRules Target, TargetReceipt Receipt) { } public virtual void FinalizeOutput(ReadOnlyTargetRules Target, TargetMakefileBuilder MakefileBuilder) { } public virtual void PrepareRuntimeDependencies(List RuntimeDependencies, Dictionary TargetFileToSourceFile, DirectoryReference ExeDir) { } /// /// Adds a build product and its associated debug file to a receipt. /// /// Build product to add /// The type of build product public virtual bool ShouldAddDebugFileToReceipt(FileReference OutputFile, BuildProductType OutputType) { return true; } public virtual FileReference GetDebugFile(FileReference OutputFile, string DebugExtension) { // by default, just change the extension to the debug extension return OutputFile.ChangeExtension(DebugExtension); } public virtual void SetupBundleDependencies(ReadOnlyTargetRules Target, IEnumerable Binaries, string GameName) { } public virtual string GetSDKVersion() { return "Not Applicable"; } /// /// This is the extension of the file that is added as extra link object. It can be an obj file but also a dynamic list.. depends on the platform /// /// The extension (without dot) public virtual string GetExtraLinkFileExtension() { throw new NotSupportedException("This platform does not support merged modules."); } public virtual IEnumerable GetExtraLinkFileAdditionalSymbols(UEBuildBinaryType binaryType) { return Enumerable.Empty(); } /// /// Runs the provided tool and argument. Returns the output, using a rexex capture if one is provided /// /// Full path to the tool to run /// Argument that will be passed to the tool /// null, or a Regular expression to capture in the output /// protected string? RunToolAndCaptureOutput(FileReference Command, string ToolArg, string? Expression = null) { string ProcessOutput = Utils.RunLocalProcessAndReturnStdOut(Command.FullName, ToolArg, null); if (String.IsNullOrEmpty(Expression)) { return ProcessOutput; } Match M = Regex.Match(ProcessOutput, Expression); return M.Success ? M.Groups[1].ToString() : null; } /// /// Runs the provided tool and argument and parses the output to retrieve the version /// /// Full path to the tool to run /// Argument that will result in the version string being shown (it's ok this is a byproduct of a command that returns an error) /// Regular expression to capture the version. By default we look for four integers separated by periods, with the last two optional /// public Version RunToolAndCaptureVersion(FileReference Command, string VersionArg, string VersionExpression = @"(\d+\.\d+(\.\d+)?(\.\d+)?)") { string? ProcessOutput = RunToolAndCaptureOutput(Command, VersionArg, VersionExpression); Version? ToolVersion; if (Version.TryParse(ProcessOutput, out ToolVersion)) { return ToolVersion; } Logger.LogWarning("Unable to retrieve version from {Command} {Arg}", Command, VersionArg); return new Version(0, 0); } }; }