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

1610 lines
63 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using EpicGames.Core;
using Microsoft.Extensions.Logging;
using UnrealBuildBase;
namespace UnrealBuildTool
{
class LinuxToolChain : ClangToolChain
{
protected class LinuxToolChainInfo : ClangToolChainInfo
{
// cache the location of NDK tools
public bool bIsCrossCompiling { get; init; }
public DirectoryReference? BaseLinuxPath { get; init; }
public DirectoryReference? MultiArchRoot { get; init; }
public FileReference Objcopy { get; init; }
public FileReference DumpSyms { get; init; }
public FileReference BreakpadEncoder { get; init; }
public LinuxToolChainInfo(DirectoryReference? BaseLinuxPath, DirectoryReference? MultiArchRoot, FileReference Clang, FileReference Archiver, FileReference Objcopy, ILogger Logger)
: base(BaseLinuxPath, Clang, Archiver, Logger)
{
this.BaseLinuxPath = BaseLinuxPath;
this.MultiArchRoot = MultiArchRoot;
this.Objcopy = Objcopy;
// these are supplied by the engine and do not change depending on the circumstances
DumpSyms = FileReference.Combine(Unreal.EngineDirectory, "Binaries", "Linux", $"dump_syms{BuildHostPlatform.Current.BinarySuffix}");
BreakpadEncoder = FileReference.Combine(Unreal.EngineDirectory, "Binaries", "Linux", $"BreakpadSymbolEncoder{BuildHostPlatform.Current.BinarySuffix}");
}
}
/** Flavor of the current build (will map to target triplet)*/
UnrealArch Architecture;
/** Pass --gdb-index option to linker to generate .gdb_index section. */
protected bool bGdbIndexSection = true;
/** Allows you to override the maximum binary size allowed to be passed to objcopy.exe when cross building on Windows. */
/** Max value is 2GB, due to bat file limitation */
protected ulong MaxBinarySizeOverrideForObjcopy = 0;
/** Platform SDK to use */
protected LinuxPlatformSDK PlatformSDK;
protected LinuxToolChainInfo LinuxInfo => (Info as LinuxToolChainInfo)!;
public LinuxToolChain(UnrealArch InArchitecture, LinuxPlatformSDK InSDK, ClangToolChainOptions InOptions, ILogger InLogger)
: this(UnrealTargetPlatform.Linux, InArchitecture, InSDK, InOptions, InLogger)
{
// prevent unknown clangs since the build is likely to fail on too old or too new compilers
if ((CompilerVersionLessThan(18, 0, 0) || CompilerVersionGreaterOrEqual(19, 0, 0)) && !Options.HasFlag(ClangToolChainOptions.UseAutoRTFMCompiler))
{
throw new BuildException(
String.Format("This version of the Unreal Engine can only be compiled with clang 18.x. clang {0} may not build it - please use a different version.",
Info.ClangVersion)
);
}
}
public LinuxToolChain(UnrealTargetPlatform InPlatform, UnrealArch InArchitecture, LinuxPlatformSDK InSDK, ClangToolChainOptions InOptions, ILogger InLogger)
: base(InOptions, InLogger)
{
Architecture = InArchitecture;
PlatformSDK = InSDK;
}
protected override ClangToolChainInfo GetToolChainInfo()
{
DirectoryReference? MultiArchRoot = PlatformSDK.GetSDKLocation();
DirectoryReference? BaseLinuxPath = PlatformSDK.GetBaseLinuxPathForArchitecture(Architecture);
bool bForceUseSystemCompiler = PlatformSDK.ForceUseSystemCompiler();
if (bForceUseSystemCompiler)
{
// use native linux toolchain
FileReference? ClangPath = FileReference.FromString(LinuxCommon.WhichClang(Logger));
FileReference? LlvmArPath = FileReference.FromString(LinuxCommon.Which("llvm-ar", Logger));
FileReference? ObjcopyPath = FileReference.FromString(LinuxCommon.Which("llvm-objcopy", Logger));
if (ClangPath == null)
{
throw new BuildException("Unable to find system clang; cannot instantiate Linux toolchain");
}
else if (LlvmArPath == null)
{
throw new BuildException("Unable to find system llvm-ar; cannot instantiate Linux toolchain");
}
else if (ObjcopyPath == null)
{
throw new BuildException("Unable to find system llvm-objcopy; cannot instantiate Linux toolchain");
}
bIsCrossCompiling = false;
return new LinuxToolChainInfo(null, null, ClangPath, LlvmArPath, ObjcopyPath, Logger);
}
else
{
if (BaseLinuxPath == null)
{
throw new BuildException("LINUX_MULTIARCH_ROOT environment variable is not set; cannot instantiate Linux toolchain");
}
if (MultiArchRoot == null)
{
MultiArchRoot = BaseLinuxPath;
Logger.LogInformation("Using LINUX_ROOT (deprecated, consider LINUX_MULTIARCH_ROOT)");
}
// set up the path to our toolchain
FileReference ClangPath = FileReference.Combine(BaseLinuxPath, "bin", $"clang++{BuildHostPlatform.Current.BinarySuffix}");
FileReference LlvmArPath = FileReference.Combine(BaseLinuxPath, "bin", $"llvm-ar{BuildHostPlatform.Current.BinarySuffix}");
FileReference ObjcopyPath = FileReference.Combine(BaseLinuxPath, "bin", $"llvm-objcopy{BuildHostPlatform.Current.BinarySuffix}");
// if we have RTFMCompiler enabled switch the compiler but leave the sysroot to use our toolchain
// this *shouldnt* cause issues as these compiler should ideally be the same version
if (Options.HasFlag(ClangToolChainOptions.UseAutoRTFMCompiler))
{
DirectoryReference AutoRTFMDir = DirectoryReference.Combine(Unreal.EngineDirectory, "Restricted", "NotForLicensees", "Binaries", BuildHostPlatform.Current.Platform.ToString(), "AutoRTFM", "bin");
// set up the path to our toolchain
if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Linux)
{
ClangPath = FileReference.Combine(AutoRTFMDir, $"verse-clang++{BuildHostPlatform.Current.BinarySuffix}");
}
else
{
// We use verse-clang-cl when the host is Windows just to keep the number of compilers deployed to a minimum.
ClangPath = FileReference.Combine(AutoRTFMDir, $"verse-clang-cl{BuildHostPlatform.Current.BinarySuffix}");
}
}
if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Linux)
{
Environment.SetEnvironmentVariable("LC_ALL", "C");
}
bIsCrossCompiling = true;
return new LinuxToolChainInfo(BaseLinuxPath, MultiArchRoot, ClangPath, LlvmArPath, ObjcopyPath, Logger);
}
}
protected virtual bool CrossCompiling()
{
return bIsCrossCompiling;
}
protected internal virtual string GetDumpEncodeDebugCommand(LinkEnvironment LinkEnvironment, FileItem OutputFile, Action action)
{
bool bUseCmdExe = BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Win64;
string DumpCommand = bUseCmdExe ? "\"{0}\" \"{1}\" \"{2}\" 2>NUL" : "\"{0}\" -c -o \"{2}\" \"{1}\"";
Func<string, string> DeleteCommand = fileName =>
{
if (bUseCmdExe)
{
return $"if exist \"{fileName}\" del /F \"{fileName}\"";
}
else
{
return $"rm \"{fileName}\"";
}
};
FileItem EncodedBinarySymbolsFile = FileItem.GetItemByPath(Path.Combine(LinkEnvironment.OutputDirectory!.FullName, OutputFile.Location.GetFileNameWithoutExtension() + ".sym"));
FileItem SymbolsFile = FileItem.GetItemByPath(Path.Combine(LinkEnvironment.IntermediateDirectory!.FullName, OutputFile.Location.GetFileName() + ".psym"));
FileItem StrippedFile = FileItem.GetItemByPath(Path.Combine(LinkEnvironment.IntermediateDirectory.FullName, OutputFile.Location.GetFileName() + "_nodebug"));
FileItem DebugFile = FileItem.GetItemByPath(Path.Combine(LinkEnvironment.OutputDirectory.FullName, OutputFile.Location.GetFileNameWithoutExtension() + ".debug"));
bool preservePsym = Options.HasFlag(ClangToolChainOptions.PreservePSYM);
if (preservePsym)
{
SymbolsFile = FileItem.GetItemByPath(Path.Combine(LinkEnvironment.OutputDirectory.FullName, OutputFile.Location.GetFileNameWithoutExtension() + ".psym"));
}
StringWriter Out = new StringWriter();
Out.NewLine = bUseCmdExe ? "\r\n" : "\n";
if (!Options.HasFlag(ClangToolChainOptions.DisableDumpSyms) || Options.HasFlag(ClangToolChainOptions.PreservePSYM))
{
// dump_syms
Out.WriteLine(DumpCommand,
LinuxInfo.DumpSyms,
OutputFile.AbsolutePath,
SymbolsFile.AbsolutePath
);
// encode breakpad symbols
Out.WriteLine("\"{0}\" \"{1}\" \"{2}\"",
LinuxInfo.BreakpadEncoder,
SymbolsFile.AbsolutePath,
EncodedBinarySymbolsFile.AbsolutePath
);
if (preservePsym)
{
action.ProducedItems.Add(SymbolsFile);
}
else
{
Out.WriteLine(DeleteCommand(SymbolsFile.AbsolutePath));
}
}
else
{
// we have to create dummy files to prevent packaging errors
Out.WriteLine("echo DummySyms>> \"{0}\"", EncodedBinarySymbolsFile.AbsolutePath);
}
action.ProducedItems.Add(EncodedBinarySymbolsFile);
if (LinkEnvironment.bCreateDebugInfo)
{
if (MaxBinarySizeOverrideForObjcopy > 0 && bUseCmdExe)
{
Out.WriteLine("for /F \"tokens=*\" %%F in (\"{0}\") DO set size=%%~zF",
OutputFile.AbsolutePath
);
Out.WriteLine("if %size% LSS {0} (", MaxBinarySizeOverrideForObjcopy);
}
// objcopy stripped file
Out.WriteLine("\"{0}\" --strip-all \"{1}\" \"{2}\"",
LinuxInfo.Objcopy,
OutputFile.AbsolutePath,
StrippedFile.AbsolutePath
);
// objcopy debug file
Out.WriteLine("\"{0}\" --only-keep-debug \"{1}\" \"{2}\"",
LinuxInfo.Objcopy,
OutputFile.AbsolutePath,
DebugFile.AbsolutePath
);
// objcopy link debug file to final so
Out.WriteLine("\"{0}\" --add-gnu-debuglink=\"{1}\" \"{2}\" \"{3}\"",
LinuxInfo.Objcopy,
DebugFile.AbsolutePath,
StrippedFile.AbsolutePath,
OutputFile.AbsolutePath
);
Out.WriteLine(DeleteCommand(StrippedFile.AbsolutePath));
if (bUseCmdExe)
{
if (MaxBinarySizeOverrideForObjcopy > 0)
{
// If we have an override size, then we need to create a dummy file if that size is exceeded
Out.WriteLine(") ELSE (");
Out.WriteLine("echo DummyDebug >> \"{0}\"", DebugFile.AbsolutePath);
Out.WriteLine(")");
}
}
else
{
// Change the debug file to normal permissions. It was taking on the +x rights from the output file
Out.WriteLine("chmod 644 \"{0}\"",
DebugFile.AbsolutePath
);
}
}
else
{
// If we have disabled objcopy then we need to create a dummy debug file
Out.WriteLine("echo DummyDebug >> \"{0}\"",
DebugFile.AbsolutePath
);
}
action.ProducedItems.Add(DebugFile);
return Out.ToString();
}
private static bool ShouldUseLibcxx()
{
// set UE_LINUX_USE_LIBCXX to either 0 or 1. If unset, defaults to 1.
string? UseLibcxxEnvVarOverride = Environment.GetEnvironmentVariable("UE_LINUX_USE_LIBCXX");
if (String.IsNullOrEmpty(UseLibcxxEnvVarOverride) || UseLibcxxEnvVarOverride == "1")
{
return true;
}
return false;
}
/// <inheritdoc/>
protected override void GetCompileArguments_WarningsAndErrors(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
base.GetCompileArguments_WarningsAndErrors(CompileEnvironment, Arguments);
//Arguments.Add("-Wunreachable-code"); // additional warning not normally included in Wall: warns if there is code that will never be executed - not helpful due to bIsGCC and similar
Arguments.Add("-Wno-dangling"); // kinda scary, but matches what we currently do on MS platforms when clang is used
Arguments.Add("-Wno-undefined-bool-conversion"); // hides checking if 'this' pointer is null
}
private void AddLTOFlags(List<string> Arguments, bool bForLinker)
{
if (Options.HasFlag(ClangToolChainOptions.EnableLinkTimeOptimization))
{
if (Options.HasFlag(ClangToolChainOptions.EnableThinLTO))
{
if (bForLinker)
{
Arguments.Add( "-flto=thin -Wl,--thinlto-jobs=all, -Wl,-mllvm,-disable-auto-upgrade-debug-info, -Wl,-mllvm,-enable-ext-tsp-block-placement=1");
}
else
{
Arguments.Add("-flto=thin");
}
}
else
{
Arguments.Add("-flto");
}
}
}
private void AddCompilerLTOFlags(List<string> Arguments)
{
AddLTOFlags(Arguments, false);
}
private void AddLinkerLTOFlags(List<string> Arguments)
{
AddLTOFlags(Arguments, true);
}
/// <inheritdoc/>
protected override void GetCompileArguments_Optimizations(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
base.GetCompileArguments_Optimizations(CompileEnvironment, Arguments);
AddCompilerLTOFlags(Arguments);
if (CompileEnvironment.bCodeCoverage)
{
Arguments.Add("-O0");
if (ShouldUseLibcxx())
{
Arguments.Add("--coverage"); // gcov
}
else
{
Arguments.Add("-fprofile-instr-generate -fcoverage-mapping"); // llvm-cov
}
}
else if (!CompileEnvironment.bOptimizeCode) // optimization level
{
Arguments.Add("-O0");
}
else
{
// Don't over optimise if using Address/MemorySanitizer or you'll get false positive errors due to erroneous optimisation of necessary Address/MemorySanitizer instrumentation.
if (Options.HasFlag(ClangToolChainOptions.EnableAddressSanitizer) || Options.HasFlag(ClangToolChainOptions.EnableMemorySanitizer))
{
Arguments.Add("-O1 -g");
// This enables __asan_default_options() in UnixCommonStartup.h which disables the leak detector
Arguments.Add("-DDISABLE_ASAN_LEAK_DETECTOR=1");
}
else if (Options.HasFlag(ClangToolChainOptions.EnableThreadSanitizer))
{
Arguments.Add("-O1 -g");
}
else
{
if (CompileEnvironment.OptimizationLevel == OptimizationMode.Size)
{
Arguments.Add("-Oz");
}
else if (CompileEnvironment.OptimizationLevel == OptimizationMode.SizeAndSpeed)
{
Arguments.Add("-Os");
}
else
{
if (CompileEnvironment.bPGOOptimize || CompileEnvironment.bPGOProfile)
{
// -Os tends to be both faster and smaller than -O3 when PGO is enabled
Arguments.Add("-Os");
}
else
{
Arguments.Add("-O3");
}
}
}
}
bool bRetainFramePointers = CompileEnvironment.bRetainFramePointers
|| Options.HasFlag(ClangToolChainOptions.EnableAddressSanitizer) || Options.HasFlag(ClangToolChainOptions.EnableMemorySanitizer) || Options.HasFlag(ClangToolChainOptions.EnableThreadSanitizer)
|| CompileEnvironment.Configuration == CppConfiguration.Debug;
if (CompileEnvironment.Configuration == CppConfiguration.Shipping)
{
if (!bRetainFramePointers)
{
Arguments.Add("-fomit-frame-pointer");
}
}
// switches to help debugging
else if (CompileEnvironment.Configuration == CppConfiguration.Debug)
{
Arguments.Add("-fno-inline"); // disable inlining for better debuggability (e.g. callstacks, "skip file" in gdb)
Arguments.Add("-fstack-protector"); // detect stack smashing
}
if (bRetainFramePointers)
{
Arguments.Add("-fno-optimize-sibling-calls");
Arguments.Add("-fno-omit-frame-pointer");
}
}
/// <inheritdoc/>
protected override void GetCompileArguments_Debugging(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
base.GetCompileArguments_Debugging(CompileEnvironment, Arguments);
// debug info
// bCreateDebugInfo is normally set for all configurations, including Shipping - this is needed to enable callstack in Shipping builds (proper resolution: UEPLAT-205, separate files with debug info)
if (CompileEnvironment.bCreateDebugInfo)
{
Arguments.Add("-gdwarf-4");
if (bGdbIndexSection)
{
// Generate .debug_pubnames and .debug_pubtypes sections in a format suitable for conversion into a
// GDB index. This option is only useful with a linker that can produce GDB index version 7.
Arguments.Add("-ggnu-pubnames");
}
if (Options.HasFlag(ClangToolChainOptions.TuneDebugInfoForLLDB))
{
Arguments.Add("-glldb");
}
if (CompileEnvironment.bDebugLineTablesOnly)
{
Arguments.Add("-gline-tables-only");
}
}
if (CompileEnvironment.bHideSymbolsByDefault)
{
Arguments.Add("-fvisibility-ms-compat");
Arguments.Add("-fvisibility-inlines-hidden");
}
}
/// <inheritdoc/>
protected override void GetCompilerArguments_Sanitizers(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
base.GetCompilerArguments_Sanitizers(CompileEnvironment, Arguments);
// UBSan
if (Options.HasFlag(ClangToolChainOptions.EnableUndefinedBehaviorSanitizer))
{
Arguments.Add("-fno-sanitize=vptr");
}
// MSan
if (Options.HasFlag(ClangToolChainOptions.EnableMemorySanitizer))
{
// Force using the ANSI allocator if MSan is enabled
// -fsanitize-memory-track-origins adds a 1.5x-2.5x slow down ontop of MSan normal amount of overhead
// -fsanitize-memory-track-origins=1 is faster but collects only allocation points but not intermediate stores
Arguments.Add("-fsanitize-memory-track-origins");
}
// LibFuzzer
if (Options.HasFlag(ClangToolChainOptions.EnableLibFuzzer))
{
Arguments.Add("-fsanitize=fuzzer");
}
}
/// <inheritdoc/>
protected override void GetCompileArguments_Global(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// build up the commandline common to C and C++
// These aren't supported on Linux at this time
Arguments.Add("-DUSE_DEBUG_LOGGING=0");
Arguments.Add("-DUSE_EVENT_LOGGING=0");
// always select the driver g++ in-case we are using a different binary for clang, such as clang/clang-cl
Arguments.Add("--driver-mode=g++");
if (Options.HasFlag(ClangToolChainOptions.CompressDebugFile))
{
Arguments.Add("-gz=zlib");
}
if (ShouldUseLibcxx())
{
Arguments.Add("-nostdinc++");
if (PlatformSDK.ForceUseLegacyLibCxx())
{
Arguments.Add(GetSystemIncludePathArgument(DirectoryReference.Combine(Unreal.EngineSourceDirectory, "ThirdParty", "Unix", "LibCxx", "include"), CompileEnvironment));
Arguments.Add(GetSystemIncludePathArgument(DirectoryReference.Combine(Unreal.EngineSourceDirectory, "ThirdParty", "Unix", "LibCxx", "include", "c++", "v1"), CompileEnvironment));
}
else
{
if (!PlatformSDK.ForceUseSystemCompiler())
{
// LinuxInfo.BaseLinuxPath should only be null in the case that we've been forced to use the system compiler.
// In that case, project build.cs files should be adjusted to include the relevant directories to the system include paths
if(LinuxInfo.BaseLinuxPath != null)
{
Arguments.Add(GetSystemIncludePathArgument(DirectoryReference.Combine(LinuxInfo.BaseLinuxPath, "include"), CompileEnvironment));
Arguments.Add(GetSystemIncludePathArgument(DirectoryReference.Combine(LinuxInfo.BaseLinuxPath, "include", "c++", "v1"), CompileEnvironment));
}
}
}
}
if (CompilerVersionGreaterOrEqual(12, 0, 0))
{
Arguments.Add("-fbinutils-version=2.36");
}
if (CompileEnvironment.Architecture == UnrealArch.Arm64)
{
Arguments.Add("-funwind-tables"); // generate unwind tables as they are needed for backtrace (on x86(64) they are generated implicitly)
}
Arguments.Add("-fno-math-errno"); // do not assume that math ops have side effects
Arguments.Add(GetRTTIFlag(CompileEnvironment)); // flag for run-time type info
if (CompileEnvironment.Architecture == UnrealArch.X64)
{
Arguments.Add("-mssse3"); // enable ssse3 by default for x86. This is default on for MSVC so lets reflect that here
}
//Arguments.Add("-DOPERATOR_NEW_INLINE=FORCENOINLINE");
if (CompileEnvironment.bIsBuildingDLL)
{
Arguments.Add("-fPIC");
// Use local-dynamic TLS model. This generates less efficient runtime code for __thread variables, but avoids problems of running into
// glibc/ld.so limit (DTV_SURPLUS) for number of dlopen()'ed DSOs with static TLS (see e.g. https://www.cygwin.com/ml/libc-help/2013-11/msg00033.html)
Arguments.Add("-ftls-model=local-dynamic");
}
else
{
Arguments.Add("-ffunction-sections");
Arguments.Add("-fdata-sections");
}
// only suppress if happen to be using a system compiler and we have not explicitly requested pie
if (!CompileEnvironment.bIsBuildingDLL)
{
if (CompileEnvironment.bUsePIE)
{
Arguments.Add("-fPIE");
}
else
{
Arguments.Add("-fno-PIE");
}
}
if (CompileEnvironment.bUseStackProtection)
{
Arguments.Add("-fstack-protector");
}
if (PlatformSDK.bVerboseCompiler)
{
Arguments.Add("-v"); // for better error diagnosis
}
if (CrossCompiling())
{
Arguments.Add($"-target {CompileEnvironment.Architecture.LinuxName}"); // Set target triple
Arguments.Add($"--sysroot=\"{NormalizeCommandLinePath(LinuxInfo.BaseLinuxPath!, CompileEnvironment.RootPaths)}\"");
}
base.GetCompileArguments_Global(CompileEnvironment, Arguments);
}
/// <inheritdoc/>
protected override string EscapePreprocessorDefinition(string Definition)
{
string[] SplitData = Definition.Split('=');
string? Key = SplitData.ElementAtOrDefault(0);
string? Value = SplitData.ElementAtOrDefault(1);
if (String.IsNullOrEmpty(Key))
{
return "";
}
if (!String.IsNullOrEmpty(Value))
{
if (!Value.StartsWith("\"") && (Value.Contains(' ') || Value.Contains('$')))
{
Value = Value.Trim('\"'); // trim any leading or trailing quotes
Value = "\"" + Value + "\""; // ensure wrap string with double quotes
}
// replace double quotes to escaped double quotes if exists
Value = Value.Replace("\"", "\\\"");
}
return Value == null
? String.Format("{0}", Key)
: String.Format("{0}={1}", Key, Value);
}
public override void PrepareRuntimeDependencies(List<RuntimeDependency> RuntimeDependencies, Dictionary<FileReference, FileReference> TargetFileToSourceFile, DirectoryReference ExeDir)
{
// If ASan is enabled we need to copy the companion helper libraries from the MSVC tools bin folder to the
// target executable folder.
if (Options.HasFlag(ClangToolChainOptions.EnableAddressSanitizer) ||
Options.HasFlag(ClangToolChainOptions.EnableThreadSanitizer) ||
Options.HasFlag(ClangToolChainOptions.EnableUndefinedBehaviorSanitizer) ||
Options.HasFlag(ClangToolChainOptions.EnableMemorySanitizer) ||
Options.HasFlag(ClangToolChainOptions.EnableLibFuzzer))
{
bool bInternalBuild = false;
BuildVersion? Version;
if (BuildVersion.TryRead(BuildVersion.GetDefaultFileName(), out Version))
{
bInternalBuild = !Version.IsLicenseeVersion;
}
if (bInternalBuild)
{
string? InternalSdkPath = UEBuildPlatform.GetSDK(UnrealTargetPlatform.Linux)!.GetInternalSDKPath();
if (InternalSdkPath != null)
{
DirectoryReference InternalSdkPathRef = new DirectoryReference(InternalSdkPath);
FileReference SymbolizerSourcePath = FileReference.Combine(InternalSdkPathRef, "bin/llvm-symbolizer");
FileReference SymbolizerTargetPath = FileReference.Combine(ExeDir, "llvm-symbolizer");
RuntimeDependencies.Add(new RuntimeDependency(SymbolizerTargetPath, StagedFileType.NonUFS));
TargetFileToSourceFile[SymbolizerTargetPath] = SymbolizerSourcePath;
}
}
}
}
protected virtual void GetLinkArguments(LinkEnvironment LinkEnvironment, List<string> Arguments)
{
// always select the driver g++ in-case we are using a different binary for clang, such as clang/clang-cl
Arguments.Add("--driver-mode=g++");
Arguments.Add((BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Win64) ? "-fuse-ld=lld.exe" : "-fuse-ld=lld");
if (Options.HasFlag(ClangToolChainOptions.CompressDebugFile))
{
Arguments.Add("-Wl,--compress-debug-sections=zlib");
}
// debugging symbols
// Applying to all configurations @FIXME: temporary hack for FN to enable callstack in Shipping builds (proper resolution: UEPLAT-205)
Arguments.Add("-rdynamic"); // needed for backtrace_symbols()...
if (LinkEnvironment.bIsBuildingDLL)
{
Arguments.Add("-shared");
}
else
{
// ignore unresolved symbols in shared libs
Arguments.Add("-Wl,--unresolved-symbols=ignore-in-shared-libs");
}
if (Options.HasFlag(ClangToolChainOptions.EnableAddressSanitizer) ||
Options.HasFlag(ClangToolChainOptions.EnableThreadSanitizer) ||
Options.HasFlag(ClangToolChainOptions.EnableUndefinedBehaviorSanitizer) ||
Options.HasFlag(ClangToolChainOptions.EnableMemorySanitizer) ||
Options.HasFlag(ClangToolChainOptions.EnableLibFuzzer))
{
Arguments.Add("-g");
if (Options.HasFlag(ClangToolChainOptions.EnableSharedSanitizer))
{
Arguments.Add("-shared-libsan");
// LLVM 15 compiler-rt introduced a new bug causing ASan to crash when going over net code
// https://github.com/llvm/llvm-project/issues/59007
// adding this fixes it, may be required now?
Arguments.Add("-lresolv");
}
if (Options.HasFlag(ClangToolChainOptions.EnableAddressSanitizer))
{
Arguments.Add("-fsanitize=address");
Arguments.Add("-fsanitize-recover=address");
}
else if (Options.HasFlag(ClangToolChainOptions.EnableThreadSanitizer))
{
Arguments.Add("-fsanitize=thread");
}
else if (Options.HasFlag(ClangToolChainOptions.EnableUndefinedBehaviorSanitizer))
{
Arguments.Add("-fsanitize=undefined");
}
else if (Options.HasFlag(ClangToolChainOptions.EnableMemorySanitizer))
{
// -fsanitize-memory-track-origins adds a 1.5x-2.5x slow ontop of MSan normal amount of overhead
// -fsanitize-memory-track-origins=1 is faster but collects only allocation points but not intermediate stores
Arguments.Add("-fsanitize=memory -fsanitize-memory-track-origins");
}
if (Options.HasFlag(ClangToolChainOptions.EnableLibFuzzer))
{
Arguments.Add("-fsanitize=fuzzer");
}
if (CrossCompiling())
{
string ClangVersionFolder = (Info.ClangVersion.Major < 16) ? String.Format("{0}.{1}.{2}", Info.ClangVersion.Major, Info.ClangVersion.Minor, Info.ClangVersion.Build) : String.Format("{0}", Info.ClangVersion.Major);
// x64 only replaced the linux folder with arch, while on arm64 its still linux
if (LinkEnvironment.Architecture == UnrealArch.Arm64)
{
Arguments.Add(String.Format("-Wl,-rpath=\"{0}/lib/clang/{1}/lib/linux\"",
LinuxInfo.BaseLinuxPath, ClangVersionFolder));
}
else
{
Arguments.Add(String.Format("-Wl,-rpath=\"{0}/lib/clang/{1}/lib/x86_64-unknown-linux-gnu\"",
LinuxInfo.BaseLinuxPath, ClangVersionFolder));
}
}
}
if (LinkEnvironment.bCreateDebugInfo && bGdbIndexSection)
{
// Generate .gdb_index section. On my machine, this cuts symbol loading time (breaking at main) from 45
// seconds to 17 seconds (with gdb v8.3.1).
Arguments.Add("-Wl,--gdb-index");
}
if (LinkEnvironment.bCodeCoverage)
{
// Unreal Separates the linking phase and the compilation phase.
// We pass to clang the flag `--coverage` during the compile time
// And we link the correct compiler-rt library (shipped by UE, and part of the LLVM toolchain)
// to every binary produced.
if (ShouldUseLibcxx())
{
Arguments.Add("--coverage"); // gcov
}
else
{
Arguments.Add("-fprofile-instr-generate"); // llvm-cov
}
}
// RPATH for third party libs
Arguments.Add("-Wl,-rpath=${ORIGIN}");
Arguments.Add("-Wl,-rpath-link=${ORIGIN}");
Arguments.Add("-Wl,-rpath=${ORIGIN}/.."); // for modules that are in sub-folders of the main Engine/Binary/Linux folder
if (LinkEnvironment.Architecture == UnrealArch.X64)
{
Arguments.Add("-Wl,-rpath=${ORIGIN}/../../../Engine/Binaries/ThirdParty/Qualcomm/Linux");
}
else
{
// x86_64 is now using updated ICU that doesn't need extra .so
Arguments.Add("-Wl,-rpath=${ORIGIN}/../../../Engine/Binaries/ThirdParty/ICU/icu4c-53_1/Unix/" + LinkEnvironment.Architecture.LinuxName);
}
// @FIXME: Workaround for generating RPATHs for launching on devices UE-54136
Arguments.Add("-Wl,-rpath=${ORIGIN}/../../../Engine/Binaries/ThirdParty/PhysX3/Unix/x86_64-unknown-linux-gnu");
// Some OS ship ld with new ELF dynamic tags, which use DT_RUNPATH vs DT_RPATH. Since DT_RUNPATH do not propagate to dlopen()ed DSOs,
// this breaks the editor on such systems. See https://kenai.com/projects/maxine/lists/users/archive/2011-01/message/12 for details
Arguments.Add("-Wl,--disable-new-dtags");
// This severely improves runtime linker performance. Without using FixDeps the impact on link time is not as big.
Arguments.Add("-Wl,--as-needed");
// Additionally speeds up editor startup by 1-2s
Arguments.Add("-Wl,--hash-style=gnu");
// This apparently can help LLDB speed up symbol lookups
Arguments.Add("-Wl,--build-id");
if (!LinkEnvironment.bIsBuildingDLL)
{
Arguments.Add("-Wl,--gc-sections");
if (LinkEnvironment.bUsePIE)
{
Arguments.Add("-pie");
}
else
{
Arguments.Add("-Wl,-no-pie");
}
}
// Profile Guided Optimization (PGO) and Link Time Optimization (LTO)
// Whether we actually can enable that is checked in CanUseAdvancedLinkerFeatures() earlier
if (LinkEnvironment.bPGOOptimize)
{
//
// Clang emits a warning for each compiled function that doesn't have a matching entry in the profile data.
// This can happen when the profile data is older than the binaries we're compiling.
//
// Disable this warning. It's far too verbose.
//
Arguments.Add("-Wno-backend-plugin");
DirectoryReference? PGODir = DirectoryReference.FromString(LinkEnvironment.PGODirectory!);
Arguments.Add($"-fprofile-instr-use=\"{NormalizeCommandLinePath(DirectoryReference.Combine(PGODir!, LinkEnvironment.PGOFilenamePrefix!))}\"");
}
else if (LinkEnvironment.bPGOProfile)
{
Log.TraceInformationOnce("Enabling Profile Guided Instrumentation (PGI). Linking will take a while.");
Arguments.Add("-fprofile-generate");
}
// whether we actually can do that is checked in CanUseAdvancedLinkerFeatures() earlier
AddLinkerLTOFlags(Arguments);
if (CrossCompiling())
{
Arguments.Add($"-target {LinkEnvironment.Architecture.LinuxName}"); // Set target triple
DirectoryReference SysRootPath = LinuxInfo.BaseLinuxPath!;
Arguments.Add($"--sysroot=\"{NormalizeCommandLinePath(SysRootPath)}\"");
// Linking with the toolchain on linux appears to not search usr/
if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Linux)
{
Arguments.Add($"-B\"{NormalizeCommandLinePath(DirectoryReference.Combine(SysRootPath, "usr", "lib"))}\"");
Arguments.Add($"-B\"{NormalizeCommandLinePath(DirectoryReference.Combine(SysRootPath, "usr", "lib64"))}\"");
Arguments.Add($"-L\"{NormalizeCommandLinePath(DirectoryReference.Combine(SysRootPath, "usr", "lib"))}\"");
Arguments.Add($"-L\"{NormalizeCommandLinePath(DirectoryReference.Combine(SysRootPath, "usr", "lib64"))}\"");
}
}
if (LinkEnvironment.Configuration == CppConfiguration.Shipping)
{
Arguments.Add("-Wl,--icf=all"); // Enables ICF (Identical Code Folding). [all, safe] safe == fold functions that can be proven not to have their address taken.
Arguments.Add("-Wl,-O3");
}
}
string GetArchiveArguments(LinkEnvironment LinkEnvironment)
{
return " rcs";
}
// cache the location of NDK tools
protected bool bIsCrossCompiling;
/// <summary>
/// Contains converter from output file to temporary output file (the one that will be relinked)
/// </summary>
private Dictionary<FileItem, FileItem> LinkToPrelinkFileList = new();
/// <summary>
/// Tracks that information about used C++ library is only printed once
/// </summary>
private bool bHasPrintedBuildDetails = false;
protected void PrintBuildDetails(CppCompileEnvironment CompileEnvironment, ILogger Logger)
{
Logger.LogInformation("------- Build details --------");
Logger.LogInformation("Using {ToolchainInfo}.", LinuxInfo.BaseLinuxPath == null ? "system toolchain" : $"toolchain located at '{LinuxInfo.BaseLinuxPath}'");
Logger.LogInformation("Using clang ({ClangPath}) version '{ClangVersionString}' (string), {ClangVersionMajor} (major), {ClangVersionMinor} (minor), {ClangVersionPatch} (patch)",
Info.Clang, Info.ClangVersionString, Info.ClangVersion.Major, Info.ClangVersion.Minor, Info.ClangVersion.Build);
// inform the user which C++ library the engine is going to be compiled against - important for compatibility with third party code that uses STL
Logger.LogInformation("Using {Lib} standard C++ library.", ShouldUseLibcxx() ? "bundled libc++" : "compiler default (most likely libstdc++)");
Logger.LogInformation("Using lld linker");
Logger.LogInformation("Using llvm-ar ({LlvmAr}) version '{LlvmArVersionString} (string)'", Info.Archiver, Info.ArchiverVersionString);
if (Options.HasFlag(ClangToolChainOptions.EnableAddressSanitizer) ||
Options.HasFlag(ClangToolChainOptions.EnableThreadSanitizer) ||
Options.HasFlag(ClangToolChainOptions.EnableUndefinedBehaviorSanitizer) ||
Options.HasFlag(ClangToolChainOptions.EnableMemorySanitizer) ||
Options.HasFlag(ClangToolChainOptions.EnableLibFuzzer))
{
string SanitizerInfo = "Building with:";
string StaticOrShared = Options.HasFlag(ClangToolChainOptions.EnableSharedSanitizer) ? " dynamically" : " statically";
SanitizerInfo += Options.HasFlag(ClangToolChainOptions.EnableAddressSanitizer) ? StaticOrShared + " linked AddressSanitizer" : "";
SanitizerInfo += Options.HasFlag(ClangToolChainOptions.EnableThreadSanitizer) ? StaticOrShared + " linked ThreadSanitizer" : "";
SanitizerInfo += Options.HasFlag(ClangToolChainOptions.EnableUndefinedBehaviorSanitizer) ? StaticOrShared + " linked UndefinedBehaviorSanitizer" : "";
SanitizerInfo += Options.HasFlag(ClangToolChainOptions.EnableMemorySanitizer) ? StaticOrShared + " linked MemorySanitizer" : "";
SanitizerInfo += Options.HasFlag(ClangToolChainOptions.EnableLibFuzzer) ? StaticOrShared + " linked LibFuzzer" : "";
Logger.LogInformation("{SanitizerInfo}", SanitizerInfo);
}
if (Options.HasFlag(ClangToolChainOptions.CompressDebugFile))
{
Logger.LogInformation("Compressing debug files");
}
Logger.LogInformation("Targeted minimum CPU architecture: {0}", (CompileEnvironment.Architecture == UnrealArch.X64) ? CompileEnvironment.MinCpuArchX64 : CompileEnvironment.MinArm64CpuTarget);
if (CompileEnvironment.bPGOOptimize)
{
Logger.LogInformation("Using PGO (profile guided optimization).");
Logger.LogInformation(" Directory for PGO data files='{CompileEnvironmentPGODirectory}'", CompileEnvironment.PGODirectory);
Logger.LogInformation(" Prefix for PGO data files='{CompileEnvironmentPGOFilenamePrefix}'", CompileEnvironment.PGOFilenamePrefix);
}
if (CompileEnvironment.bCodeCoverage)
{
Logger.LogInformation("Using --coverage build flag");
}
if (CompileEnvironment.bPGOProfile)
{
Logger.LogInformation("Using PGI (profile guided instrumentation).");
}
if (Options.HasFlag(ClangToolChainOptions.EnableLinkTimeOptimization))
{
if (Options.HasFlag(ClangToolChainOptions.EnableThinLTO))
{
Logger.LogInformation("Using ThinLTO (link-time optimization).");
}
else
{
Logger.LogInformation("Using LTO (link-time optimization).");
}
}
if (CompileEnvironment.bUsePIE)
{
Logger.LogInformation("Using position independent executables (PIE)");
}
if (CompileEnvironment.bUseStackProtection)
{
Logger.LogInformation("Using stack protection");
}
Logger.LogInformation("------------------------------");
}
protected override CPPOutput CompileCPPFiles(CppCompileEnvironment CompileEnvironment, IEnumerable<FileItem> InputFiles, DirectoryReference OutputDir, string ModuleName, IActionGraphBuilder Graph)
{
if (ShouldSkipCompile(CompileEnvironment))
{
return new CPPOutput();
}
List<string> GlobalArguments = new();
GetCompileArguments_Global(CompileEnvironment, GlobalArguments);
//var BuildPlatform = UEBuildPlatform.GetBuildPlatform(CompileEnvironment.Platform);
if (!bHasPrintedBuildDetails)
{
PrintBuildDetails(CompileEnvironment, Logger);
bHasPrintedBuildDetails = true;
}
// Create a compile action for each source file.
CPPOutput Result = new CPPOutput();
foreach (FileItem SourceFile in InputFiles)
{
CompileCPPFile(CompileEnvironment, SourceFile, OutputDir, ModuleName, Graph, GlobalArguments, Result);
}
return Result;
}
public FileItem GetPrelinkOutput(DirectoryReference intermediateDir, FileItem output)
{
return FileItem.GetItemByFileReference(FileReference.Combine(intermediateDir, "Prelink", output.Name));
}
/// <summary>
/// Creates an action to archive all the .o files into single .a file
/// </summary>
public FileItem CreateArchiveAndIndex(LinkEnvironment LinkEnvironment, IActionGraphBuilder Graph, ILogger Logger)
{
// Create an archive action
Action ArchiveAction = Graph.CreateAction(ActionType.Link);
ArchiveAction.WorkingDirectory = Unreal.EngineSourceDirectory;
ArchiveAction.CommandPath = Info.Archiver;
ArchiveAction.bCanExecuteInUBA = OperatingSystem.IsWindows(); // Linker on native linux uses vfork/exec which is not handled in uba right now
// this will produce a final library
ArchiveAction.bProducesImportLibrary = true;
// Add the output file as a production of the link action.
FileItem OutputFile = FileItem.GetItemByFileReference(LinkEnvironment.OutputFilePath);
ArchiveAction.ProducedItems.Add(OutputFile);
ArchiveAction.CommandDescription = "Archive";
ArchiveAction.StatusDescription = Path.GetFileName(OutputFile.AbsolutePath);
ArchiveAction.CommandArguments += String.Format("{0} \"{1}\"", GetArchiveArguments(LinkEnvironment), OutputFile.AbsolutePath);
// Add the input files to a response file, and pass the response file on the command-line.
List<string> InputFileNames = new List<string>();
foreach (FileItem InputFile in LinkEnvironment.InputFiles)
{
string InputAbsolutePath = InputFile.AbsolutePath.Replace("\\", "/");
InputFileNames.Add(String.Format("\"{0}\"", InputAbsolutePath));
ArchiveAction.PrerequisiteItems.Add(InputFile);
}
// this won't stomp linker's response (which is not used when compiling static libraries)
FileReference ResponsePath = GetResponseFileName(LinkEnvironment, OutputFile);
if (!ProjectFileGenerator.bGenerateProjectFiles)
{
FileItem ResponseFileItem = Graph.CreateIntermediateTextFile(ResponsePath, InputFileNames);
ArchiveAction.PrerequisiteItems.Add(ResponseFileItem);
}
ArchiveAction.CommandArguments += String.Format(" @\"{0}\"", ResponsePath.FullName);
// Add the additional arguments specified by the environment.
ArchiveAction.CommandArguments += LinkEnvironment.AdditionalArguments;
ArchiveAction.CommandArguments = ArchiveAction.CommandArguments.Replace("\\", "/");
if (BuildHostPlatform.Current.ShellType == ShellType.Sh)
{
ArchiveAction.CommandArguments += "'";
}
else
{
ArchiveAction.CommandArguments += "\"";
}
// Only execute linking on the local PC.
ArchiveAction.bCanExecuteRemotely = false;
return OutputFile;
}
public override FileItem LinkFiles(LinkEnvironment LinkEnvironment, bool bBuildImportLibraryOnly, IActionGraphBuilder Graph)
{
Debug.Assert(!bBuildImportLibraryOnly);
List<string> RPaths = new List<string>();
if (LinkEnvironment.bIsBuildingLibrary || bBuildImportLibraryOnly)
{
return CreateArchiveAndIndex(LinkEnvironment, Graph, Logger);
}
// Create an action that invokes the linker.
Action LinkAction = Graph.CreateAction(ActionType.Link);
LinkAction.RootPaths = LinkEnvironment.RootPaths;
LinkAction.WorkingDirectory = Unreal.EngineSourceDirectory;
string LinkCommandString;
LinkCommandString = "\"" + Info.Clang + "\"";
// Get link arguments.
List<string> LinkArguments = new List<string>();
GetLinkArguments(LinkEnvironment, LinkArguments);
LinkCommandString += " " + String.Join(' ', LinkArguments);
// Tell the action that we're building an import library here and it should conditionally be
// ignored as a prerequisite for other actions
LinkAction.bProducesImportLibrary = LinkEnvironment.bIsBuildingDLL;
// Add profdata as a prerequisite for pgo optimize
if (LinkEnvironment.bPGOOptimize && LinkEnvironment.PGODirectory != null && LinkEnvironment.PGOFilenamePrefix != null)
{
DirectoryReference PGODir = new DirectoryReference(LinkEnvironment.PGODirectory);
LinkAction.PrerequisiteItems.Add(FileItem.GetItemByFileReference(FileReference.Combine(PGODir, LinkEnvironment.PGOFilenamePrefix)));
}
bool needsRelink = LinkEnvironment.bIsBuildingDLL && LinkEnvironment.bIsCrossReferenced;
// If there will be a relink we let the link step write to a file under intermediate folder
FileItem RealOutputFile = FileItem.GetItemByFileReference(LinkEnvironment.OutputFilePath);
FileItem OutputFile = RealOutputFile;
if (needsRelink)
{
OutputFile = GetPrelinkOutput(LinkEnvironment.IntermediateDirectory!, OutputFile);
}
// Add the output file as a production of the link action.
LinkAction.ProducedItems.Add(OutputFile);
// LTO/PGO can take a lot of time, make it clear for the user
if (LinkEnvironment.bPGOProfile)
{
LinkAction.CommandDescription = "Link-PGI";
}
else if (LinkEnvironment.bPGOOptimize)
{
LinkAction.CommandDescription = "Link-PGO";
}
else if (LinkEnvironment.bAllowLTCG)
{
LinkAction.CommandDescription = "Link-LTO";
}
else
{
LinkAction.CommandDescription = "Link";
}
if (Options.HasFlag(ClangToolChainOptions.EnableLinkTimeOptimization) && Options.HasFlag(ClangToolChainOptions.EnableThinLTO))
{
// Set the weight to number of logical cores as lld can max out the available cores
LinkAction.Weight = Utils.GetLogicalProcessorCount();
// Disallow remote to prevent this long running action from running on an agent if remote linking is enabled
LinkAction.bCanExecuteRemotely = false;
}
// because the logic choosing between lld and ld is somewhat messy atm (lld fails to link .DSO due to bugs), make the name of the linker clear
LinkAction.CommandDescription += (LinkCommandString.Contains("-fuse-ld=lld")) ? " (lld)" : " (ld)";
LinkAction.CommandVersion = Info.ClangVersionString;
LinkAction.StatusDescription = Path.GetFileName(OutputFile.AbsolutePath);
LinkAction.CacheBucket = GetCacheBucket(TargetRules, null);
LinkAction.ArtifactMode = ArtifactMode.Enabled;
// Add the output file to the command-line.
LinkCommandString += String.Format(" -o \"{0}\"", OutputFile.AbsolutePath);
// Add the input files to a response file, and pass the response file on the command-line.
List<string> ResponseLines = new List<string>();
foreach (FileItem InputFile in LinkEnvironment.InputFiles)
{
if (InputFile.HasExtension(".ldscript"))
{
ResponseLines.Add($"--version-script=\"{NormalizeCommandLinePath(InputFile)}\"");
}
else
{
ResponseLines.Add($"\"{NormalizeCommandLinePath(InputFile)}\"");
}
LinkAction.PrerequisiteItems.Add(InputFile);
}
if (LinkEnvironment.bIsBuildingDLL)
{
ResponseLines.Add(String.Format(" -soname=\"{0}\"", OutputFile.Location.GetFileName()));
}
// Start with the configured LibraryPaths and also add paths to any libraries that
// we depend on (libraries that we've build ourselves).
List<DirectoryReference> AllLibraryPaths = LinkEnvironment.SystemLibraryPaths;
IEnumerable<string> AdditionalLibraries = Enumerable.Concat(LinkEnvironment.SystemLibraries, LinkEnvironment.Libraries.Select(x => x.FullName));
foreach (string AdditionalLibrary in AdditionalLibraries)
{
string PathToLib = Path.GetDirectoryName(AdditionalLibrary)!;
if (!String.IsNullOrEmpty(PathToLib))
{
// make path absolute, because FixDependencies script may be executed in a different directory
DirectoryReference AbsolutePathToLib = new DirectoryReference(PathToLib);
if (!AllLibraryPaths.Contains(AbsolutePathToLib))
{
AllLibraryPaths.Add(AbsolutePathToLib);
}
}
if ((AdditionalLibrary.Contains("Plugins") || AdditionalLibrary.Contains("Binaries/ThirdParty") || AdditionalLibrary.Contains("Binaries\\ThirdParty")) && Path.GetDirectoryName(AdditionalLibrary) != Path.GetDirectoryName(RealOutputFile.AbsolutePath))
{
string RelativePath = new FileReference(AdditionalLibrary).Directory.MakeRelativeTo(RealOutputFile.Location.Directory);
if (LinkEnvironment.bIsBuildingDLL)
{
// Remove the root Unreal.RootDirectory from the RuntimeLibaryPath
string AdditionalLibraryRootPath = new FileReference(AdditionalLibrary).Directory.MakeRelativeTo(Unreal.RootDirectory);
// Figure out how many dirs we need to go back
string RelativeRootPath = Unreal.RootDirectory.MakeRelativeTo(RealOutputFile.Location.Directory);
// Combine the two together ie. number of ../ + the path after the root
RelativePath = Path.Combine(RelativeRootPath, AdditionalLibraryRootPath);
}
// On Windows, MakeRelativeTo can silently fail if the engine and the project are located on different drives
if (CrossCompiling() && RelativePath.StartsWith(Unreal.RootDirectory.FullName))
{
// do not replace directly, but take care to avoid potential double slashes or missed slashes
string PathFromRootDir = RelativePath.Replace(Unreal.RootDirectory.FullName, "");
// Path.Combine doesn't combine these properly
RelativePath = ((PathFromRootDir.StartsWith("\\") || PathFromRootDir.StartsWith("/")) ? "..\\..\\.." : "..\\..\\..\\") + PathFromRootDir;
}
if (!RPaths.Contains(RelativePath))
{
RPaths.Add(RelativePath);
ResponseLines.Add(String.Format(" -rpath=\"${{ORIGIN}}/{0}\"", RelativePath.Replace('\\', '/')));
}
}
}
foreach (string RuntimeLibaryPath in LinkEnvironment.RuntimeLibraryPaths)
{
string RelativePath = RuntimeLibaryPath;
if (!RelativePath.StartsWith("$"))
{
if (LinkEnvironment.bIsBuildingDLL)
{
// Remove the root Unreal.RootDirectory from the RuntimeLibaryPath
string RuntimeLibraryRootPath = new DirectoryReference(RuntimeLibaryPath).MakeRelativeTo(Unreal.RootDirectory);
// Figure out how many dirs we need to go back
string RelativeRootPath = Unreal.RootDirectory.MakeRelativeTo(RealOutputFile.Location.Directory);
// Combine the two together ie. number of ../ + the path after the root
RelativePath = Path.Combine(RelativeRootPath, RuntimeLibraryRootPath);
}
else
{
string RelativeRootPath = new DirectoryReference(RuntimeLibaryPath).MakeRelativeTo(Unreal.RootDirectory);
// We're assuming that the binary will be placed according to our ProjectName/Binaries/Platform scheme
RelativePath = Path.Combine("..", "..", "..", RelativeRootPath);
}
}
// On Windows, MakeRelativeTo can silently fail if the engine and the project are located on different drives
if (CrossCompiling() && RelativePath.StartsWith(Unreal.RootDirectory.FullName))
{
// do not replace directly, but take care to avoid potential double slashes or missed slashes
string PathFromRootDir = RelativePath.Replace(Unreal.RootDirectory.FullName, "");
// Path.Combine doesn't combine these properly
RelativePath = ((PathFromRootDir.StartsWith("\\") || PathFromRootDir.StartsWith("/")) ? "..\\..\\.." : "..\\..\\..\\") + PathFromRootDir;
}
if (!RPaths.Contains(RelativePath))
{
RPaths.Add(RelativePath);
ResponseLines.Add(String.Format(" -rpath=\"${{ORIGIN}}/{0}\"", RelativePath.Replace('\\', '/')));
}
}
ResponseLines.Add(String.Format(" -rpath-link=\"{0}\"", Path.GetDirectoryName(RealOutputFile.AbsolutePath)));
// Add the library paths to the argument list.
foreach (DirectoryReference LibraryPath in AllLibraryPaths)
{
// use absolute paths because of FixDependencies script again
ResponseLines.Add(String.Format(" -L\"{0}\"", LibraryPath.FullName.Replace('\\', '/')));
}
List<string> EngineAndGameLibrariesLinkFlags = new List<string>();
List<FileItem> EngineAndGameLibrariesFiles = new List<FileItem>();
// Pre-2.25 ld has symbol resolution problems when .so are mixed with .a in a single --start-group/--end-group
// when linking with --as-needed.
// Move external libraries to a separate --start-group/--end-group to fix it (and also make groups smaller and faster to link).
// See https://github.com/EpicGames/UnrealEngine/pull/2778 and https://github.com/EpicGames/UnrealEngine/pull/2793 for discussion
string ExternalLibraries = "";
// add libraries in a library group
ResponseLines.Add(String.Format(" --start-group"));
foreach (string AdditionalLibrary in AdditionalLibraries)
{
if (String.IsNullOrEmpty(Path.GetDirectoryName(AdditionalLibrary)))
{
// library was passed just like "jemalloc", turn it into -ljemalloc
ExternalLibraries += String.Format(" -l{0}", AdditionalLibrary);
}
else if (Path.GetExtension(AdditionalLibrary) == ".a")
{
// static library passed in, pass it along but make path absolute, because FixDependencies script may be executed in a different directory
string AbsoluteAdditionalLibrary = Path.GetFullPath(AdditionalLibrary);
if (AbsoluteAdditionalLibrary.Contains(' '))
{
AbsoluteAdditionalLibrary = String.Format("\"{0}\"", AbsoluteAdditionalLibrary);
}
AbsoluteAdditionalLibrary = AbsoluteAdditionalLibrary.Replace('\\', '/');
// libcrypto/libssl contain number of functions that are being used in different DSOs. FIXME: generalize?
if (LinkEnvironment.bIsBuildingDLL && (AbsoluteAdditionalLibrary.Contains("libcrypto") || AbsoluteAdditionalLibrary.Contains("libssl")))
{
ResponseLines.Add(" --whole-archive " + AbsoluteAdditionalLibrary + " --no-whole-archive");
}
else
{
ResponseLines.Add(" " + AbsoluteAdditionalLibrary);
}
LinkAction.PrerequisiteItems.Add(FileItem.GetItemByPath(AdditionalLibrary));
}
else
{
// Skip over full-pathed library dependencies when building DLLs to avoid circular
// dependencies.
FileItem LibraryDependency = FileItem.GetItemByPath(AdditionalLibrary);
if (LinkToPrelinkFileList.TryGetValue(LibraryDependency, out FileItem? ToRelinkItem))
{
LibraryDependency = ToRelinkItem;
ExternalLibraries += $" -L\"{ToRelinkItem.Directory.FullName.Replace("\\", "/")}\"";
}
string LibName = Path.GetFileNameWithoutExtension(AdditionalLibrary);
if (LibName.StartsWith("lib"))
{
// Remove lib prefix
LibName = LibName.Remove(0, 3);
}
else if (LibraryDependency.Exists)
{
// The library exists, but it is not prefixed with "lib", so force the
// linker to find it without that prefix by prepending a colon to
// the file name.
LibName = String.Format(":{0}", LibraryDependency.Name);
}
string LibLinkFlag = String.Format(" -l{0}", LibName);
if (needsRelink)
{
// We are building a cross referenced DLL so we can't actually include
// dependencies at this point. Instead we add it to the list of
// libraries to be used in the FixDependencies step.
EngineAndGameLibrariesLinkFlags.Add(LibLinkFlag);
EngineAndGameLibrariesFiles.Add(LibraryDependency);
// it is important to add this exactly to the same place where the missing libraries would have been, it will be replaced later
if (!ExternalLibraries.Contains("--allow-shlib-undefined"))
{
ExternalLibraries += String.Format(" -Wl,--allow-shlib-undefined");
}
}
else
{
LinkAction.PrerequisiteItems.Add(LibraryDependency);
ExternalLibraries += LibLinkFlag;
}
}
}
ResponseLines.Add(" --end-group");
FileReference ResponseFileName = GetResponseFileName(LinkEnvironment, OutputFile);
FileItem ResponseFileItem = Graph.CreateIntermediateTextFile(ResponseFileName, ResponseLines);
LinkCommandString += String.Format(" -Wl,@\"{0}\"", ResponseFileName);
LinkAction.PrerequisiteItems.Add(ResponseFileItem);
LinkCommandString += " -Wl,--start-group";
LinkCommandString += ExternalLibraries;
// make unresolved symbols an error, unless a) building a cross-referenced DSO b) we opted out
if ((!LinkEnvironment.bIsBuildingDLL || !LinkEnvironment.bIsCrossReferenced) && !LinkEnvironment.bIgnoreUnresolvedSymbols)
{
// This will make the linker report undefined symbols the current module, but ignore in the dependent DSOs.
// It is tempting, but may not be possible to change that report-all - due to circular dependencies between our libs.
LinkCommandString += " -Wl,--unresolved-symbols=ignore-in-shared-libs";
}
LinkCommandString += " -Wl,--end-group";
LinkCommandString += " -lrt"; // needed for clock_gettime()
LinkCommandString += " -lm"; // math
if (ShouldUseLibcxx())
{
// libc++ and its abi lib
LinkCommandString += " -nodefaultlibs";
string LibCxxLibDir = LinuxInfo.BaseLinuxPath + "/lib64/";
if(PlatformSDK.ForceUseLegacyLibCxx())
{
LibCxxLibDir = "ThirdParty/Unix/LibCxx/lib/Unix/" + LinkEnvironment.Architecture.LinuxName + "/";
}
LinkCommandString += " " + LibCxxLibDir + "libc++.a";
LinkCommandString += " " + LibCxxLibDir + "libc++abi.a";
LinkCommandString += " -lm";
LinkCommandString += " -lc";
LinkCommandString += " -lpthread"; // pthread_mutex_trylock is missing from libc stubs
LinkCommandString += " -lgcc_s";
LinkCommandString += " -lgcc";
}
// these can be helpful for understanding the order of libraries or library search directories
if (PlatformSDK.bVerboseLinker)
{
LinkCommandString += " -Wl,--verbose";
LinkCommandString += " -Wl,--trace";
LinkCommandString += " -v";
}
// Add the additional arguments specified by the environment.
LinkCommandString += LinkEnvironment.AdditionalArguments;
LinkCommandString = LinkCommandString.Replace("\\\\", "/");
LinkCommandString = LinkCommandString.Replace("\\", "/");
bool bUseCmdExe = BuildHostPlatform.Current.ShellType == ShellType.Cmd;
FileReference ShellBinary = BuildHostPlatform.Current.Shell;
string ExecuteSwitch = bUseCmdExe ? " /C" : ""; // avoid -c so scripts don't need +x
// Linux has issues with scripts and parameter expansion from curely brakets
if (!bUseCmdExe)
{
LinkCommandString = LinkCommandString.Replace("{", "'{");
LinkCommandString = LinkCommandString.Replace("}", "}'");
LinkCommandString = LinkCommandString.Replace("$'{", "'${"); // fixing $'{ORIGIN}' to be '${ORIGIN}'
}
string LinkScriptName = String.Format((bUseCmdExe ? "Link-{0}.bat" : "Link-{0}.sh"), OutputFile.Location.GetFileName());
FileReference LinkScriptFullPath = FileReference.Combine(LinkEnvironment.IntermediateDirectory!, LinkScriptName);
LinkAction.PrerequisiteItems.Add(FileItem.GetItemByFileReference(LinkScriptFullPath));
{
StringWriter LinkWriter = new();
if (bUseCmdExe)
{
LinkWriter.NewLine = "\r\n";
LinkWriter.WriteLine("@echo off");
LinkWriter.WriteLine("rem Automatically generated by UnrealBuildTool");
LinkWriter.WriteLine("rem *DO NOT EDIT*");
LinkWriter.WriteLine();
LinkWriter.WriteLine("set Retries=0");
LinkWriter.WriteLine(":linkloop");
LinkWriter.WriteLine("if %Retries% GEQ 1 goto failedtorelink");
LinkWriter.WriteLine(LinkCommandString);
LinkWriter.WriteLine("if %errorlevel% neq 0 goto sleepandretry");
if (!needsRelink)
{
LinkWriter.WriteLine(GetDumpEncodeDebugCommand(LinkEnvironment, OutputFile, LinkAction));
}
LinkWriter.WriteLine("exit 0");
LinkWriter.WriteLine(":sleepandretry");
LinkWriter.WriteLine("ping 127.0.0.1 -n 1 -w 5000 >NUL 2>NUL"); // timeout complains about lack of redirection
LinkWriter.WriteLine("set /a Retries+=1");
LinkWriter.WriteLine("goto linkloop");
LinkWriter.WriteLine(":failedtorelink");
LinkWriter.WriteLine("echo Failed to link {0} after %Retries% retries", OutputFile.AbsolutePath);
LinkWriter.WriteLine("exit 1");
}
else
{
LinkWriter.NewLine = "\n";
LinkWriter.WriteLine("#!/bin/sh");
LinkWriter.WriteLine("# Automatically generated by UnrealBuildTool");
LinkWriter.WriteLine("# *DO NOT EDIT*");
LinkWriter.WriteLine();
LinkWriter.WriteLine("set -o errexit");
LinkWriter.WriteLine(LinkCommandString);
if (!needsRelink)
{
LinkWriter.WriteLine(GetDumpEncodeDebugCommand(LinkEnvironment, OutputFile, LinkAction));
}
}
Directory.CreateDirectory(Path.GetDirectoryName(LinkScriptFullPath.FullName)!);
Graph.CreateIntermediateTextFile(LinkScriptFullPath, LinkWriter.ToString());
}
LinkAction.CommandPath = ShellBinary;
// This must maintain the quotes around the LinkScriptFullPath
LinkAction.CommandArguments = ExecuteSwitch + " \"" + LinkScriptFullPath.FullName + "\"";
// Only execute linking on the local PC.
LinkAction.bCanExecuteRemotely = false;
// Prepare a script that will run later, once all shared libraries and the executable
// are created. This script will be called by action created in FixDependencies()
if (needsRelink)
{
// Create the action to relink the library. This actions does not overwrite the source file so it can be executed in parallel
Action RelinkAction = Graph.CreateAction(ActionType.Link);
RelinkAction.RootPaths = LinkEnvironment.RootPaths;
RelinkAction.WorkingDirectory = LinkAction.WorkingDirectory;
RelinkAction.StatusDescription = LinkAction.StatusDescription;
RelinkAction.CommandDescription = "Relink";
RelinkAction.bCanExecuteRemotely = false;
RelinkAction.CacheBucket = GetCacheBucket(TargetRules, null);
RelinkAction.ArtifactMode = ArtifactMode.Enabled;
RelinkAction.ProducedItems.Add(RealOutputFile);
foreach (FileItem Dependency in EngineAndGameLibrariesFiles)
{
RelinkAction.PrerequisiteItems.Add(Dependency);
}
RelinkAction.PrerequisiteItems.Add(OutputFile); // also depend on the first link action's output
string LinkOutputFileForwardSlashes = OutputFile.AbsolutePath.Replace("\\", "/");
string RelinkedFileForwardSlashes = RealOutputFile.AbsolutePath.Replace("\\", "/");
string EngineAndGameLibrariesString = "";
foreach (string Library in EngineAndGameLibrariesLinkFlags)
{
EngineAndGameLibrariesString += Library;
}
// create the relinking step
string RelinkScriptName = String.Format((bUseCmdExe ? "Relink-{0}.bat" : "Relink-{0}.sh"), RealOutputFile.Location.GetFileName());
FileReference RelinkScriptFullPath = FileReference.Combine(LinkEnvironment.IntermediateDirectory!, RelinkScriptName);
RelinkAction.PrerequisiteItems.UnionWith(LinkAction.PrerequisiteItems);
RelinkAction.PrerequisiteItems.Add(FileItem.GetItemByFileReference(RelinkScriptFullPath));
{
StringWriter RelinkWriter = new();
string RelinkInvocation = LinkCommandString;
string Replace = "-Wl,--allow-shlib-undefined";
RelinkInvocation = RelinkInvocation.Replace(Replace, EngineAndGameLibrariesString);
// should be the same as RelinkedFileRef
RelinkInvocation = RelinkInvocation.Replace(LinkOutputFileForwardSlashes, RelinkedFileForwardSlashes);
RelinkInvocation = RelinkInvocation.Replace("$", "\\$");
if (bUseCmdExe)
{
RelinkWriter.WriteLine("@echo off");
RelinkWriter.WriteLine("rem Automatically generated by UnrealBuildTool");
RelinkWriter.WriteLine("rem *DO NOT EDIT*");
RelinkWriter.WriteLine();
RelinkWriter.WriteLine("set Retries=0");
RelinkWriter.WriteLine(":relinkloop");
RelinkWriter.WriteLine("if %Retries% GEQ 10 goto failedtorelink");
RelinkWriter.WriteLine(RelinkInvocation);
RelinkWriter.WriteLine("if %errorlevel% neq 0 goto sleepandretry");
RelinkWriter.WriteLine(GetDumpEncodeDebugCommand(LinkEnvironment, RealOutputFile, RelinkAction));
RelinkWriter.WriteLine("exit 0");
RelinkWriter.WriteLine(":sleepandretry");
RelinkWriter.WriteLine("ping 127.0.0.1 -n 1 -w 5000 >NUL 2>NUL"); // timeout complains about lack of redirection
RelinkWriter.WriteLine("set /a Retries+=1");
RelinkWriter.WriteLine("goto relinkloop");
RelinkWriter.WriteLine(":failedtorelink");
RelinkWriter.WriteLine("echo Failed to relink {0} after %Retries% retries", OutputFile.AbsolutePath);
RelinkWriter.WriteLine("exit 1");
}
else
{
RelinkWriter.NewLine = "\n";
RelinkWriter.WriteLine("#!/bin/sh");
RelinkWriter.WriteLine("# Automatically generated by UnrealBuildTool");
RelinkWriter.WriteLine("# *DO NOT EDIT*");
RelinkWriter.WriteLine();
RelinkWriter.WriteLine("set -o errexit");
RelinkWriter.WriteLine(RelinkInvocation);
RelinkWriter.WriteLine(GetDumpEncodeDebugCommand(LinkEnvironment, RealOutputFile, RelinkAction));
}
Directory.CreateDirectory(Path.GetDirectoryName(RelinkScriptFullPath.FullName)!);
Graph.CreateIntermediateTextFile(RelinkScriptFullPath, RelinkWriter.ToString());
}
RelinkAction.CommandPath = ShellBinary;
RelinkAction.CommandArguments = ExecuteSwitch + " \"" + RelinkScriptFullPath + "\"";
}
return RealOutputFile;
}
public override void SetupBundleDependencies(ReadOnlyTargetRules Target, IEnumerable<UEBuildBinary> Binaries, string GameName)
{
// Populate the lookup table from real output to prelink output
lock (LinkToPrelinkFileList)
{
foreach (UEBuildBinary binary in Binaries)
{
if (binary.bCreateImportLibrarySeparately)
{
FileItem output = FileItem.GetItemByFileReference(binary.OutputFilePath);
LinkToPrelinkFileList.Add(output, GetPrelinkOutput(binary.IntermediateDirectory, output));
}
}
}
}
public void StripSymbols(FileReference SourceFile, FileReference TargetFile, ILogger Logger)
{
if (SourceFile != TargetFile)
{
// Strip command only works in place so we need to copy original if target is different
File.Copy(SourceFile.FullName, TargetFile.FullName, true);
}
ProcessStartInfo StartInfo = new ProcessStartInfo();
StartInfo.FileName = LinuxInfo.Objcopy.FullName;
StartInfo.Arguments = "--strip-debug \"" + TargetFile.FullName + "\"";
StartInfo.UseShellExecute = false;
StartInfo.CreateNoWindow = true;
Utils.RunLocalProcessAndLogOutput(StartInfo, Logger);
}
public override void AddExtraToolArguments(IList<string> ExtraArguments)
{
// We explicitly include the clang include directories so tools like IWYU can run outside of the clang directory.
// More info: https://github.com/include-what-you-use/include-what-you-use#how-to-install
string? InternalSdkPath = UEBuildPlatform.GetSDK(UnrealTargetPlatform.Linux)!.GetInternalSDKPath();
if (InternalSdkPath != null)
{
// starting with clang 16.x the directory naming changed to include major version only
string ClangVersionString = (Info.ClangVersion.Major >= 16) ? Info.ClangVersion.Major.ToString() : Info.ClangVersion.ToString();
ExtraArguments.Add(String.Format("-isystem {0}", System.IO.Path.Combine(InternalSdkPath, "lib", "clang", ClangVersionString, "include").Replace("\\", "/")));
ExtraArguments.Add(String.Format("-isystem {0}", System.IO.Path.Combine(InternalSdkPath, "usr", "include").Replace("\\", "/")));
}
}
public override string GetExtraLinkFileExtension()
{
return "ldscript";
}
}
}