// 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 AllCpuSuffixes = new() { { UnrealArch.Arm64, "-arm64" }, { UnrealArch.X64, "-x64" }, }; // short names for the above suffixes public static readonly Dictionary ShortArchNames = new Dictionary() { { "", "" }, { "-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 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 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 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 ModulesToSkip = new() { { UnrealArch.Arm64, Array.Empty() }, { UnrealArch.X64, new string[] { "OnlineSubsystemOculus", "OculusHMD", "OculusMR" } } }; private static Dictionary GeneratedModulesToSkip = new() { { UnrealArch.Arm64, Array.Empty() }, { 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 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 Arguments) { // remove paths that are not meant for this architecture // @todo can remove this when we only add paths properly for architecture IEnumerable 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))); } /// protected override string EscapePreprocessorDefinition(string Definition) { return Definition.Contains('"') ? Definition.Replace("\"", "\\\"") : Definition; } /// protected override void GetCompileArguments_WarningsAndErrors(CppCompileEnvironment CompileEnvironment, List 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 } /// protected override void GetCompileArguments_Optimizations(CppCompileEnvironment CompileEnvironment, List 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"); } /// protected override void GetCompileArguments_Architecture(CppCompileEnvironment CompileEnvironment, List 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"); } } /// protected override void GetCompileArguments_Debugging(CppCompileEnvironment CompileEnvironment, List 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 } } /// protected override void GetCompilerArguments_Sanitizers(CppCompileEnvironment CompileEnvironment, List 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"); //} } /// protected override void GetCompileArguments_Global(CppCompileEnvironment CompileEnvironment, List 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 } /// protected override FileItem GetCompileArguments_FileType(CppCompileEnvironment CompileEnvironment, FileItem SourceFile, DirectoryReference OutputDir, List 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 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 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 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 Result = new List(); 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 HasHandledLaunchModule = new(); private HashSet HasHandledCoreModule = new(); protected override CPPOutput CompileCPPFiles(CppCompileEnvironment CompileEnvironment, IEnumerable InputFiles, DirectoryReference OutputDir, string ModuleName, IActionGraphBuilder Graph) { if (ShouldSkipCompile(CompileEnvironment) || ShouldSkipModule(ModuleName, CompileEnvironment.Architecture)) { return new CPPOutput(); } List 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 InputFileNames = new List(); 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 AdditionalLibraryPaths = new List(); List AdditionalLibraries = new List(); // 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 ObjectFileDirectories = new HashSet(); 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 Libraries, IEnumerable BundleResources, Dictionary 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; } }; }