Files
2025-05-18 13:04:45 +08:00

1528 lines
55 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 System.Text.RegularExpressions;
using EpicGames.Core;
using Microsoft.Extensions.Logging;
using UnrealBuildBase;
namespace UnrealBuildTool
{
class AndroidToolChain : ClangToolChain, IAndroidToolChain
{
// Minimum NDK API level to allow
public const int MinimumNDKAPILevel = 26;
// this is architectures with the dash, which we match in filenames that have inlined arch name
public static readonly Dictionary<UnrealArch, string> AllCpuSuffixes = new()
{
{ UnrealArch.Arm64, "-arm64" },
{ UnrealArch.X64, "-x64" },
};
// short names for the above suffixes
public static readonly Dictionary<string, string> ShortArchNames = new Dictionary<string, string>()
{
{ "", "" },
{ "-arm64", "a8" },
{ "-x64", "x6" },
};
public enum ClangSanitizer
{
None,
Address,
HwAddress,
UndefinedBehavior,
UndefinedBehaviorMinimal,
Thread,
};
public static string GetCompilerOption(ClangSanitizer Sanitizer)
{
switch (Sanitizer)
{
case ClangSanitizer.Address: return "address";
case ClangSanitizer.HwAddress: return "hwaddress";
case ClangSanitizer.UndefinedBehavior:
case ClangSanitizer.UndefinedBehaviorMinimal: return "undefined";
case ClangSanitizer.Thread: return "thread";
default: return "";
}
}
// Version string from the Android specific build of clang. E.g in Android (6317467 based on r365631c1) clang version 9.0.8
// this would be 6317467)
protected static string? AndroidClangBuild;
// the "-android" suffix paths here are vcpkg triplets for the android platform
private static Dictionary<UnrealArch, string[]> AllArchNames = new() {
{ UnrealArch.Arm64, new string[] { "arm64", "arm64-v8a", "arm64-android" } },
{ UnrealArch.X64, new string[] { "x64", "x86_64", "x64-android" } }
};
// architecture paths to use for filtering include and lib paths
private static Dictionary<UnrealArch, string[]> AllFilterArchNames = new() {
{ UnrealArch.Arm64, new string[] { "arm64", "arm64-v8a", "arm64-android" } },
{ UnrealArch.X64, new string[] { "x64", "x86_64", "x64-android" } },
// using Default as a placeholder to remove old folders for arches we no longer support, but licensees may have in their Build rules that we need to strip out
{ UnrealArch.Deprecated , new string[] { "armv7", "armeabi-v7a", "arm-android", "x86", "x86-android" } }
};
private static Dictionary<UnrealArch, string[]> LibrariesToSkip = new() {
{ UnrealArch.Arm64, new string[] { "nvToolsExt", "nvToolsExtStub", "vorbisenc", } },
{ UnrealArch.X64, new string[] { "nvToolsExt", "nvToolsExtStub", "oculus", "OVRPlugin", "vrapi", "ovrkernel", "systemutils", "openglloader", "ovrplatformloader", "vorbisenc", } }
};
private static Dictionary<UnrealArch, string[]> ModulesToSkip = new() {
{ UnrealArch.Arm64, Array.Empty<string>() },
{ UnrealArch.X64, new string[] { "OnlineSubsystemOculus", "OculusHMD", "OculusMR" } }
};
private static Dictionary<UnrealArch, string[]> GeneratedModulesToSkip = new() {
{ UnrealArch.Arm64, Array.Empty<string>() },
{ UnrealArch.X64, new string[] { "OculusEntitlementCallbackProxy", "OculusCreateSessionCallbackProxy", "OculusFindSessionsCallbackProxy", "OculusIdentityCallbackProxy", "OculusNetConnection", "OculusNetDriver", "OnlineSubsystemOculus_init" } }
};
public string? NDKToolchainVersion;
public ulong NDKVersionInt;
int ClangVersionMajor = -1;
int ClangVersionMinor = -1;
int ClangVersionPatch = -1;
protected void SetClangVersion(int Major, int Minor, int Patch)
{
ClangVersionMajor = Major;
ClangVersionMinor = Minor;
ClangVersionPatch = Patch;
}
public string GetClangVersionString()
{
return String.Format("{0}.{1}.{2}", ClangVersionMajor, ClangVersionMinor, ClangVersionPatch);
}
public bool IsNewNDKModel()
{
// Google changed NDK structure in r22+
return NDKVersionInt >= 220000;
}
public bool HasEmbeddedHWASanSupport()
{
return NDKVersionInt >= 260000;
}
public AndroidToolChain(FileReference? InProjectFile, ILogger InLogger)
: this(InProjectFile, ClangToolChainOptions.None, InLogger)
{
}
DirectoryReference GCCToolchainPath;
DirectoryReference SysrootPath;
public AndroidToolChain(FileReference? InProjectFile, ClangToolChainOptions ToolchainOptions, ILogger InLogger)
: base(ToolchainOptions, InLogger)
{
Options = ToolchainOptions;
ProjectFile = InProjectFile;
string? NDKPath = AndroidPlatformSDK.GetNDKRoot();
// don't register if we don't have an NDKROOT specified
if (String.IsNullOrEmpty(NDKPath))
{
throw new BuildException("NDKROOT is not specified; cannot use Android toolchain.");
}
NDKPath = NDKPath.Replace("\"", "");
string ArchitecturePath;
string ArchitecturePathWindows32 = @"prebuilt/windows";
string ArchitecturePathWindows64 = @"prebuilt/windows-x86_64";
string ArchitecturePathMac = @"prebuilt/darwin-x86_64";
string ArchitecturePathLinux = @"prebuilt/linux-x86_64";
string ExeExtension = ".exe";
if (Directory.Exists(Path.Combine(NDKPath, @"toolchains/llvm", ArchitecturePathWindows64)))
{
Logger.LogDebug(" Found Windows 64 bit versions of toolchain");
ArchitecturePath = ArchitecturePathWindows64;
}
else if (Directory.Exists(Path.Combine(NDKPath, @"toolchains/llvm", ArchitecturePathWindows32)))
{
Logger.LogDebug(" Found Windows 32 bit versions of toolchain");
ArchitecturePath = ArchitecturePathWindows32;
}
else if (Directory.Exists(Path.Combine(NDKPath, @"toolchains/llvm", ArchitecturePathMac)))
{
Logger.LogDebug(" Found Mac versions of toolchain");
ArchitecturePath = ArchitecturePathMac;
ExeExtension = "";
}
else if (Directory.Exists(Path.Combine(NDKPath, @"toolchains/llvm", ArchitecturePathLinux)))
{
Logger.LogDebug(" Found Linux versions of toolchain");
ArchitecturePath = ArchitecturePathLinux;
ExeExtension = "";
}
else
{
throw new BuildException("Couldn't find 32-bit or 64-bit versions of the Android toolchain with NDKROOT: " + NDKPath);
}
// get the installed version (in the form r10e and 100500)
UEBuildPlatformSDK SDK = UEBuildPlatform.GetSDK(UnrealTargetPlatform.Android)!;
NDKToolchainVersion = SDK.GetInstalledVersion();
SDK.TryConvertVersionToInt(NDKToolchainVersion, out NDKVersionInt);
// figure out clang version (will live in toolchains/llvm from NDK 21 forward
if (Directory.Exists(Path.Combine(NDKPath, @"toolchains/llvm")))
{
// look for version in AndroidVersion.txt (fail if not found)
string VersionFilename = Path.Combine(NDKPath, @"toolchains/llvm", ArchitecturePath, "AndroidVersion.txt");
if (!File.Exists(VersionFilename))
{
throw new BuildException("Cannot find supported Android toolchain");
}
string[] VersionFile = File.ReadAllLines(VersionFilename);
string[] VersionParts = VersionFile[0].Split('.');
SetClangVersion(Int32.Parse(VersionParts[0]), (VersionParts.Length > 1) ? Int32.Parse(VersionParts[1]) : 0, (VersionParts.Length > 2) ? Int32.Parse(VersionParts[2]) : 0);
}
else
{
throw new BuildException("Cannot find supported Android toolchain with NDKPath:" + NDKPath);
}
// set up the path to our toolchains
ClangPath = Utils.CollapseRelativeDirectories(Path.Combine(NDKPath, @"toolchains/llvm", ArchitecturePath, @"bin/clang++" + ExeExtension));
// Android (6317467 based on r365631c1) clang version 9.0.8
string AndroidClangBuildTmp = Utils.RunLocalProcessAndReturnStdOut(ClangPath, "--version", Logger);
try
{
AndroidClangBuild = Regex.Match(AndroidClangBuildTmp, @"(\w+) based on").Groups[1].ToString();
if (String.IsNullOrEmpty(AndroidClangBuild))
{
AndroidClangBuild = Regex.Match(AndroidClangBuildTmp, @"(\w+), based on").Groups[1].ToString();
}
}
catch
{
Logger.LogWarning("Failed to retreive build version from {AndroidClangBuild}", AndroidClangBuild);
AndroidClangBuild = "unknown";
}
// use lld for r21+
ArPathArm64 = Utils.CollapseRelativeDirectories(Path.Combine(NDKPath, @"toolchains/llvm", ArchitecturePath, @"bin/llvm-ar" + ExeExtension));
ArPathx64 = ArPathArm64;
// NDK setup (enforce minimum API level)
int NDKApiLevel64Int = GetNdkApiLevelInt(MinimumNDKAPILevel);
GCCToolchainPath = DirectoryReference.FromString(Path.Combine(NDKPath, @"toolchains/llvm", ArchitecturePath))!;
SysrootPath = DirectoryReference.FromString(Path.Combine(NDKPath, @"toolchains/llvm", ArchitecturePath, "sysroot").Replace("\\", "/"))!;
// toolchain params (note: use ANDROID=1 same as we define it)
ToolchainLinkParamsArm64 = " --target=aarch64-none-linux-android" + NDKApiLevel64Int + " -DANDROID=1";
ToolchainLinkParamsx64 = " --target=x86_64-none-linux-android" + NDKApiLevel64Int + " -DANDROID=1";
ToolchainParamsArm64 = ToolchainLinkParamsArm64;
ToolchainParamsx64 = ToolchainLinkParamsx64;
if (!IsNewNDKModel())
{
// We need to manually provide -D__ANDROID_API__ for NDK versions prior to r22 only, for newer ones, --target=aarch64-none-linux-android + NDKApiLevel64Int does it for us
ToolchainParamsArm64 += " -D__ANDROID_API__=" + NDKApiLevel64Int;
ToolchainParamsx64 += " -D__ANDROID_API__=" + NDKApiLevel64Int;
}
ReadElfPath = Path.Combine(NDKPath, @"toolchains/llvm", ArchitecturePath, @"bin/llvm-readelf" + ExeExtension);
ObjDumpPath = Path.Combine(NDKPath, @"toolchains/llvm", ArchitecturePath, @"bin/llvm-objdump" + ExeExtension);
}
IEnumerable<string> AdditionalPaths(CppRootPaths Roots)
{
return new[]
{
$"--gcc-toolchain=\"{NormalizeCommandLinePath(GCCToolchainPath, Roots)}\"",
$"--sysroot=\"{NormalizeCommandLinePath(SysrootPath, Roots)}\"",
};
}
protected override ClangToolChainInfo GetToolChainInfo()
{
return new ClangToolChainInfo(DirectoryReference.FromString(AndroidPlatformSDK.GetNDKRoot()), FileReference.FromString(ClangPath)!, FileReference.FromString(ArPathArm64)!, Logger);
}
public static string GetGLESVersion(bool bBuildForES31)
{
string GLESversion = "0x00030000";
if (bBuildForES31)
{
GLESversion = "0x00030002";
}
return GLESversion;
}
private bool BuildWithHiddenSymbolVisibility()
{
ConfigHierarchy Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(ProjectFile), UnrealTargetPlatform.Android);
return Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bBuildWithHiddenSymbolVisibility", out bool bBuild) && bBuild;
}
private bool CompressDebugSymbols()
{
ConfigHierarchy Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(ProjectFile), UnrealTargetPlatform.Android);
return Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bCompressDebugSymbols", out bool bBuild) && bBuild;
}
private bool DisableFunctionDataSections()
{
ConfigHierarchy Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(ProjectFile), UnrealTargetPlatform.Android);
bool bDisableFunctionDataSections = false;
return Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bDisableFunctionDataSections", out bDisableFunctionDataSections) && bDisableFunctionDataSections;
}
private bool EnableAdvancedBinaryCompression()
{
ConfigHierarchy Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(ProjectFile), UnrealTargetPlatform.Android);
bool bEnableAdvancedBinaryCompression = false;
return Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bEnableAdvancedBinaryCompression", out bEnableAdvancedBinaryCompression) && bEnableAdvancedBinaryCompression;
}
private bool DisableStackProtector()
{
ConfigHierarchy Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(ProjectFile), UnrealTargetPlatform.Android);
bool bDisableStackProtector = false;
return Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bDisableStackProtector", out bDisableStackProtector) && bDisableStackProtector;
}
private bool DisableLibCppSharedDependencyValidation()
{
ConfigHierarchy Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(ProjectFile), UnrealTargetPlatform.Android);
bool bDisableLibCppSharedDependencyValidation = false;
return Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bDisableLibCppSharedDependencyValidation", out bDisableLibCppSharedDependencyValidation) && bDisableLibCppSharedDependencyValidation;
}
private string GetVersionScriptFilename(LinkEnvironment LinkEnvironment)
{
return Path.Combine(LinkEnvironment.IntermediateDirectory!.FullName, "ExportSymbols.ldscript");
}
public int GetNdkApiLevelInt(int MinNdk = MinimumNDKAPILevel)
{
string NDKVersion = GetNdkApiLevel();
int NDKVersionInt = MinNdk;
if (NDKVersion.Contains('-'))
{
int Version;
if (Int32.TryParse(NDKVersion.Substring(NDKVersion.LastIndexOf('-') + 1), out Version))
{
if (Version > NDKVersionInt)
{
NDKVersionInt = Version;
}
}
}
return NDKVersionInt;
}
public ulong GetNdkVersionInt()
{
return NDKVersionInt;
}
static string CachedPlatformsFilename = "";
static bool CachedPlatformsValid = false;
static int CachedMinPlatform = -1;
static int CachedMaxPlatform = -1;
private bool ReadMinMaxPlatforms(string PlatformsFilename, out int MinPlatform, out int MaxPlatform)
{
if (!CachedPlatformsFilename.Equals(PlatformsFilename))
{
// reset cache to defaults
CachedPlatformsFilename = PlatformsFilename;
CachedPlatformsValid = false;
CachedMinPlatform = -1;
CachedMaxPlatform = -1;
// try to read it
try
{
JsonObject? PlatformsObj = null;
if (JsonObject.TryRead(new FileReference(PlatformsFilename), out PlatformsObj))
{
CachedPlatformsValid = PlatformsObj.TryGetIntegerField("min", out CachedMinPlatform) && PlatformsObj.TryGetIntegerField("max", out CachedMaxPlatform);
}
}
catch (Exception)
{
}
}
MinPlatform = CachedMinPlatform;
MaxPlatform = CachedMaxPlatform;
return CachedPlatformsValid;
}
//This doesn't take into account SDK version overrides in packaging
public int GetMinSdkVersion(int MinSdk = MinimumNDKAPILevel)
{
int MinSDKVersion = MinSdk;
ConfigHierarchy Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(ProjectFile), UnrealTargetPlatform.Android);
Ini.GetInt32("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "MinSDKVersion", out MinSDKVersion);
return MinSDKVersion;
}
protected virtual bool ValidateNDK(string PlatformsFilename, string ApiString)
{
int MinPlatform, MaxPlatform;
if (!ReadMinMaxPlatforms(PlatformsFilename, out MinPlatform, out MaxPlatform))
{
return false;
}
if (ApiString.Contains('-'))
{
int Version;
if (Int32.TryParse(ApiString.Substring(ApiString.LastIndexOf('-') + 1), out Version))
{
return (Version >= MinPlatform && Version <= MaxPlatform);
}
}
return false;
}
public virtual string GetNdkApiLevel()
{
// ask the .ini system for what version to use
ConfigHierarchy Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(ProjectFile), UnrealTargetPlatform.Android);
string NDKLevel;
Ini.GetString("/Script/AndroidPlatformEditor.AndroidSDKSettings", "NDKAPILevel", out NDKLevel!);
// check for project override of NDK API level
string ProjectNDKLevel;
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "NDKAPILevelOverride", out ProjectNDKLevel!);
ProjectNDKLevel = ProjectNDKLevel.Trim();
if (!String.IsNullOrEmpty(ProjectNDKLevel))
{
NDKLevel = ProjectNDKLevel;
}
string PlatformsFilename = Environment.ExpandEnvironmentVariables("%NDKROOT%/meta/platforms.json");
if (!File.Exists(PlatformsFilename))
{
throw new BuildException("No NDK platforms found in {0}", PlatformsFilename);
}
if (NDKLevel == "latest")
{
int MinPlatform, MaxPlatform;
if (!ReadMinMaxPlatforms(PlatformsFilename, out MinPlatform, out MaxPlatform))
{
throw new BuildException("No NDK platforms found in {0}", PlatformsFilename);
}
NDKLevel = "android-" + MaxPlatform.ToString();
}
// validate the platform NDK is installed
if (!ValidateNDK(PlatformsFilename, NDKLevel))
{
throw new BuildException("The NDK API requested '{0}' not installed in {1}", NDKLevel, PlatformsFilename);
}
return NDKLevel;
}
public string GetLargestApiLevel()
{
string PlatformsFilename = Environment.ExpandEnvironmentVariables("%NDKROOT%/meta/platforms.json");
if (!File.Exists(PlatformsFilename))
{
throw new BuildException("No NDK platforms found in {0}", PlatformsFilename);
}
int MinPlatform, MaxPlatform;
if (!ReadMinMaxPlatforms(PlatformsFilename, out MinPlatform, out MaxPlatform))
{
throw new BuildException("No NDK platforms found in {0}", PlatformsFilename);
}
return "android-" + MaxPlatform.ToString();
}
protected override void GetCompileArguments_IncludePaths(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// remove paths that are not meant for this architecture
// @todo can remove this when we only add paths properly for architecture
IEnumerable<DirectoryReference> FilteredPaths = CompileEnvironment.UserIncludePaths.Where(x => IsDirectoryForArch(x.FullName, CompileEnvironment.Architecture));
Arguments.AddRange(FilteredPaths.Select(IncludePath => GetUserIncludePathArgument(IncludePath, CompileEnvironment)));
FilteredPaths = CompileEnvironment.SystemIncludePaths.Where(x => IsDirectoryForArch(x.FullName, CompileEnvironment.Architecture));
Arguments.AddRange(FilteredPaths.Select(IncludePath => GetSystemIncludePathArgument(IncludePath, CompileEnvironment)));
}
/// <inheritdoc/>
protected override string EscapePreprocessorDefinition(string Definition)
{
return Definition.Contains('"') ? Definition.Replace("\"", "\\\"") : Definition;
}
/// <inheritdoc/>
protected override void GetCompileArguments_WarningsAndErrors(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
base.GetCompileArguments_WarningsAndErrors(CompileEnvironment, Arguments);
// @todo unlikely all needed
Arguments.Add("-Wno-unknown-warning-option"); // Clang has been forked for Android - some warnings may not apply.
Arguments.Add("-Wno-local-type-template-args"); // engine triggers this
Arguments.Add("-Wno-return-type-c-linkage"); // needed for PhysX
Arguments.Add("-Wno-reorder"); // member initialization order
Arguments.Add("-Wno-logical-op-parentheses"); // needed for external headers we can't change
Arguments.Add("-Wno-nonportable-include-path"); // not all of these are real
Arguments.Add("-Wno-extra-qualification"); // metasound DECLARE_METASOUND_xxx sometimes have redundant ::Metasound prefix
Arguments.Add("-Wno-shorten-64-to-32"); // too many right now, same on other platforms
}
/// <inheritdoc/>
protected override void GetCompileArguments_Optimizations(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// @todo the base has PGO options that are a little different than what we want - it would be nice to merge these
// optimization level
if (!CompileEnvironment.bOptimizeCode)
{
Arguments.Add("-O0");
}
else
{
if (CompileEnvironment.OptimizationLevel == OptimizationMode.Size)
{
Arguments.Add("-Oz");
}
else if (CompileEnvironment.OptimizationLevel == OptimizationMode.SizeAndSpeed)
{
Arguments.Add("-Os");
if (CompileEnvironment.Architecture == UnrealArch.Arm64)
{
Arguments.Add("-moutline");
}
}
else
{
Arguments.Add("-O3");
}
}
if (CompileEnvironment.bAllowLTCG)
{
if ((Options & ClangToolChainOptions.EnableThinLTO) != 0)
{
Arguments.Add("-flto=thin");
}
else
{
Arguments.Add("-flto");
}
}
// Profile Guided Optimization (PGO) and Link Time Optimization (LTO)
if (CompileEnvironment.bPGOOptimize)
{
//
// Clang emits warnings for each compiled object file 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 these warnings. They are far too verbose.
//
Arguments.Add("-Wno-profile-instr-out-of-date");
Arguments.Add("-Wno-profile-instr-unprofiled");
// apparently there can be hashing conflicts with PGO which can result in:
// 'Function control flow change detected (hash mismatch)' warnings.
Arguments.Add("-Wno-backend-plugin");
Arguments.Add(String.Format(" -fprofile-use=\"{0}.profdata\"", Path.Combine(CompileEnvironment.PGODirectory!, CompileEnvironment.PGOFilenamePrefix!).Replace("\\", "/")));
}
else if (CompileEnvironment.bPGOProfile)
{
// Android supports only LLVM IR-based for instrumentation-based PGO.
Arguments.Add("-fprofile-generate");
// for sampling-based profile collection to generate minimal debug information:
Arguments.Add("-gline-tables-only");
}
if (!CompileEnvironment.bUseInlining)
{
Arguments.Add("-fno-inline-functions");
}
Arguments.Add("-fdebug-types-section");
}
/// <inheritdoc/>
protected override void GetCompileArguments_Architecture(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
base.GetCompileArguments_Architecture(CompileEnvironment, Arguments);
Arguments.AddRange(AdditionalPaths(CompileEnvironment.RootPaths));
if (CompileEnvironment.Architecture == UnrealArch.Arm64)
{
Arguments.Add(ToolchainParamsArm64);
Arguments.Add("-D__arm64__"); // for some reason this isn't defined and needed for PhysX
Arguments.Add("-DPLATFORM_ANDROID_ARM64=1");
Arguments.Add("-march=armv8-a");
}
else if (CompileEnvironment.Architecture == UnrealArch.X64)
{
Arguments.Add(ToolchainParamsx64);
Arguments.Add("-DPLATFORM_ANDROID_X64=1");
Arguments.Add("-fno-omit-frame-pointer");
Arguments.Add("-march=atom");
}
}
/// <inheritdoc/>
protected override void GetCompileArguments_Debugging(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
base.GetCompileArguments_Debugging(CompileEnvironment, Arguments);
// debug info
if (CompileEnvironment.bCreateDebugInfo)
{
Arguments.Add("-g2");
Arguments.Add("-gdwarf-4");
if (CompressDebugSymbols())
{
Arguments.Add("-gz");
}
if (CompileEnvironment.bDebugLineTablesOnly)
{
Arguments.Add("-gline-tables-only");
}
}
if (!DisableStackProtector())
{
Arguments.Add("-fstack-protector-strong"); // Emits extra code to check for buffer overflows
}
// Add flags for on-device debugging
if (CompileEnvironment.Configuration == CppConfiguration.Debug)
{
Arguments.Add("-fno-omit-frame-pointer"); // Disable removing the save/restore frame pointer for better debugging
if (CompilerVersionGreaterOrEqual(3, 6, 0))
{
Arguments.Add(" -fno-function-sections"); // Improve breakpoint location
}
}
// Some switches interfere with on-device debugging
if (CompileEnvironment.Configuration != CppConfiguration.Debug && !DisableFunctionDataSections())
{
Arguments.Add("-ffunction-sections"); // Places each function in its own section of the output file, linker may be able to perform opts to improve locality of reference
Arguments.Add("-fdata-sections"); // Places each data item in its own section of the output file, linker may be able to perform opts to improve locality of reference
}
}
/// <inheritdoc/>
protected override void GetCompilerArguments_Sanitizers(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// TODO: Reconcile with base
//base.GetCompilerArguments_Sanitizers(CompileEnvironment, Arguments);
ClangSanitizer Sanitizer = BuildWithSanitizer();
if (Sanitizer != ClangSanitizer.None)
{
Arguments.Add("-fsanitize=" + GetCompilerOption(Sanitizer));
if (Sanitizer == ClangSanitizer.Address || Sanitizer == ClangSanitizer.HwAddress)
{
Arguments.Add("-fno-omit-frame-pointer");
}
}
//string? SanitizerMode = Environment.GetEnvironmentVariable("ENABLE_ADDRESS_SANITIZER");
//if ((SanitizerMode != null && SanitizerMode == "YES") || (Options.HasFlag(ClangToolChainOptions.EnableAddressSanitizer)))
//{
// Arguments.Add("-fsanitize=address -fno-omit-frame-pointer");
//}
//string? UndefSanitizerMode = Environment.GetEnvironmentVariable("ENABLE_UNDEFINED_BEHAVIOR_SANITIZER");
//if ((UndefSanitizerMode != null && UndefSanitizerMode == "YES") || (Options.HasFlag(ClangToolChainOptions.EnableUndefinedBehaviorSanitizer)))
//{
// Arguments.Add("-fsanitize=undefined -fno-sanitize=bounds,enum,return,float-divide-by-zero");
//}
//if (Options.HasFlag(ClangToolChainOptions.EnableThreadSanitizer))
//{
// Arguments.Add("-fsanitize=thread");
//}
//if (Options.HasFlag(ClangToolChainOptions.EnableMemorySanitizer))
//{
// Arguments.Add("-fsanitize=memory");
//}
}
/// <inheritdoc/>
protected override void GetCompileArguments_Global(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
base.GetCompileArguments_Global(CompileEnvironment, Arguments);
// NDK setup (enforce minimum API level)
int NDKApiLevel64Int = GetNdkApiLevelInt(21); // deliberately use 21 minimum to force NDKApiLevel64Bit to update if below minimum
string NDKApiLevel64Bit = GetNdkApiLevel();
if (NDKApiLevel64Int < MinimumNDKAPILevel)
{
NDKApiLevel64Int = MinimumNDKAPILevel;
NDKApiLevel64Bit = "android-" + MinimumNDKAPILevel;
}
Log.TraceInformationOnce("Compiling Native 64-bit code with NDK API '{0}'", NDKApiLevel64Bit);
string NativeGluePath = Path.GetFullPath(GetNativeGluePath());
if (BuildWithHiddenSymbolVisibility())
{
Arguments.Add("-fvisibility=hidden");
Arguments.Add("-fvisibility-inlines-hidden");
//TODO: add when Android's clang will support this
//Arguments.Add("-fvisibility-inlines-hidden-static-local-var");
}
Arguments.Add(GetRTTIFlag(CompileEnvironment));
Arguments.Add("-no-canonical-prefixes");
Arguments.Add("-fno-PIE");
Arguments.Add("-funwind-tables"); // Just generates any needed static data, affects no code
Arguments.Add("-fPIC"); // Generates position-independent code (PIC) suitable for use in a shared library
Arguments.Add("-fno-strict-aliasing"); // Prevents unwanted or invalid optimizations that could produce incorrect code
Arguments.Add("-fno-short-enums"); // Do not allocate to an enum type only as many bytes as it needs for the declared range of possible values
Arguments.Add("-fforce-emit-vtables"); // Helps with devirtualization
Arguments.Add("-D_FORTIFY_SOURCE=2"); // FORTIFY default
Arguments.Add($"-DPLATFORM_USED_NDK_VERSION_INTEGER={NDKApiLevel64Int}"); // NDK version
Arguments.Add("-DPLATFORM_64BITS=1"); // NDK version
}
/// <inheritdoc/>
protected override FileItem GetCompileArguments_FileType(CppCompileEnvironment CompileEnvironment, FileItem SourceFile, DirectoryReference OutputDir, List<string> Arguments, Action CompileAction, CPPOutput CompileResult)
{
FileItem TargetFile = base.GetCompileArguments_FileType(CompileEnvironment, SourceFile, OutputDir, Arguments, CompileAction, CompileResult);
string Extension = Path.GetExtension(SourceFile.AbsolutePath).ToUpperInvariant();
if (Extension == ".C")
{
if (SourceFile.AbsolutePath.Equals(GetNativeGluePath()))
{
// Remove visibility settings for android native glue. Since it doesn't decorate with visibility attributes.
Arguments.RemoveAll(x => x.StartsWith("-fvisibility"));
}
// remove any PCH includes - mostly for the force-added .c files in Launch as those will attempt to have the PCH used that was made with .cpp language
Arguments.RemoveAll(x => x.StartsWith("-include-pch"));
}
return TargetFile;
}
protected virtual string GetLinkArguments(LinkEnvironment LinkEnvironment, UnrealArch Architecture)
{
string Result = string.Join(' ', AdditionalPaths(LinkEnvironment.RootPaths));
//Result += " -nostdlib";
Result += " -static-libstdc++";
Result += " -no-canonical-prefixes";
Result += " -shared";
Result += " -Wl,-Bsymbolic";
Result += " -Wl,--no-undefined";
if (!DisableFunctionDataSections())
{
Result += " -Wl,--gc-sections"; // Enable garbage collection of unused input sections. works best with -ffunction-sections, -fdata-sections
}
if (!LinkEnvironment.bCreateDebugInfo)
{
Result += " -Wl,--strip-debug";
}
else if (CompressDebugSymbols())
{
Result += " -gz";
}
if (Architecture == UnrealArch.X64)
{
Result += ToolchainLinkParamsx64;
Result += " -march=atom";
}
else // if (Architecture == UnrealArch.Arm64)
{
Result += ToolchainLinkParamsArm64;
Result += " -march=armv8-a";
}
if (LinkEnvironment.Configuration == CppConfiguration.Shipping)
{
Result += " -Wl,--icf=all"; // Enables ICF (Identical Code Folding). [all, safe] safe == fold functions that can be proven not to have their address taken.
Result += " -Wl,-O3";
}
Result += " -Wl,-no-pie";
// use lld as linker (requires llvm-strip)
Result += " -fuse-ld=lld";
// make sure the DT_SONAME field is set properly (or we can a warning toast at startup on new Android)
Result += " -Wl,-soname,libUnreal.so";
{
string VersionScriptFile = GetVersionScriptFilename(LinkEnvironment);
using (StreamWriter Writer = File.CreateText(VersionScriptFile))
{
// Make all symbols hidden (except new/delete operators and ones called from Java)
Writer.WriteLine("{ global: _Znwm*; _Znam*; _ZdlPv*; _ZdaPv*; Java_*; ANativeActivity_onCreate; JNI_OnLoad; local: *; };");
}
Result += " -Wl,--version-script=\"" + VersionScriptFile + "\"";
}
Result += " -Wl,--build-id=sha1"; // add build-id to make debugging easier
if (LinkEnvironment.bPGOOptimize)
{
//
// Clang emits warnings for each compiled object file 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 these warnings. They are far too verbose.
//
Result += " -Wno-profile-instr-out-of-date";
Result += " -Wno-profile-instr-unprofiled";
Result += String.Format(" -fprofile-use=\"{0}.profdata\"", Path.Combine(LinkEnvironment.PGODirectory!, LinkEnvironment.PGOFilenamePrefix!).Replace("\\", "/"));
}
else if (LinkEnvironment.bPGOProfile)
{
// Android supports only LLVM IR-based for instrumentation-based PGO.
Result += " -fprofile-generate";
// for sampling-based profile collection to generate minimal debug information:
Result += " -gline-tables-only";
}
if (LinkEnvironment.bAllowLTCG)
{
if ((Options & ClangToolChainOptions.EnableThinLTO) != 0)
{
Result += " -flto=thin";
Result += " -Wl,--thinlto-jobs=all";
}
else
{
Result += " -flto";
}
}
// verbose output from the linker
// Result += " -v";
ClangSanitizer Sanitizer = BuildWithSanitizer();
if (Sanitizer != ClangSanitizer.None)
{
Result += " -fsanitize=" + GetCompilerOption(Sanitizer);
}
if (EnableAdvancedBinaryCompression())
{
int MinSDKVersion = GetMinSdkVersion();
if (MinSDKVersion >= 28)
{
//Pack relocations in RELR format and Android APS2 packed format for RELA relocations if they can't be expressed in RELR
Result += " -Wl,--pack-dyn-relocs=android+relr,--use-android-relr-tags";
}
else if (MinSDKVersion >= 23)
{
Result += " -Wl,--pack-dyn-relocs=android";
}
if (MinSDKVersion >= 23)
{
Result += " -Wl,--hash-style=gnu"; // generate GNU style hashes, faster lookup and faster startup. Avoids generating old .hash section. Supported on >= Android M
}
}
// Enable support for non-4k virtual page sizes
Result += " -z max-page-size=16384";
return Result;
}
protected virtual void ModifyLibraries(LinkEnvironment LinkEnvironment)
{
// @todo Lumin: verify this works with base android
if (GetNdkApiLevelInt() >= 21)
{
// this file was added in NDK11 so use existence to detect (RELEASE.TXT no longer present)
//string NDKRoot = Environment.GetEnvironmentVariable("NDKROOT").Replace("\\", "/");
}
}
static string GetArArguments(LinkEnvironment LinkEnvironment)
{
string Result = "";
Result += " -r";
return Result;
}
static bool IsDirectoryForArch(string Dir, UnrealArch Arch)
{
// make sure paths use one particular slash
Dir = Dir.Replace("\\", "/").ToLowerInvariant();
// look for other architectures in the Dir path, and fail if it finds it
foreach (KeyValuePair<UnrealArch, string[]> Pair in AllFilterArchNames)
{
if (Pair.Key != Arch)
{
foreach (string ArchName in Pair.Value)
{
// if there's a directory in the path with a bad architecture name, reject it
if (Regex.IsMatch(Dir, "/" + ArchName + "$") || Regex.IsMatch(Dir, "/" + ArchName + "/") || Regex.IsMatch(Dir, "/" + ArchName + "_API[0-9]+_NDK[0-9]+", RegexOptions.IgnoreCase))
{
return false;
}
}
}
}
// if nothing was found, we are okay
return true;
}
static bool ShouldSkipModule(string ModuleName, UnrealArch Arch)
{
foreach (string ModName in ModulesToSkip[Arch])
{
if (ModName == ModuleName)
{
return true;
}
}
// if nothing was found, we are okay
return false;
}
bool ShouldSkipLib(string FullLib, UnrealArch Arch)
{
// strip any absolute path
string Lib = Path.GetFileNameWithoutExtension(FullLib);
if (Lib.StartsWith("lib"))
{
Lib = Lib.Substring(3);
}
// reject any libs we outright don't want to link with
foreach (string LibName in LibrariesToSkip[Arch])
{
if (LibName == Lib)
{
return true;
}
}
// deal with .so files with wrong architecture
if (Path.GetExtension(FullLib) == ".so")
{
string ParentDirectory = Path.GetDirectoryName(FullLib)!;
if (!IsDirectoryForArch(ParentDirectory, Arch))
{
return true;
}
}
// apply the same directory filtering to libraries as we do to additional library paths
if (!IsDirectoryForArch(Path.GetDirectoryName(FullLib)!, Arch))
{
return true;
}
// if another architecture is in the filename, reject it
foreach (KeyValuePair<UnrealArch, string> ComboName in AllCpuSuffixes)
{
if (ComboName.Key != Arch)
{
string ArchitectureName = ComboName.Key.ToString();
if (Lib.EndsWith(ComboName.Value) || Lib.EndsWith(ArchitectureName))
{
return true;
}
}
}
// if nothing was found, we are okay
return false;
}
private static string GetNativeGluePath()
{
return Environment.GetEnvironmentVariable("NDKROOT") + "/sources/android/native_app_glue/android_native_app_glue.c";
}
private static string GetCpuFeaturesPath()
{
return Environment.GetEnvironmentVariable("NDKROOT") + "/sources/android/cpufeatures/cpu-features.c";
}
public override CppCompileEnvironment CreateSharedResponseFile(CppCompileEnvironment CompileEnvironment, FileReference OutResponseFile, IActionGraphBuilder Graph)
{
// Seems like Android clang toolchain does not handle response files including response files
return CompileEnvironment;
}
void GenerateEmptyLinkFunctionsForRemovedModules(List<FileItem> SourceFiles, UnrealArch Arch, string ModuleName, DirectoryReference OutputDirectory, IActionGraphBuilder Graph, ILogger Logger)
{
// Only add to Launch module
if (!ModuleName.Equals("Launch"))
{
return;
}
string LinkerExceptionsName = "../UELinkerExceptions";
FileReference LinkerExceptionsCPPFilename = FileReference.Combine(OutputDirectory, LinkerExceptionsName + ".cpp");
List<string> Result = new List<string>();
Result.Add("#include \"CoreTypes.h\"");
Result.Add("");
if (Arch == UnrealArch.X64)
{
Result.Add("#if PLATFORM_ANDROID_X64");
}
else
{
Result.Add("#if PLATFORM_ANDROID_ARM64");
}
foreach (string ModName in ModulesToSkip[Arch])
{
Result.Add(" void EmptyLinkFunctionForStaticInitialization" + ModName + "(){}");
}
foreach (string ModName in GeneratedModulesToSkip[Arch])
{
Result.Add(" void EmptyLinkFunctionForGeneratedCode" + ModName + "(){}");
}
Result.Add("#endif");
Graph.CreateIntermediateTextFile(LinkerExceptionsCPPFilename, Result);
SourceFiles.Add(FileItem.GetItemByFileReference(LinkerExceptionsCPPFilename));
}
// cache the location of NDK tools
protected static string? ClangPath;
protected static string ToolchainParamsArm64 = "";
protected static string ToolchainParamsx64 = "";
protected static string ToolchainLinkParamsArm64 = "";
protected static string ToolchainLinkParamsx64 = "";
protected static string? ArPathArm64;
protected static string? ArPathx64;
protected static string? ReadElfPath;
protected static string? ObjDumpPath;
public static string GetStripExecutablePath(UnrealArch UnrealArch)
{
string StripPath = ArPathArm64!;
if (UnrealArch == UnrealArch.X64)
{
StripPath = ArPathx64!;
}
return StripPath.Replace("-ar", "-strip");
}
public static string GetReadElfExecutablePath(UnrealArch UnrealArch)
{
return ReadElfPath!;
}
public static string GetObjDumpExecutablePath(UnrealArch UnrealArch)
{
return ObjDumpPath!;
}
private HashSet<UnrealArch> HasHandledLaunchModule = new();
private HashSet<UnrealArch> HasHandledCoreModule = new();
protected override CPPOutput CompileCPPFiles(CppCompileEnvironment CompileEnvironment, IEnumerable<FileItem> InputFiles, DirectoryReference OutputDir, string ModuleName, IActionGraphBuilder Graph)
{
if (ShouldSkipCompile(CompileEnvironment) || ShouldSkipModule(ModuleName, CompileEnvironment.Architecture))
{
return new CPPOutput();
}
List<FileItem> ModifiedInputFiles = new(InputFiles);
// Deal with Launch module special if first time seen
if (!HasHandledLaunchModule.Contains(CompileEnvironment.Architecture) && (ModuleName.Equals("Launch") || ModuleName.Equals("AndroidLauncher")))
{
// Directly added NDK files for NDK extensions
ModifiedInputFiles.Add(FileItem.GetItemByPath(GetNativeGluePath()));
// Deal with dynamic modules removed by architecture
GenerateEmptyLinkFunctionsForRemovedModules(ModifiedInputFiles, CompileEnvironment.Architecture, ModuleName, OutputDir, Graph, Logger);
HasHandledLaunchModule.Add(CompileEnvironment.Architecture);
}
if (!HasHandledCoreModule.Contains(CompileEnvironment.Architecture) && ModuleName.Equals("Core") && (CompileEnvironment.PrecompiledHeaderAction == PrecompiledHeaderAction.None))
{
// This is used by Crypto code in Core
ModifiedInputFiles.Add(FileItem.GetItemByPath(GetCpuFeaturesPath()));
HasHandledCoreModule.Add(CompileEnvironment.Architecture);
}
return base.CompileCPPFiles(CompileEnvironment, ModifiedInputFiles, OutputDir, ModuleName, Graph);
}
public static string InlineArchName(string Pathname, UnrealArch Arch, bool bUseShortNames = false)
{
string FinalArch = "-" + Arch.ToString().ToLower();
if (bUseShortNames)
{
FinalArch = ShortArchNames[FinalArch];
}
return Path.Combine(Path.GetDirectoryName(Pathname)!, Path.GetFileNameWithoutExtension(Pathname) + FinalArch + Path.GetExtension(Pathname));
}
public string RemoveArchName(string Pathname)
{
// remove all architecture names
foreach (string Arch in AllCpuSuffixes.Values)
{
Pathname = Path.Combine(Path.GetDirectoryName(Pathname)!, Path.GetFileName(Pathname).Replace(Arch, ""));
}
return Pathname;
}
public static DirectoryReference InlineArchIncludeFolder(DirectoryReference PathRef, UnrealArch Arch)
{
return DirectoryReference.Combine(PathRef, "include", Arch.ToString());
}
public override FileItem? LinkFiles(LinkEnvironment LinkEnvironment, bool bBuildImportLibraryOnly, IActionGraphBuilder Graph)
{
if (!LinkEnvironment.bIsBuildingLibrary)
{
// @todo Lumin: will this add them multiple times?
ModifyLibraries(LinkEnvironment);
}
UnrealArch Arch = LinkEnvironment.Architecture;
// Create an action that invokes the linker.
Action LinkAction = Graph.CreateAction(ActionType.Link);
LinkAction.RootPaths = LinkEnvironment.RootPaths;
LinkAction.WorkingDirectory = Unreal.EngineSourceDirectory;
if (LinkEnvironment.bIsBuildingLibrary)
{
if (Arch == UnrealArch.Arm64)
{
LinkAction.CommandPath = new FileReference(ArPathArm64!);
}
else
{
LinkAction.CommandPath = new FileReference(ArPathx64!);
}
}
else
{
LinkAction.CommandPath = new FileReference(ClangPath!);
}
DirectoryReference LinkerPath = LinkAction.WorkingDirectory;
LinkAction.WorkingDirectory = LinkEnvironment.IntermediateDirectory!;
// Get link arguments.
LinkAction.CommandArguments = LinkEnvironment.bIsBuildingLibrary ? GetArArguments(LinkEnvironment) : GetLinkArguments(LinkEnvironment, Arch);
// Add the output file as a production of the link action.
FileItem OutputFile;
OutputFile = FileItem.GetItemByFileReference(LinkEnvironment.OutputFilePaths.Where(x => x.FullName.Contains(LinkEnvironment.Architecture.ToString(), StringComparison.InvariantCultureIgnoreCase)).First());
LinkAction.ProducedItems.Add(OutputFile);
LinkAction.StatusDescription = String.Format("{0}", Path.GetFileName(OutputFile.AbsolutePath));
LinkAction.CommandVersion = AndroidClangBuild!;
// LinkAction.bPrintDebugInfo = true;
// Add the output file to the command-line.
if (LinkEnvironment.bIsBuildingLibrary)
{
LinkAction.CommandArguments += String.Format(" \"{0}\"", OutputFile.AbsolutePath);
}
else
{
LinkAction.CommandArguments += String.Format(" -o \"{0}\"", OutputFile.AbsolutePath);
}
if (LinkEnvironment.bAllowLTCG && 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;
}
// 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 InputPath;
if (InputFile.Location.IsUnderDirectory(LinkEnvironment.IntermediateDirectory!))
{
InputPath = InputFile.Location.MakeRelativeTo(LinkEnvironment.IntermediateDirectory!);
}
else
{
InputPath = InputFile.Location.FullName;
}
InputFileNames.Add(String.Format("\"{0}\"", InputPath.Replace('\\', '/')));
LinkAction.PrerequisiteItems.Add(InputFile);
}
string LinkResponseArguments = "";
// libs don't link in other libs
if (!LinkEnvironment.bIsBuildingLibrary)
{
// Make a list of library paths to search
List<string> AdditionalLibraryPaths = new List<string>();
List<string> AdditionalLibraries = new List<string>();
// Add the library paths to the additional path list
foreach (DirectoryReference LibraryPath in LinkEnvironment.SystemLibraryPaths)
{
// LinkerPaths could be relative or absolute
string AbsoluteLibraryPath = Utils.ExpandVariables(LibraryPath.FullName);
if (IsDirectoryForArch(AbsoluteLibraryPath, Arch))
{
// environment variables aren't expanded when using the $( style
if (Path.IsPathRooted(AbsoluteLibraryPath) == false)
{
AbsoluteLibraryPath = Path.Combine(LinkerPath.FullName, AbsoluteLibraryPath);
}
AbsoluteLibraryPath = Utils.CollapseRelativeDirectories(AbsoluteLibraryPath);
if (!AdditionalLibraryPaths.Contains(AbsoluteLibraryPath))
{
AdditionalLibraryPaths.Add(AbsoluteLibraryPath);
}
}
}
// discover additional libraries and their paths
foreach (string SystemLibrary in LinkEnvironment.SystemLibraries)
{
if (!ShouldSkipLib(SystemLibrary, Arch))
{
if (String.IsNullOrEmpty(Path.GetDirectoryName(SystemLibrary)))
{
if (SystemLibrary.StartsWith("lib"))
{
AdditionalLibraries.Add(SystemLibrary);
}
else
{
AdditionalLibraries.Add("lib" + SystemLibrary);
}
}
}
}
foreach (FileReference Library in LinkEnvironment.Libraries)
{
if (!ShouldSkipLib(Library.FullName, Arch))
{
string AbsoluteLibraryPath = Path.GetDirectoryName(Library.FullName)!;
LinkAction.PrerequisiteItems.Add(FileItem.GetItemByFileReference(Library));
string Lib = Path.GetFileNameWithoutExtension(Library.FullName);
if (Lib.StartsWith("lib"))
{
AdditionalLibraries.Add(Lib);
if (!AdditionalLibraryPaths.Contains(AbsoluteLibraryPath))
{
AdditionalLibraryPaths.Add(AbsoluteLibraryPath);
}
}
else
{
AdditionalLibraries.Add(AbsoluteLibraryPath);
}
if (!DisableLibCppSharedDependencyValidation() && ReadElfPath != null)
{
string? Output = Utils.RunLocalProcessAndReturnStdOut(ReadElfPath, "--dynamic \"" + Library.FullName + "\"");
if (Output != null)
{
if (Output.Contains("libc++_shared.so"))
{
if (IsNewNDKModel())
{
throw new BuildException("Lib {0} depends on libc++_shared.so. There are known incompatibility issues when linking libc++_shared.so with Unreal Engine built with NDK22+." +
" Please rebuild your dependencies with static libc++!", Lib);
}
else
{
Logger.LogWarning("Lib {LibName} depends on libc++_shared.so. Unreal Engine is designed to be linked with libs that are built against static libc++ only. Please rebuild your dependencies with static libc++!", Lib);
}
}
}
}
}
}
// add the library paths to response
foreach (string LibaryPath in AdditionalLibraryPaths)
{
LinkResponseArguments += String.Format(" -L\"{0}\"", LibaryPath);
}
// add libraries in a library group
LinkResponseArguments += String.Format(" -Wl,--start-group");
foreach (string AdditionalLibrary in AdditionalLibraries)
{
if (AdditionalLibrary.StartsWith("lib"))
{
LinkResponseArguments += String.Format(" \"-l{0}\"", AdditionalLibrary.Substring(3));
}
else
{
LinkResponseArguments += String.Format(" \"{0}\"", AdditionalLibrary);
}
}
LinkResponseArguments += String.Format(" -Wl,--end-group");
// Write the MAP file to the output directory.
if (LinkEnvironment.bCreateMapFile)
{
FileReference MAPFilePath = FileReference.Combine(LinkEnvironment.OutputDirectory!, Path.GetFileNameWithoutExtension(OutputFile.AbsolutePath) + ".map");
FileItem MAPFile = FileItem.GetItemByFileReference(MAPFilePath);
LinkResponseArguments += String.Format(" -Wl,--cref -Wl,-Map,\"{0}\"", MAPFilePath);
LinkAction.ProducedItems.Add(MAPFile);
// Export a list of object file paths, so we can locate the object files referenced by the map file
ExportObjectFilePaths(LinkEnvironment, Path.ChangeExtension(MAPFilePath.FullName, ".objpaths"));
}
}
// Add the additional arguments specified by the environment.
LinkResponseArguments += LinkEnvironment.AdditionalArguments;
// Write out a response file
FileReference ResponseFileName = GetResponseFileName(LinkEnvironment, OutputFile);
InputFileNames.Add(LinkResponseArguments.Replace("\\", "/"));
FileItem ResponseFileItem = Graph.CreateIntermediateTextFile(ResponseFileName, InputFileNames);
LinkAction.CommandArguments += String.Format(" @\"{0}\"", ResponseFileName);
LinkAction.PrerequisiteItems.Add(ResponseFileItem);
// Fix up the paths in commandline
LinkAction.CommandArguments = LinkAction.CommandArguments.Replace("\\", "/");
// Only execute linking on the local PC.
LinkAction.bCanExecuteRemotely = false;
string VersionScriptFileItem = GetVersionScriptFilename(LinkEnvironment);
LinkAction.PrerequisiteItems.Add(FileItem.GetItemByPath(VersionScriptFileItem));
Logger.LogInformation("Link: {LinkActionCommandPathFullName} {LinkActionCommandArguments}", LinkAction.CommandPath.FullName, LinkAction.CommandArguments);
// Windows can run into an issue with too long of a commandline when clang tries to call ld to link.
// To work around this we call clang to just get the command it would execute and generate a
// second response file to directly call ld with the right arguments instead of calling through clang.
/* disable while tracking down some linker errors this introduces
if (OperatingSystem.IsWindows())
{
// capture the actual link command without running it
ProcessStartInfo StartInfo = new ProcessStartInfo();
StartInfo.WorkingDirectory = LinkEnvironment.IntermediateDirectory.FullName;
StartInfo.FileName = LinkAction.CommandPath;
StartInfo.Arguments = "-### " + LinkAction.CommandArguments;
StartInfo.UseShellExecute = false;
StartInfo.CreateNoWindow = true;
StartInfo.RedirectStandardError = true;
LinkerCommandline = "";
Process Proc = new Process();
Proc.StartInfo = StartInfo;
Proc.ErrorDataReceived += new DataReceivedEventHandler(OutputReceivedForLinker);
Proc.Start();
Proc.BeginErrorReadLine();
Proc.WaitForExit(5000);
LinkerCommandline = LinkerCommandline.Trim();
// the command should be in quotes; if not we'll just use clang to link as usual
int FirstQuoteIndex = LinkerCommandline.IndexOf('"');
if (FirstQuoteIndex >= 0)
{
int SecondQuoteIndex = LinkerCommandline.Substring(FirstQuoteIndex + 1).IndexOf('"');
if (SecondQuoteIndex >= 0)
{
LinkAction.CommandPath = LinkerCommandline.Substring(FirstQuoteIndex + 1, SecondQuoteIndex - FirstQuoteIndex);
LinkAction.CommandArguments = LinkerCommandline.Substring(FirstQuoteIndex + SecondQuoteIndex + 3);
// replace double backslashes
LinkAction.CommandPath = LinkAction.CommandPath.Replace("\\\\", "/");
// now create a response file for the full command using ld directly
FileReference FinalResponseFileName = FileReference.Combine(LinkEnvironment.IntermediateDirectory, OutputFile.Location.GetFileName() + ".responseFinal");
FileItem FinalResponseFileItem = Graph.CreateIntermediateTextFile(FinalResponseFileName, LinkAction.CommandArguments);
LinkAction.CommandArguments = string.Format("@\"{0}\"", FinalResponseFileName);
LinkAction.PrerequisiteItems.Add(FinalResponseFileItem);
}
}
}
*/
return OutputFile;
}
// captures stderr from clang
private static string LinkerCommandline = "";
public static void OutputReceivedForLinker(object Sender, DataReceivedEventArgs Line)
{
if ((Line != null) && (Line.Data != null) && (Line.Data.Contains("--sysroot")))
{
LinkerCommandline += Line.Data;
}
}
private void ExportObjectFilePaths(LinkEnvironment LinkEnvironment, string FileName)
{
// Write the list of object file directories
HashSet<DirectoryReference> ObjectFileDirectories = new HashSet<DirectoryReference>();
foreach (FileItem InputFile in LinkEnvironment.InputFiles)
{
ObjectFileDirectories.Add(InputFile.Location.Directory);
}
foreach (FileReference Library in LinkEnvironment.Libraries)
{
ObjectFileDirectories.Add(Library.Directory);
}
foreach (DirectoryReference LibraryPath in LinkEnvironment.SystemLibraryPaths)
{
ObjectFileDirectories.Add(LibraryPath);
}
foreach (string LibraryPath in (Environment.GetEnvironmentVariable("LIB") ?? "").Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
ObjectFileDirectories.Add(new DirectoryReference(LibraryPath));
}
Directory.CreateDirectory(Path.GetDirectoryName(FileName)!);
File.WriteAllLines(FileName, ObjectFileDirectories.Select(x => x.FullName).OrderBy(x => x).ToArray());
}
public override void ModifyBuildProducts(ReadOnlyTargetRules Target, UEBuildBinary Binary, IEnumerable<string> Libraries, IEnumerable<UEBuildBundleResource> BundleResources, Dictionary<FileReference, BuildProductType> BuildProducts)
{
// only the .so needs to be in the manifest; we always have to build the apk since its contents depend on the project
/*
// the binary will have all of the .so's in the output files, we need to trim down to the shared apk (which is what needs to go into the manifest)
if (Target.bDeployAfterCompile && Binary.Config.Type != UEBuildBinaryType.StaticLibrary)
{
foreach (FileReference BinaryPath in Binary.Config.OutputFilePaths)
{
FileReference ApkFile = BinaryPath.ChangeExtension(".apk");
BuildProducts.Add(ApkFile, BuildProductType.Package);
}
}
*/
}
public static void OutputReceivedDataEventHandler(object Sender, DataReceivedEventArgs Line, ILogger Logger)
{
if ((Line != null) && (Line.Data != null))
{
Logger.LogInformation("{Output}", Line.Data);
}
}
public virtual string GetStripPath(FileReference SourceFile)
{
string StripExe;
if (SourceFile.FullName.Contains("-arm64"))
{
StripExe = ArPathArm64!;
}
else
if (SourceFile.FullName.Contains("-x64"))
{
StripExe = ArPathx64!;
}
else
{
throw new BuildException("Couldn't determine Android architecture to strip symbols from {0}", SourceFile.FullName);
}
// fix the executable (replace the last -ar with -strip and keep any extension)
int ArIndex = StripExe.LastIndexOf("-ar");
StripExe = StripExe.Substring(0, ArIndex) + "-strip" + StripExe.Substring(ArIndex + 3);
return StripExe;
}
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 = GetStripPath(SourceFile).Trim('"');
StartInfo.Arguments = " --strip-debug \"" + TargetFile.FullName + "\"";
StartInfo.UseShellExecute = false;
StartInfo.CreateNoWindow = true;
Utils.RunLocalProcessAndLogOutput(StartInfo, Logger);
}
public ClangSanitizer BuildWithSanitizer()
{
if (Options.HasFlag(ClangToolChainOptions.EnableAddressSanitizer))
{
return ClangSanitizer.Address;
}
else if (Options.HasFlag(ClangToolChainOptions.EnableHWAddressSanitizer))
{
return ClangSanitizer.HwAddress;
}
else if (Options.HasFlag(ClangToolChainOptions.EnableThreadSanitizer))
{
return ClangSanitizer.Thread;
}
else if (Options.HasFlag(ClangToolChainOptions.EnableUndefinedBehaviorSanitizer))
{
return ClangSanitizer.UndefinedBehavior;
}
else if (Options.HasFlag(ClangToolChainOptions.EnableMinimalUndefinedBehaviorSanitizer))
{
return ClangSanitizer.UndefinedBehaviorMinimal;
}
return ClangSanitizer.None;
}
};
}