diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aot.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aot.cs index acc5fef88ca..246fc8bd50a 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Aot.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aot.cs @@ -84,15 +84,6 @@ public class Aot : AndroidAsyncTask AotMode AotMode; SequencePointsMode sequencePointsMode; - public override bool RunTask () - { - // NdkUtil must always be initialized - once per thread - if (!NdkUtil.Init (LogCodedError, AndroidNdkDirectory)) - return false; - - return base.RunTask (); - } - public static bool GetAndroidAotMode(string androidAotMode, out AotMode aotMode) { aotMode = AotMode.Normal; @@ -138,7 +129,7 @@ public static bool TryGetSequencePointsMode (string value, out SequencePointsMod return false; } - static string GetNdkToolchainLibraryDir(string binDir, string archDir = null) + static string GetNdkToolchainLibraryDir (NdkTools ndk, string binDir, string archDir = null) { var baseDir = Path.GetFullPath(Path.Combine(binDir, "..")); @@ -154,7 +145,7 @@ static string GetNdkToolchainLibraryDir(string binDir, string archDir = null) goto no_toolchain_error; } - if (NdkUtil.UsingClangNDK) + if (ndk.UsesClang) return libPath; gccLibDir = Directory.EnumerateDirectories(libPath).ToList(); @@ -171,9 +162,9 @@ static string GetNdkToolchainLibraryDir(string binDir, string archDir = null) throw new Exception("Could not find a valid NDK compiler toolchain library path"); } - static string GetNdkToolchainLibraryDir (string binDir, AndroidTargetArch arch) + static string GetNdkToolchainLibraryDir (NdkTools ndk, string binDir, AndroidTargetArch arch) { - return GetNdkToolchainLibraryDir (binDir, NdkUtil.GetArchDirName (arch)); + return GetNdkToolchainLibraryDir (ndk, binDir, ndk.GetArchDirName (arch)); } static string QuoteFileName(string fileName) @@ -183,7 +174,7 @@ static string QuoteFileName(string fileName) return builder.ToString(); } - int GetNdkApiLevel(string androidNdkPath, string androidApiLevel, AndroidTargetArch arch) + int GetNdkApiLevel (NdkTools ndk, string androidApiLevel, AndroidTargetArch arch) { var manifest = AndroidAppManifest.Load (ManifestFile.ItemSpec, MonoAndroidHelper.SupportedVersions); @@ -209,7 +200,7 @@ int GetNdkApiLevel(string androidNdkPath, string androidApiLevel, AndroidTargetA else if (level == 23) level = 21; // API levels below level 21 do not provide support for 64-bit architectures. - if (NdkUtil.IsNdk64BitArch(arch) && level < 21) { + if (ndk.IsNdk64BitArch (arch) && level < 21) { level = 21; } @@ -217,7 +208,7 @@ int GetNdkApiLevel(string androidNdkPath, string androidApiLevel, AndroidTargetA // mapping above and we do not want to crash needlessly. for (; level >= 5; level--) { try { - NdkUtil.GetNdkPlatformLibPath (androidNdkPath, arch, level); + ndk.GetDirectoryPath (NdkToolchainDir.PlatformLib, arch, level); break; } catch (InvalidOperationException ex) { // Path not found, continue searching... @@ -230,10 +221,9 @@ int GetNdkApiLevel(string androidNdkPath, string androidApiLevel, AndroidTargetA public async override System.Threading.Tasks.Task RunTaskAsync () { - // NdkUtil must always be initialized - once per thread - if (!NdkUtil.Init (LogCodedError, AndroidNdkDirectory)) { - LogDebugMessage ("Failed to initialize NdkUtil"); - return; + NdkTools? ndk = NdkTools.Create (AndroidNdkDirectory, Log); + if (ndk == null) { + return; // NdkTools.Create will log appropriate error } bool hasValidAotMode = GetAndroidAotMode (AndroidAotMode, out AotMode); @@ -251,7 +241,7 @@ public async override System.Threading.Tasks.Task RunTaskAsync () var nativeLibs = new List (); - await this.WhenAllWithLock (GetAotConfigs (), + await this.WhenAllWithLock (GetAotConfigs (ndk), (config, lockObject) => { if (!config.Valid) { Cancel (); @@ -277,7 +267,7 @@ await this.WhenAllWithLock (GetAotConfigs (), LogDebugTaskItems (" NativeLibrariesReferences: ", NativeLibrariesReferences); } - IEnumerable GetAotConfigs () + IEnumerable GetAotConfigs (NdkTools ndk) { if (!Directory.Exists (AotOutputDirectory)) Directory.CreateDirectory (AotOutputDirectory); @@ -325,7 +315,7 @@ IEnumerable GetAotConfigs () throw new Exception ("Unsupported Android target architecture ABI: " + abi); } - if (EnableLLVM && !NdkUtil.ValidateNdkPlatform (LogMessage, LogCodedError, AndroidNdkDirectory, arch, enableLLVM:EnableLLVM)) { + if (EnableLLVM && !ndk.ValidateNdkPlatform (LogMessage, LogCodedError, arch, enableLLVM:EnableLLVM)) { yield return Config.Invalid; yield break; } @@ -341,8 +331,8 @@ IEnumerable GetAotConfigs () int level = 0; string toolPrefix = EnableLLVM - ? NdkUtil.GetNdkToolPrefix (AndroidNdkDirectory, arch, level = GetNdkApiLevel (AndroidNdkDirectory, AndroidApiLevel, arch)) - : Path.Combine (AndroidBinUtilsDirectory, $"{NdkUtil.GetArchDirName (arch)}-"); + ? ndk.GetNdkToolPrefixForAOT (arch, level = GetNdkApiLevel (ndk, AndroidApiLevel, arch)) + : Path.Combine (AndroidBinUtilsDirectory, $"{ndk.GetArchDirName (arch)}-"); var toolchainPath = toolPrefix.Substring(0, toolPrefix.LastIndexOf(Path.DirectorySeparatorChar)); var ldFlags = string.Empty; if (EnableLLVM) { @@ -353,25 +343,25 @@ IEnumerable GetAotConfigs () string androidLibPath = string.Empty; try { - androidLibPath = NdkUtil.GetNdkPlatformLibPath(AndroidNdkDirectory, arch, level); + androidLibPath = ndk.GetDirectoryPath (NdkToolchainDir.PlatformLib, arch, level); } catch (InvalidOperationException ex) { Diagnostic.Error (5101, ex.Message); } string toolchainLibDir; - if (NdkUtil.UsingClangNDK) - toolchainLibDir = GetNdkToolchainLibraryDir (toolchainPath, arch); + if (ndk.UsesClang) + toolchainLibDir = GetNdkToolchainLibraryDir (ndk, toolchainPath, arch); else - toolchainLibDir = GetNdkToolchainLibraryDir (toolchainPath); + toolchainLibDir = GetNdkToolchainLibraryDir (ndk, toolchainPath); var libs = new List(); - if (NdkUtil.UsingClangNDK) { + if (ndk.UsesClang) { libs.Add ($"-L{toolchainLibDir.TrimEnd ('\\')}"); libs.Add ($"-L{androidLibPath.TrimEnd ('\\')}"); if (arch == AndroidTargetArch.Arm) { // Needed for -lunwind to work - string compilerLibDir = Path.Combine (toolchainPath, "..", "sysroot", "usr", "lib", NdkUtil.GetArchDirName (arch)); + string compilerLibDir = Path.Combine (toolchainPath, "..", "sysroot", "usr", "lib", ndk.GetArchDirName (arch)); libs.Add ($"-L{compilerLibDir.TrimEnd ('\\')}"); } } @@ -385,7 +375,7 @@ IEnumerable GetAotConfigs () string ldName = String.Empty; if (EnableLLVM) { - ldName = NdkUtil.GetNdkTool (AndroidNdkDirectory, arch, "ld", level); + ldName = ndk.GetToolPath (NdkToolKind.Linker, arch, level); if (!String.IsNullOrEmpty (ldName)) { ldName = Path.GetFileName (ldName); if (ldName.IndexOf ('-') >= 0) { diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs index ff354a468d9..36763a00717 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs @@ -635,8 +635,12 @@ private void AddNativeLibraries (ArchiveFileList files, string [] supportedAbis) return; } - NdkUtil.Init (AndroidNdkDirectory); - string clangDir = NdkUtil.GetClangDeviceLibraryPath (AndroidNdkDirectory); + NdkTools? ndk = NdkTools.Create (AndroidNdkDirectory, Log); + if (ndk == null) { + return; // NdkTools.Create will log appropriate error + } + + string clangDir = ndk.GetClangDeviceLibraryPath (); if (String.IsNullOrEmpty (clangDir)) { LogSanitizerError ($"Unable to find the clang compiler directory. Is NDK installed?"); return; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs index 8b8ff883a20..f7f592e6ce4 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs @@ -168,7 +168,7 @@ IEnumerable GetLinkerConfigs () linkerArgs.Add (QuoteFileName (file)); } - string ld = MonoAndroidHelper.GetExecutablePath (AndroidBinUtilsDirectory, $"{NdkUtil.GetNdkToolchainPrefix (arch, false)}ld"); + string ld = MonoAndroidHelper.GetExecutablePath (AndroidBinUtilsDirectory, $"{NdkTools.GetBinutilsToolchainPrefix (arch)}ld"); yield return new Config { LinkerPath = Path.Combine (AndroidBinUtilsDirectory, ld), LinkerOptions = String.Join (" ", linkerArgs), diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/MakeBundleNativeCodeExternal.cs b/src/Xamarin.Android.Build.Tasks/Tasks/MakeBundleNativeCodeExternal.cs index 36cb78917ae..4d95d17ebb3 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/MakeBundleNativeCodeExternal.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/MakeBundleNativeCodeExternal.cs @@ -28,11 +28,11 @@ public class MakeBundleNativeCodeExternal : AndroidTask [Required] public ITaskItem[] Assemblies { get; set; } - + // Which ABIs to include native libs for [Required] public string [] SupportedAbis { get; set; } - + [Required] public string TempOutputPath { get; set; } @@ -57,11 +57,13 @@ public MakeBundleNativeCodeExternal () public override bool RunTask () { - if (!NdkUtil.Init (Log, AndroidNdkDirectory)) - return false; + NdkTools? ndk = NdkTools.Create (AndroidNdkDirectory, Log); + if (ndk == null) { + return false; // NdkTools.Create will log appropriate error + } try { - return DoExecute (); + return DoExecute (ndk); } catch (XamarinAndroidException e) { Log.LogCodedError (string.Format ("XA{0:0000}", e.Code), e.MessageWithoutCode); if (MonoAndroidHelper.LogInternalExceptions) @@ -72,7 +74,7 @@ public override bool RunTask () return !Log.HasLoggedErrors; } - bool DoExecute () + bool DoExecute (NdkTools ndk) { var results = new List (); string bundlepath = Path.Combine (TempOutputPath, "bundles"); @@ -102,11 +104,11 @@ bool DoExecute () break; } - if (!NdkUtil.ValidateNdkPlatform (Log, AndroidNdkDirectory, arch, enableLLVM: false)) { + if (!ndk.ValidateNdkPlatform (arch, enableLLVM: false)) { return false; } - int level = NdkUtil.GetMinimumApiLevelFor (arch, AndroidNdkDirectory); + int level = ndk.GetMinimumApiLevelFor (arch); var outpath = Path.Combine (bundlepath, abi); if (!Directory.Exists (outpath)) Directory.CreateDirectory (outpath); @@ -140,10 +142,10 @@ bool DoExecute () CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden, }; - string windowsCompilerSwitches = NdkUtil.GetCompilerTargetParameters (AndroidNdkDirectory, arch, level); - var compilerNoQuotes = NdkUtil.GetNdkTool (AndroidNdkDirectory, arch, "gcc", level); + string windowsCompilerSwitches = ndk.GetCompilerTargetParameters (arch, level); + var compilerNoQuotes = ndk.GetToolPath (NdkToolKind.CompilerC, arch, level); var compiler = $"\"{compilerNoQuotes}\" {windowsCompilerSwitches}".Trim (); - var gas = '"' + NdkUtil.GetNdkTool (AndroidNdkDirectory, arch, "as", level) + '"'; + var gas = '"' + ndk.GetToolPath (NdkToolKind.Assembler, arch, level) + '"'; psi.EnvironmentVariables ["CC"] = compiler; psi.EnvironmentVariables ["AS"] = gas; Log.LogDebugMessage ("CC=" + compiler); @@ -167,7 +169,7 @@ bool DoExecute () clb = new CommandLineBuilder (); - // See NdkUtils.GetNdkTool for reasons why + // See NdkToolsWithClangWithPlatforms.ctor for reasons why if (!String.IsNullOrEmpty (windowsCompilerSwitches)) clb.AppendTextUnquoted (windowsCompilerSwitches); @@ -188,14 +190,14 @@ bool DoExecute () clb.AppendFileNameIfNotNull (IncludePath); } - string asmIncludePath = NdkUtil.GetNdkAsmIncludePath (AndroidNdkDirectory, arch, level); + string asmIncludePath = ndk.GetDirectoryPath (NdkToolchainDir.AsmInclude, arch, level); if (!String.IsNullOrEmpty (asmIncludePath)) { clb.AppendSwitch ("-I"); clb.AppendFileNameIfNotNull (asmIncludePath); } clb.AppendSwitch ("-I"); - clb.AppendFileNameIfNotNull (NdkUtil.GetNdkPlatformIncludePath (AndroidNdkDirectory, arch, level)); + clb.AppendFileNameIfNotNull (ndk.GetDirectoryPath (NdkToolchainDir.PlatformInclude, arch, level)); clb.AppendFileNameIfNotNull (Path.Combine (outpath, "temp.c")); Log.LogDebugMessage ("[CC] " + compiler + " " + clb); if (MonoAndroidHelper.RunProcess (compilerNoQuotes, clb.ToString (), OnCcOutputData, OnCcErrorData) != 0) { @@ -216,13 +218,13 @@ bool DoExecute () clb.AppendSwitch ("-o"); clb.AppendFileNameIfNotNull (Path.Combine (outpath, BundleSharedLibraryName)); clb.AppendSwitch ("-L"); - clb.AppendFileNameIfNotNull (NdkUtil.GetNdkPlatformLibPath (AndroidNdkDirectory, arch, level)); + clb.AppendFileNameIfNotNull (ndk.GetDirectoryPath (NdkToolchainDir.PlatformLib, arch, level)); clb.AppendSwitch ("-lc"); clb.AppendSwitch ("-lm"); clb.AppendSwitch ("-ldl"); clb.AppendSwitch ("-llog"); clb.AppendSwitch ("-lz"); // Compress - string ld = NdkUtil.GetNdkTool (AndroidNdkDirectory, arch, "ld", level); + string ld = ndk.GetToolPath (NdkToolKind.Linker, arch, level); Log.LogMessage (MessageImportance.Normal, "[LD] " + ld + " " + clb); if (MonoAndroidHelper.RunProcess (ld, clb.ToString (), OnLdOutputData, OnLdErrorData) != 0) { Log.LogCodedError ("XA5201", Properties.Resources.XA5201, proc.ExitCode); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/NdkUtils.cs b/src/Xamarin.Android.Build.Tasks/Tasks/NdkUtils.cs deleted file mode 100644 index 851e4fb83ad..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Tasks/NdkUtils.cs +++ /dev/null @@ -1,474 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; -using System.Threading; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -using Java.Interop.Tools.Diagnostics; -using Xamarin.Android.Tools; -using Microsoft.Android.Build.Tasks; - -namespace Xamarin.Android.Tasks -{ - public static class NdkUtil - { - // We need it to work fine during our tests which are executed at the same time in various threads. - [ThreadStatic] - static bool usingClangNDK; - - public static bool UsingClangNDK => usingClangNDK; - - public static bool Init (string ndkPath) - { - return Init (delegate { }, ndkPath); // For tests which don't have access to a TaskLoggingHelper - } - - public static bool Init (TaskLoggingHelper log, string ndkPath) => - Init ((c, m) => log.LogCodedError (c, m), ndkPath); - - public static bool Init (Action logError, string ndkPath) - { - Version ndkVersion; - bool hasNdkVersion = GetNdkToolchainRelease (ndkPath ?? "", out ndkVersion); - if (!hasNdkVersion) { - logError ("XA5104", Properties.Resources.XA5104); - return false; - } - - usingClangNDK = ndkVersion.Major >= 19; - - return true; - } - - public static bool ValidateNdkPlatform (TaskLoggingHelper log, string ndkPath, AndroidTargetArch arch, bool enableLLVM) - { - return ValidateNdkPlatform ((m) => log.LogMessage (m), (c, m) => log.LogCodedError (c, m), ndkPath, arch, enableLLVM); - } - - public static bool ValidateNdkPlatform (Action logMessage, Action logError, string ndkPath, AndroidTargetArch arch, bool enableLLVM) - { - if (!UsingClangNDK) - return NdkUtilOld.ValidateNdkPlatform (logMessage, logError, ndkPath, arch, enableLLVM); - - // Check that we have a compatible NDK version for the targeted ABIs. - Version ndkVersion; - bool hasNdkVersion = GetNdkToolchainRelease (ndkPath, out ndkVersion); - - if (hasNdkVersion && ndkVersion.Major < 19) { - logMessage ( - "The detected Android NDK version is incompatible with this version of Xamarin.Android, " + - "please upgrade to NDK r19 or newer."); - } - - return true; - } - - public static string GetNdkToolPrefix (string androidNdkPath, AndroidTargetArch arch, int apiLevel) - { - if (!UsingClangNDK) - return NdkUtilOld.GetNdkToolPrefix (androidNdkPath, arch); - - var path = GetNdkTool (androidNdkPath, arch, "as", apiLevel); - if (path != null) - path = path.Substring (0, path.LastIndexOf ("-") + 1); - return path; - } - - // See the "NDK r19 bug" comment in the "GetNdkTool" method below for explanation of the issue - // this method fixes. - public static string GetCompilerTargetParameters (string androidNdkPath, AndroidTargetArch arch, int apiLevel, bool forCPlusPlus = false) - { - if (!UsingClangNDK || !OS.IsWindows) - return String.Empty; - - string targetPrefix; - string otherParams = String.Empty; - switch (arch) { - case AndroidTargetArch.Arm: - targetPrefix = "--target=armv7a-linux-androideabi"; - break; - - case AndroidTargetArch.Arm64: - targetPrefix = "--target=aarch64-linux-android"; - break; - - case AndroidTargetArch.X86: - targetPrefix = "--target=i686-linux-android"; - otherParams = "-mstackrealign"; - break; - - case AndroidTargetArch.X86_64: - targetPrefix = "--target=x86_64-linux-android"; - break; - - default: - throw new InvalidOperationException ($"Unsupported target architecture {arch}"); - } - - string stdlib = String.Empty; - if (forCPlusPlus) - stdlib = "-stdlib=libc++"; - - return $"{targetPrefix}{apiLevel} {otherParams} -fno-addrsig {stdlib}"; - } - - static string GetToolchainDir (string androidNdkPath) - { - return Path.Combine (androidNdkPath, "toolchains", "llvm", "prebuilt", MonoAndroidHelper.AndroidSdk.AndroidNdkHostPlatform); - } - - public static string GetClangDeviceLibraryPath (string androidNdkPath) - { - if (!UsingClangNDK) - throw new InvalidOperationException ("NDK version with the clang compiler must be used"); - - string toolchainDir = GetToolchainDir (androidNdkPath); - string clangBaseDir = Path.Combine (toolchainDir, "lib64", "clang"); - - if (!Directory.Exists (clangBaseDir)) { - return null; - } - - // There should be just one subdir - clang version - but it's better to be safe than sorry... - foreach (string dir in Directory.EnumerateDirectories (clangBaseDir)) { - if (dir[0] == '.') { - continue; - } - - string libDir = Path.Combine (dir, "lib", "linux"); - if (Directory.Exists (libDir)) { - return libDir; - } - } - - return null; - } - - public static string GetNdkTool (string androidNdkPath, AndroidTargetArch arch, string tool, int apiLevel) - { - if (!UsingClangNDK) - return NdkUtilOld.GetNdkTool (androidNdkPath, arch, tool); - - string toolchainDir = GetToolchainDir (androidNdkPath); - string toolName; - bool forCompiler = false; - - if (String.Compare (tool, "gcc", StringComparison.Ordinal) == 0 || - String.Compare (tool, "clang", StringComparison.Ordinal) == 0) { - forCompiler = true; - toolName = "clang"; - } else if (String.Compare (tool, "g++", StringComparison.Ordinal) == 0 || - String.Compare (tool, "clang++", StringComparison.Ordinal) == 0) { - forCompiler = true; - toolName = "clang++"; - } else - toolName = tool; - - // - // NDK r19 bug. - // - // The llvm toolchain directory contains a selection of shell scripts (both Unix and Windows) - // which call `clang/clang++` with different `-target` parameters depending on both the target - // architecture and API level. For instance, the clang/clang++ compilers targetting aarch64 on API level - // 28 will have the following Unix shell scripts present in the toolchain `bin` directory: - // - // aarch64-linux-android28-clang - // aarch64-linux-android28-clang++ - // - // However, the Windows version of the NDK has a bug where there is only one Windows - // counterpart to the above Unix scripts: - // - // aarch64-linux-android28-clang.cmd - // - // This script, despite its name suggesting that it calls `clang.exe` in fact calls - // `clang++.exe` which breaks compilation of some C programs (including the code generated by - // Mono's mkbundle utility) because `clang++` treats the input as C++. There is no corresponding - // `aarch64-linux-android28-clang++.cmd` and so invocation of `clang.exe` becomes harder and, - // most certainly, non-standard as far as cross-platform NDK compatibility is concerned. - // - // The code below tries to rectify the situation by special-casing the compiler tool handling to - // return path to the actual .exe instead of the CMD. Unfortunately, the caller of this code - // will need to provide the correct parameters for the compilers. - // - string toolchainPrefix; - if (forCompiler) { - if (!OS.IsWindows) - toolchainPrefix = $"{GetNdkToolchainPrefix (arch, true)}{apiLevel}"; - else - toolchainPrefix = String.Empty; - } else - toolchainPrefix = GetNdkToolchainPrefix (arch, false); - - string extension = OS.IsWindows ? ".exe" : String.Empty; - if (forCompiler && OS.IsWindows) - toolName = $"{toolName}{extension}"; - else - toolName = GetPrefixedName (toolName); - - string toolPath = GetToolPath (toolName); - if (String.IsNullOrEmpty (toolPath) && String.Compare ("ld", tool, StringComparison.OrdinalIgnoreCase) == 0) { - // NDK r22 removed arch-prefixed `ld` binary. There exists the unprefixed `ld` binary, from the LLVM - // toolchain, and two binutils linkers - `ld.bfd` and `ld.gold`. Since we will need to keep using - // binutils once NDK removes them, let's use one of the latter. `ld.gold` is the better choice, so we'll - // use it if found - toolPath = GetToolPath (GetPrefixedName ("ld.gold")); - } - - if (!String.IsNullOrEmpty (toolPath)) { - return toolPath; - } - - Diagnostic.Error (5105, Properties.Resources.XA5105, toolName, arch, toolchainDir); - return null; - - string GetPrefixedName (string name) - { - return $"{toolchainPrefix}-{name}{extension}"; - } - - string GetToolPath (string name) - { - string binDir = Path.Combine (toolchainDir, "bin"); - string toolExe = MonoAndroidHelper.GetExecutablePath (binDir, name); - string toolPath = Path.Combine (binDir, toolExe); - if (File.Exists (toolPath)) - return toolPath; - return null; - } - } - - static string GetUnifiedHeadersPath (string androidNdkPath) - { - string preNdk22SysrootIncludeDir = Path.Combine (androidNdkPath, "sysroot", "usr", "include"); - if (Directory.Exists (preNdk22SysrootIncludeDir)) { - return preNdk22SysrootIncludeDir; - } - - return Path.Combine (GetToolchainDir (androidNdkPath), "sysroot", "usr", "include"); - } - - public static string GetArchDirName (AndroidTargetArch arch) - { - switch (arch) { - case AndroidTargetArch.Arm: - return "arm-linux-androideabi"; - - case AndroidTargetArch.Arm64: - return "aarch64-linux-android"; - - case AndroidTargetArch.X86: - return "i686-linux-android"; - - case AndroidTargetArch.X86_64: - return "x86_64-linux-android"; - - default: - throw new InvalidOperationException ($"Unsupported architecture {arch}"); - } - } - - public static string GetNdkAsmIncludePath (string androidNdkPath, AndroidTargetArch arch, int apiLevel) - { - if (!UsingClangNDK) - return NdkUtilOld.GetNdkAsmIncludePath (androidNdkPath, arch, apiLevel); - - string path = GetUnifiedHeadersPath (androidNdkPath); - string archDir = GetArchDirName (arch); - - return Path.Combine (path, archDir); - } - - public static string GetNdkPlatformIncludePath (string androidNdkPath, AndroidTargetArch arch, int apiLevel) - { - if (!UsingClangNDK) - return NdkUtilOld.GetNdkPlatformIncludePath (androidNdkPath, arch, apiLevel); - - string path = GetUnifiedHeadersPath (androidNdkPath); - if (Directory.Exists (path)) - return path; - - throw new InvalidOperationException ($"Android include path not found. Tried: {path}"); - } - - public static string GetNdkPlatformLibPath (string androidNdkPath, AndroidTargetArch arch, int apiLevel) - { - if (!UsingClangNDK) - return NdkUtilOld.GetNdkPlatformLibPath (androidNdkPath, arch, apiLevel); - - var checkedPaths = new List (); - string lib = arch == AndroidTargetArch.X86_64 ? "lib64" : "lib"; - string path = Path.Combine (androidNdkPath, "platforms", $"android-{apiLevel}", $"arch-{GetPlatformArch (arch)}", "usr", lib); - if (!Directory.Exists (path)) { - checkedPaths.Add (path); - path = Path.Combine (GetNdk22OrNewerSysrootDir (androidNdkPath), GetArchDirName (arch), apiLevel.ToString ()); - } - - if (!Directory.Exists (path)) { - checkedPaths.Add (path); - string paths = String.Join ("; ", checkedPaths); - throw new InvalidOperationException ($"Platform library directory for target {arch} and API Level {apiLevel} was not found. Checked paths: {paths}"); - } - return path; - } - - static string GetPlatformArch (AndroidTargetArch arch) - { - if (!UsingClangNDK) - return NdkUtilOld.GetPlatformArch (arch); - - switch (arch) { - case AndroidTargetArch.Arm: - return "arm"; - case AndroidTargetArch.Arm64: - return "arm64"; - case AndroidTargetArch.X86: - return "x86"; - case AndroidTargetArch.X86_64: - return "x86_64"; - } - return null; - } - - public static string GetNdkToolchainPrefix (AndroidTargetArch arch, bool forCompiler) - { - if (!UsingClangNDK) - return NdkUtilOld.GetNdkToolchainPrefix (arch); - - switch (arch) { - case AndroidTargetArch.Arm: - return forCompiler ? "armv7a-linux-androideabi" : "arm-linux-androideabi"; - case AndroidTargetArch.Arm64: - return "aarch64-linux-android"; - case AndroidTargetArch.X86: - return "i686-linux-android"; - case AndroidTargetArch.X86_64: - return "x86_64-linux-android"; - default: - // return empty. Since this method returns the "prefix", the resulting - // tool path just becomes the tool name i.e. "gcc" becomes "gcc". - // This should work for any custom arbitrary platform. - return String.Empty; - } - } - - public static bool GetNdkToolchainRelease (string androidNdkPath, out NdkUtilOld.NdkVersion ndkVersion) - { - return NdkUtilOld.GetNdkToolchainRelease (androidNdkPath, out ndkVersion); - } - - public static bool GetNdkToolchainRelease (string androidNdkPath, out Version ndkVersion) - { - ndkVersion = new Version (); - string sourcePropertiesPath = Path.Combine (androidNdkPath, "source.properties"); - if (!File.Exists (sourcePropertiesPath)) { - return false; - } - - foreach (string l in File.ReadAllLines (sourcePropertiesPath)) { - string line = l.Trim (); - if (!line.StartsWith ("Pkg.Revision", StringComparison.Ordinal)) - continue; - string[] parts = line.Split (new char[] {'='}, 2); - if (parts.Length != 2) - return false; - - if (Version.TryParse (parts [1].Trim (), out ndkVersion)) - return true; - break; - } - - return false; - } - - public static bool IsNdk64BitArch (AndroidTargetArch arch) - { - return arch == AndroidTargetArch.Arm64 || arch == AndroidTargetArch.X86_64; - } - - static string GetNdk22OrNewerSysrootDir (string androidNdkPath) - { - return Path.Combine (GetToolchainDir (androidNdkPath), "sysroot", "usr", "lib"); - } - - public static IEnumerable GetSupportedPlatforms (TaskLoggingHelper log, string androidNdkPath) - { - string preNdk22PlatformsDir = Path.Combine (androidNdkPath, "platforms"); - - if (Directory.Exists (preNdk22PlatformsDir)) { - return GetSupportedPlatformsPreNdk22 (preNdk22PlatformsDir); - } - - // NDK r22 no longer has a single platforms dir. The API level directories are now found in per-arch - // subdirectories under the toolchain directory. We need to examine all of them and compose a list of unique - // API levels (since they are repeated in each per-arch subdirectory, but not all architectures have the - // same set of API levels) - var apiLevels = new HashSet (); - string sysrootLibDir = GetNdk22OrNewerSysrootDir (androidNdkPath); - foreach (AndroidTargetArch targetArch in Enum.GetValues (typeof (AndroidTargetArch))) { - if (targetArch == AndroidTargetArch.None || - targetArch == AndroidTargetArch.Other || - targetArch == AndroidTargetArch.Mips) { - continue; - } - - string archDirName = GetArchDirName (targetArch); - if (String.IsNullOrEmpty (archDirName)) { - log.LogWarning ($"NDK architecture {targetArch} unknown?"); - continue; - } - - string archDir = Path.Combine (sysrootLibDir, archDirName); - if (!Directory.Exists (archDir)) { - log.LogWarning ($"Architecture {targetArch} toolchain directory '{archDir}' not found"); - continue; - } - - foreach (string platform in Directory.EnumerateDirectories (archDir, "*", SearchOption.TopDirectoryOnly)) { - string plibc = Path.Combine (platform, "libc.so"); - if (!File.Exists (plibc)) { - continue; - } - - string pdir = Path.GetFileName (platform); - int api; - if (!Int32.TryParse (pdir, out api) || apiLevels.Contains (api)) { - continue; - } - apiLevels.Add (api); - } - } - - return apiLevels; - } - - static IEnumerable GetSupportedPlatformsPreNdk22 (string platformsDir) - { - foreach (var platform in Directory.EnumerateDirectories (platformsDir)) { - var androidApi = Path.GetFileName (platform); - int api = -1; - if (int.TryParse (androidApi.Replace ("android-", String.Empty), out api)) { - yield return api; - } - } - } - - public static int GetMinimumApiLevelFor (AndroidTargetArch arch, string androidNdkPath) - { - if (!UsingClangNDK) - return NdkUtilOld.GetMinimumApiLevelFor (arch, androidNdkPath); - - int minValue = 0; - string archName = GetPlatformArch (arch); - if (!XABuildConfig.ArchAPILevels.TryGetValue (archName, out minValue)) - throw new InvalidOperationException ($"Unable to determine minimum API level for architecture {arch}"); - - return minValue; - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/NdkUtilsOld.cs b/src/Xamarin.Android.Build.Tasks/Tasks/NdkUtilsOld.cs deleted file mode 100644 index 8469371e9f1..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Tasks/NdkUtilsOld.cs +++ /dev/null @@ -1,314 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -using Java.Interop.Tools.Diagnostics; -using Xamarin.Android.Tools; - -namespace Xamarin.Android.Tasks -{ - public static class NdkUtilOld { - - public static bool ValidateNdkPlatform (Action logMessage, Action logError, string ndkPath, AndroidTargetArch arch, bool enableLLVM) - { - // Check that we have a compatible NDK version for the targeted ABIs. - NdkVersion ndkVersion; - bool hasNdkVersion = GetNdkToolchainRelease (ndkPath, out ndkVersion); - - if (IsNdk64BitArch(arch) && hasNdkVersion && ndkVersion.Version < 10) { - logMessage ( - "The detected Android NDK version is incompatible with the targeted 64-bit architecture, " + - "please upgrade to NDK r10 or newer."); - } - - // NDK r10d is buggy and cannot link x86_64 ABI shared libraries because they are 32-bits. - // See https://code.google.com/p/android/issues/detail?id=161421 - if (enableLLVM && ndkVersion.Version == 10 && ndkVersion.Revision == "d" && arch == AndroidTargetArch.X86_64) { - logError ("XA3004", Properties.Resources.XA3004); - return false; - } - - if (enableLLVM && (ndkVersion.Version < 10 || (ndkVersion.Version == 10 && ndkVersion.Revision[0] < 'd'))) { - logError ("XA3005", Properties.Resources.XA3005); - } - - return true; - } - - public static string GetNdkToolPrefix (string androidNdkPath, AndroidTargetArch arch) - { - var path = GetNdkTool (androidNdkPath, arch, "as"); - if (path != null) - path = path.Substring (0, path.LastIndexOf ("-") + 1); - return path; - } - - public static List GetNdkToolchainPath(string androidNdkPath, AndroidTargetArch arch) - { - var toolchains = GetNdkToolchainDirectories (Path.Combine (androidNdkPath, "toolchains"), arch); - if (!toolchains.Any ()) - Diagnostic.Error (5101, Properties.Resources.XA5101_Toolchain, arch); - // Sort the toolchains paths in reverse so that we prefer the latest versions. - Array.Sort(toolchains); - Array.Reverse(toolchains); - - return new List(toolchains); - } - - public static string GetNdkTool (string androidNdkPath, AndroidTargetArch arch, string tool) - { - var toolchains = GetNdkToolchainPath(androidNdkPath, arch); - string extension = OS.IsWindows ? ".exe" : string.Empty; - List toolPaths = null; - foreach (var platbase in toolchains) { - string path = Path.Combine (platbase, "prebuilt", MonoAndroidHelper.AndroidSdk.AndroidNdkHostPlatform, "bin", GetNdkToolchainPrefix (arch) + tool + extension); - if (File.Exists (path)) - return path; - if (toolPaths == null) - toolPaths = new List(); - toolPaths.Add (path); - } - { - string path = Path.Combine (androidNdkPath, "prebuilt", MonoAndroidHelper.AndroidSdk.AndroidNdkHostPlatform, "bin", tool); - if (File.Exists (path)) - return path; - if (toolPaths == null) - toolPaths = new List(); - toolPaths.Add (path); - } - - Diagnostic.Error (5101, Properties.Resources.XA5101_C_Compiler, arch, string.Join ("; ", toolPaths)); - return null; - } - - static string GetUnifiedHeadersPath (string androidNdkPath) - { - return Path.Combine (androidNdkPath, "sysroot", "usr", "include"); - } - - static string GetPerPlatformHeadersPath (string androidNdkPath, AndroidTargetArch arch, int level) - { - return Path.Combine (androidNdkPath, "platforms", "android-" + level, "arch-" + GetPlatformArch (arch), "usr", "include"); - } - - public static string GetNdkAsmIncludePath (string androidNdkPath, AndroidTargetArch arch, int level) - { - string path = GetPerPlatformHeadersPath (androidNdkPath, arch, level); - if (Directory.Exists (path)) - return null; - - path = GetUnifiedHeadersPath (androidNdkPath); - if (!Directory.Exists (path)) - return null; - - string archDir = null; - switch (arch) { - case AndroidTargetArch.Arm: - archDir = "arm-linux-androideabi"; - break; - - case AndroidTargetArch.Arm64: - archDir = "aarch64-linux-android"; - break; - - case AndroidTargetArch.Mips: - archDir = "mipsel-linux-android"; - break; - - case AndroidTargetArch.X86: - archDir = "i686-linux-android"; - break; - - case AndroidTargetArch.X86_64: - archDir = "x86_64-linux-android"; - break; - } - - if (archDir == null) - return null; - - return Path.Combine (path, archDir); - } - - public static string GetNdkPlatformIncludePath (string androidNdkPath, AndroidTargetArch arch, int level) - { - // This is for NDK older than r16 which isn't configured to use unified headers. We - string path = GetPerPlatformHeadersPath (androidNdkPath, arch, level); - if (!Directory.Exists (path)) { - // This is for NDK r15 (if configured to use unified headers) or NDK r16+ (which doesn't have - // the per-platform includes anymore) - path = GetUnifiedHeadersPath (androidNdkPath); - if (Directory.Exists (path)) - return path; - - throw new InvalidOperationException (String.Format ("Platform header files for target {0} and API Level {1} was not found. Expected path is \"{2}\"", arch, level, path)); - } - - return path; - } - - public static string GetNdkPlatformLibPath (string androidNdkPath, AndroidTargetArch arch, int level) - { - string lib = arch == AndroidTargetArch.X86_64 ? "lib64" : "lib"; - string path = Path.Combine (androidNdkPath, "platforms", "android-" + level, "arch-" + GetPlatformArch (arch), "usr", lib); - if (!Directory.Exists (path)) - throw new InvalidOperationException (String.Format ("Platform library directory for target {0} and API Level {1} was not found. Expected path is \"{2}\"", arch, level, path)); - return path; - } - - public static string GetPlatformArch (AndroidTargetArch arch) - { - switch (arch) { - case AndroidTargetArch.Arm: - return "arm"; - case AndroidTargetArch.Arm64: - return "arm64"; - case AndroidTargetArch.Mips: - return "mips"; - case AndroidTargetArch.X86: - return "x86"; - case AndroidTargetArch.X86_64: - return "x86_64"; - } - return null; - } - - static string[] GetNdkToolchainDirectories (string toolchainsPath, AndroidTargetArch arch) - { - if (!Directory.Exists (toolchainsPath)) - Diagnostic.Error (5101, Properties.Resources.XA5101, toolchainsPath); - switch (arch) { - case AndroidTargetArch.Arm: - return Directory.GetDirectories (toolchainsPath, "arm-linux-androideabi-*"); - case AndroidTargetArch.Arm64: - return Directory.GetDirectories (toolchainsPath, "aarch64-linux-android-*"); - case AndroidTargetArch.X86: - return Directory.GetDirectories (toolchainsPath, "x86-*"); - case AndroidTargetArch.X86_64: - return Directory.GetDirectories (toolchainsPath, "x86_64-*"); - case AndroidTargetArch.Mips: - return Directory.GetDirectories (toolchainsPath, "mipsel-linux-android-*"); - default: // match any directory that contains the arch name. - return Directory.GetDirectories (toolchainsPath, "*" + arch + "*"); - } - } - - public static string GetNdkToolchainPrefix (AndroidTargetArch arch) - { - switch (arch) { - case AndroidTargetArch.Arm: - return "arm-linux-androideabi-"; - case AndroidTargetArch.Arm64: - return "aarch64-linux-android-"; - case AndroidTargetArch.X86: - return "i686-linux-android-"; - case AndroidTargetArch.X86_64: - return "x86_64-linux-android-"; - case AndroidTargetArch.Mips: - return "mipsel-linux-android-"; - default: - // return empty. Since this method returns the "prefix", the resulting - // tool path just becomes the tool name i.e. "gcc" becomes "gcc". - // This should work for any custom arbitrary platform. - return String.Empty; - } - } - - static bool GetNdkToolchainRelease (string androidNdkPath, out string version) - { - var releaseVersionPath = Path.Combine (androidNdkPath, "RELEASE.txt"); - if (!File.Exists (releaseVersionPath)) - { - version = string.Empty; - return false; - } - - version = File.ReadAllText (releaseVersionPath).Trim(); - return true; - } - - static bool GetNdkToolchainSourceProperties (string androidNdkPath, out NdkVersion version) - { - version = new NdkVersion (); - var sourcePropertiesPath = Path.Combine (androidNdkPath, "source.properties"); - if (!File.Exists (sourcePropertiesPath)) { - return false; - } - var match = Regex.Match (File.ReadAllText (sourcePropertiesPath).Trim (), "^Pkg.Revision\\s*=\\s*([.0-9]+)$", RegexOptions.Multiline); - if (!match.Success) { - return false; - } - var numbers = match.Groups[1].Value.Trim().Split ('.'); - version.Version = int.Parse (numbers [0]); - version.Revision = Convert.ToChar (int.Parse (numbers [1]) + (int)'a').ToString (); - return true; - } - - public struct NdkVersion - { - public int Version; - public string Revision; - } - - public static bool GetNdkToolchainRelease (string androidNdkPath, out NdkVersion ndkVersion) - { - ndkVersion = new NdkVersion (); - - string version; - if (!GetNdkToolchainRelease (androidNdkPath, out version)) { - if (GetNdkToolchainSourceProperties (androidNdkPath, out ndkVersion)) - return true; - return false; - } - - var match = Regex.Match(version, @"r(\d+)\s*(.*)\s+.*"); - if( !match.Success) - return false; - - ndkVersion.Version = int.Parse (match.Groups[1].Value.Trim()); - ndkVersion.Revision = match.Groups[2].Value.Trim().ToLowerInvariant(); - - return true; - } - - public static bool IsNdk64BitArch (AndroidTargetArch arch) - { - return arch == AndroidTargetArch.Arm64 || arch == AndroidTargetArch.X86_64; - } - - public static IEnumerable GetSupportedPlatforms (string androidNdkPath) - { - foreach (var platform in Directory.EnumerateDirectories (Path.Combine (androidNdkPath, "platforms"))) { - var androidApi = Path.GetFileName (platform); - int api = -1; - if (int.TryParse (androidApi.Replace ("android-", String.Empty), out api)) { - yield return api; - } - } - } - - static readonly Dictionary archPathMap = new Dictionary () { - { AndroidTargetArch.Arm, "arm"}, - { AndroidTargetArch.Arm64, "arm64"}, - { AndroidTargetArch.Mips, "mips"}, - { AndroidTargetArch.None, "none"}, - { AndroidTargetArch.Other, "other"}, - { AndroidTargetArch.X86, "x86"}, - { AndroidTargetArch.X86_64, "x86_64"}, - }; - - public static int GetMinimumApiLevelFor (AndroidTargetArch arch, string androidNdkPath) - { - var minValue = IsNdk64BitArch (arch) ? 21 : 14; - var platforms = GetSupportedPlatforms (androidNdkPath).OrderBy (x => x).Where (x => x >= minValue); - return platforms.First (x => Directory.Exists (Path.Combine (androidNdkPath, "platforms", $"android-{x}", $"arch-{archPathMap[arch]}"))); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AotTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AotTests.cs index bb8e58a32d1..ffb7b48000e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AotTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AotTests.cs @@ -180,10 +180,8 @@ public void BuildBasicApplicationReleaseProfiledAotWithoutDefaultProfile () return; //NOTE: Windows has shortened paths such as: C:\Users\myuser\ANDROI~3\ndk\PLATFO~1\AN3971~1\arch-x86\usr\lib\libc.so if (checkMinLlvmPath && !IsWindows) { - bool ndk22OrNewer = false; - if (Xamarin.Android.Tasks.NdkUtil.GetNdkToolchainRelease (AndroidNdkPath, out Xamarin.Android.Tasks.NdkUtilOld.NdkVersion ndkVersion)) { - ndk22OrNewer = ndkVersion.Version >= 22; - } + Xamarin.Android.Tasks.NdkTools ndk = Xamarin.Android.Tasks.NdkTools.Create (AndroidNdkPath); + bool ndk22OrNewer = ndk.Version.Main.Major >= 22; // LLVM passes a direct path to libc.so, and we need to use the libc.so // which corresponds to the *minimum* SDK version specified in AndroidManifest.xml diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/NdkUtilTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/NdkUtilTests.cs index 2028ab07cdd..c1947f59a15 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/NdkUtilTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/NdkUtilTests.cs @@ -33,29 +33,32 @@ public void TestNdkUtil () using (var builder = new Builder ()) { var ndkDir = AndroidNdkPath; var sdkDir = AndroidSdkPath; + NdkTools ndk = NdkTools.Create (ndkDir, log); MonoAndroidHelper.AndroidSdk = new AndroidSdkInfo ((arg1, arg2) => { }, sdkDir, ndkDir, AndroidSdkResolver.GetJavaSdkPath ()); - NdkUtil.Init (log, ndkDir); - var platforms = NdkUtil.GetSupportedPlatforms (log, ndkDir); + var platforms = ndk.GetSupportedPlatforms (); Assert.AreNotEqual (0, platforms.Count (), "No platforms found"); var arch = AndroidTargetArch.X86; - Assert.IsTrue (NdkUtil.ValidateNdkPlatform (log, ndkDir, arch, enableLLVM: false)); - Assert.AreEqual (0, errors.Count, "NdkUtil.ValidateNdkPlatform should not have returned false."); - int level = NdkUtil.GetMinimumApiLevelFor (arch, ndkDir); + Assert.IsTrue (ndk.ValidateNdkPlatform (arch, enableLLVM: false)); + Assert.AreEqual (0, errors.Count, "NdkTools.ValidateNdkPlatform should not have returned false."); + int level = ndk.GetMinimumApiLevelFor (arch); int expected = 16; Assert.AreEqual (expected, level, $"Min Api Level for {arch} should be {expected}."); - var compilerNoQuotes = NdkUtil.GetNdkTool (ndkDir, arch, "gcc", level); - Assert.AreEqual (0, errors.Count, "NdkUtil.GetNdkTool should not have errored."); - Assert.NotNull (compilerNoQuotes, "NdkUtil.GetNdkTool returned null."); - var gas = NdkUtil.GetNdkTool (ndkDir, arch, "as", level); - Assert.AreEqual (0, errors.Count, "NdkUtil.GetNdkTool should not have errored."); - Assert.NotNull (gas, "NdkUtil.GetNdkTool returned null."); - var inc = NdkUtil.GetNdkPlatformIncludePath (ndkDir, arch, level); - Assert.NotNull (inc, " NdkUtil.GetNdkPlatformIncludePath should not return null"); - var libPath = NdkUtil.GetNdkPlatformLibPath (ndkDir, arch, level); - Assert.NotNull (libPath, "NdkUtil.GetNdkPlatformLibPath should not return null"); - string ld = NdkUtil.GetNdkTool (ndkDir, arch, "ld", level); - Assert.AreEqual (0, errors.Count, "NdkUtil.GetNdkTool should not have errored."); - Assert.NotNull (ld, "NdkUtil.GetNdkTool returned null."); + var compilerNoQuotes = ndk.GetToolPath (NdkToolKind.CompilerC, arch, level); + Assert.AreEqual (0, errors.Count, "NdkTools.GetToolPath should not have errored."); + Assert.NotNull (compilerNoQuotes, "NdkTools.GetToolPath returned null for NdkToolKind.CompilerC."); + compilerNoQuotes = ndk.GetToolPath (NdkToolKind.CompilerCPlusPlus, arch, level); + Assert.AreEqual (0, errors.Count, "NdkTools.GetToolPath should not have errored."); + Assert.NotNull (compilerNoQuotes, "NdkTools.GetToolPath returned null for NdkToolKind.CompilerCPlusPlus."); + var gas = ndk.GetToolPath (NdkToolKind.Assembler, arch, level); + Assert.AreEqual (0, errors.Count, "NdkTools.GetToolPath should not have errored."); + Assert.NotNull (gas, "NdkTools.GetToolPath returned null for NdkToolKind.Assembler."); + var inc = ndk.GetDirectoryPath (NdkToolchainDir.PlatformInclude, arch, level); + Assert.NotNull (inc, " NdkTools.GetToolPath should not return null for NdkToolchainDir.PlatformInclude"); + var libPath = ndk.GetDirectoryPath (NdkToolchainDir.PlatformLib, arch, level); + Assert.NotNull (libPath, "NdkTools.GetDirectoryPath should not return null for NdkToolchainDir.PlatformLib"); + string ld = ndk.GetToolPath (NdkToolKind.Linker, arch, level); + Assert.AreEqual (0, errors.Count, "NdkTools.GetToolPath should not have errored."); + Assert.NotNull (ld, "NdkTools.GetToolPath returned null for NdkToolKind.Linker."); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs index 478aade9493..1e00bd36d28 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs @@ -345,7 +345,7 @@ public static List GatherEnvironmentFiles (string outputDirectoryRoot, s public static void AssertValidEnvironmentSharedLibrary (string outputDirectoryRoot, string sdkDirectory, string ndkDirectory, string supportedAbis) { - NdkUtil.Init (ndkDirectory); + NdkTools ndk = NdkTools.Create (ndkDirectory); MonoAndroidHelper.AndroidSdk = new AndroidSdkInfo ((arg1, arg2) => {}, sdkDirectory, ndkDirectory, AndroidSdkResolver.GetJavaSdkPath ()); AndroidTargetArch arch; @@ -378,7 +378,7 @@ public static void AssertValidEnvironmentSharedLibrary (string outputDirectoryRo Assert.IsTrue (File.Exists (envSharedLibrary), $"Application environment SharedLibrary '{envSharedLibrary}' must exist"); // API level doesn't matter in this case - AssertSharedLibraryHasRequiredSymbols (envSharedLibrary, NdkUtil.GetNdkTool (ndkDirectory, arch, "readelf", 0)); + AssertSharedLibraryHasRequiredSymbols (envSharedLibrary, ndk.GetToolPath ("readelf", arch, 0)); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NdkToolKind.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NdkToolKind.cs new file mode 100644 index 00000000000..bd3b80b3645 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NdkToolKind.cs @@ -0,0 +1,11 @@ +namespace Xamarin.Android.Tasks +{ + public enum NdkToolKind + { + Assembler, + CompilerC, + CompilerCPlusPlus, + Linker, + Strip, + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NdkToolchainDir.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NdkToolchainDir.cs new file mode 100644 index 00000000000..44c9edb8777 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NdkToolchainDir.cs @@ -0,0 +1,9 @@ +namespace Xamarin.Android.Tasks +{ + public enum NdkToolchainDir + { + AsmInclude, + PlatformInclude, + PlatformLib, + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NdkTools.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NdkTools.cs new file mode 100644 index 00000000000..f4e36e517f3 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NdkTools.cs @@ -0,0 +1,402 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; + +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Utilities; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks +{ + public abstract class NdkTools + { + // Target triples used in various places (tools prefix, include directory naming etc) + static readonly Dictionary archTriples = new Dictionary { + { AndroidTargetArch.Arm, "arm-linux-androideabi" }, + { AndroidTargetArch.Arm64, "aarch64-linux-android" }, + { AndroidTargetArch.X86, "i686-linux-android" }, + { AndroidTargetArch.X86_64, "x86_64-linux-android" }, + }; + + // Architecture names as used by the `platforms/*` directories pre NDK r22 + static readonly Dictionary archPlatforms = new Dictionary { + { AndroidTargetArch.Arm, "arm" }, + { AndroidTargetArch.Arm64, "arm64" }, + { AndroidTargetArch.X86, "x86" }, + { AndroidTargetArch.X86_64, "x86_64" }, + }; + + protected Dictionary NdkToolNames = new Dictionary { + { NdkToolKind.Assembler, "as" }, + { NdkToolKind.Linker, "ld" }, + { NdkToolKind.Strip, "strip" }, + }; + + public NdkVersion Version { get; } + public string NdkRootDirectory { get; } + public bool UsesClang { get; protected set; } + public bool NoBinutils { get; protected set; } + + // We can't use MonoAndroidHelper.AndroidSdk.AndroidNdkHostPlatform here since it's + // not initialized while running tests and attempts to use cause a NREX + // We could call `MonoAndroidHelper.RefreshSupportedVersions` but since this NdkTools + // instance already knows the location of NDK, it would be just a waste of time. + protected string HostPlatform => GetNdkHostPlatform (); + protected bool IsWindows => OS.IsWindows; + + protected TaskLoggingHelper? Log { get; } + + protected NdkTools (string androidNdkPath, NdkVersion version, TaskLoggingHelper? log = null) + { + if (String.IsNullOrEmpty (androidNdkPath)) { + throw new ArgumentException ("must be a non-empty string", nameof (androidNdkPath)); + } + + Log = log; + NdkRootDirectory = androidNdkPath; + Version = version; + } + + public static NdkTools? Create (string androidNdkPath, TaskLoggingHelper? log = null) + { + if (String.IsNullOrEmpty (androidNdkPath)) { + log?.LogCodedError ("XA5104", Properties.Resources.XA5104); + return null; + } + + if (!Directory.Exists (androidNdkPath)) { + log?.LogCodedError ("XA5104", Properties.Resources.XA5104); + return null; + } + + NdkVersion? version = ReadVersion (androidNdkPath, log); + if (version == null) { + return null; + } + + if (version.Main.Major < 14) { + if (log != null) { + log.LogCodedError ("XA5104", Properties.Resources.XA5104); + log.LogDebugMessage ($"Unsupported NDK version {version}"); + } + } else if (version.Main.Major < 16) { + // old, non-clang, no unified headers + return new NdkToolsNoClangNoUnifiedHeaders (androidNdkPath, version, log); + } else if (version.Main.Major < 19) { + // old, non-clang, with unified headers + return new NdkToolsNoClangWithUnifiedHeaders (androidNdkPath, version, log); + } else if (version.Main.Major < 22) { + return new NdkToolsWithClangWithPlatforms (androidNdkPath, version, log); + } else if (version.Main.Major == 22) { + return new NdkToolsWithClangNoPlatforms (androidNdkPath, version, log); + } + + return new NdkToolsWithClangNoBinutils (androidNdkPath, version, log); + } + + public abstract string GetToolPath (NdkToolKind kind, AndroidTargetArch arch, int apiLevel); + public abstract string GetToolPath (string name, AndroidTargetArch arch, int apiLevel); + public abstract int GetMinimumApiLevelFor (AndroidTargetArch arch); + public abstract bool ValidateNdkPlatform (Action logMessage, Action logError, AndroidTargetArch arch, bool enableLLVM); + + public bool ValidateNdkPlatform (AndroidTargetArch arch, bool enableLLVM) + { + return ValidateNdkPlatform ((m) => Log?.LogMessage (m), (c, m) => Log?.LogCodedError (c, m), arch, enableLLVM); + } + + public string GetArchDirName (AndroidTargetArch arch) + { + return GetArchTriple (arch);; + } + + public virtual IEnumerable GetSupportedPlatforms () + { + // This works until NDK r22 + foreach (var platform in Directory.EnumerateDirectories (Path.Combine (NdkRootDirectory, "platforms"))) { + var androidApi = Path.GetFileName (platform); + int api = -1; + if (int.TryParse (androidApi.Replace ("android-", String.Empty), out api)) { + yield return api; + } + } + } + + // This call is very specific as it needs to return full path to the location where the arch-prefixed tools + // reside, but WITHOUT the actual tool name. This is required by Mono's AOT LLVM backend. + public string GetNdkToolPrefixForAOT (AndroidTargetArch arch, int apiLevel) + { + string path = GetToolPath (NdkToolKind.Assembler, arch, apiLevel); + return path.Substring (0, path.LastIndexOf ("-") + 1);; + } + + // Work around for a bug in NDK r19 before its 'c' release. See NdkToolsWithClangWithPlatforms.ctor + public virtual string GetCompilerTargetParameters (AndroidTargetArch arch, int apiLevel, bool forCPlusPlus = false) + { + return String.Empty; + } + + public virtual string GetClangDeviceLibraryPath () + { + throw new NotSupportedException (); + } + + public static string GetBinutilsToolchainPrefix (AndroidTargetArch arch) + { + return $"{GetArchTriple (arch)}-"; + } + + public virtual string GetNdkToolchainPrefix (AndroidTargetArch arch) + { + string triple; + switch (arch) { + case AndroidTargetArch.Arm: + case AndroidTargetArch.Arm64: + case AndroidTargetArch.X86: + case AndroidTargetArch.X86_64: + triple = GetArchTriple (arch); + break; + + default: + // return empty. Since this method returns the "prefix", the resulting + // tool path just becomes the tool name i.e. "gcc" becomes "gcc". + // This should work for any custom arbitrary platform. + return String.Empty; + } + + return $"{triple}-"; + } + + public bool IsNdk64BitArch (AndroidTargetArch arch) + { + return arch == AndroidTargetArch.Arm64 || arch == AndroidTargetArch.X86_64; + } + + public string GetDirectoryPath (NdkToolchainDir dir, AndroidTargetArch arch, int apiLevel) + { + string? path = null; + switch (dir) { + case NdkToolchainDir.AsmInclude: // optional + path = GetAsmIncludeDirPath (arch, apiLevel); + if (String.IsNullOrEmpty (path)) { + return String.Empty; + } + break; + + case NdkToolchainDir.PlatformInclude: + path = GetPlatformIncludeDirPath (arch, apiLevel); + break; + + case NdkToolchainDir.PlatformLib: + path = GetPlatformLibPath (arch, apiLevel); + break; + + default: + throw new InvalidOperationException ($"Unsupported NDK toolchain directory {dir}"); + } + + if (String.IsNullOrEmpty (path)) { + throw new InvalidOperationException ($"NDK toolchain directory {dir} is required"); + } + + return EnsureDirectoryExists (path); + } + + protected virtual string GetAsmIncludeDirPath (AndroidTargetArch arch, int apiLevel) + { + return String.Empty; + } + + protected virtual string GetPlatformLibPath (AndroidTargetArch arch, int apiLevel) + { + // This works until NDK r22 + string libDir = arch == AndroidTargetArch.X86_64 ? "lib64" : "lib"; + return Path.Combine (NdkRootDirectory, "platforms", $"android-{apiLevel}", $"arch-{GetPlatformArch (arch)}", "usr", libDir); + } + + protected abstract string GetPlatformIncludeDirPath (AndroidTargetArch arch, int apiLevel); + + protected string? GetExecutablePath (string toolPath, bool mustExist) + { + string? executablePath = null; + if (IsWindows) { + // We can't just use `File.Exists (toolPath)` since the Windows NDK contains extension-less + // Unix shell scripts which would fail to work if an attempt to execute them would be made. + // + // Also, the NDK r19+ workaround (see NdkToolsWithClangWithPlatforms.ctor) will cause `toolPath` + // here to end with .exe when looking for the compiler and we can save some time by not letting + // `MonoAndroidHelper.GetExecutablePath` iterate over all %PATHEXT% extensions only to return the + // original tool name + if (Path.HasExtension (toolPath) && File.Exists (toolPath)) { + executablePath = toolPath; + } else { + string toolDir = Path.GetDirectoryName (toolPath); + executablePath = Path.Combine (toolDir, MonoAndroidHelper.GetExecutablePath (toolDir, Path.GetFileName (toolPath))); + } + } else if (File.Exists (toolPath)) { + executablePath = toolPath; + } + + if (mustExist && String.IsNullOrEmpty (executablePath)) { + throw new InvalidOperationException ($"Required tool '{toolPath}' not found"); + } + + return executablePath; + } + + protected virtual string GetToolName (NdkToolKind kind) + { + if (!NdkToolNames.TryGetValue (kind, out string? toolName) || String.IsNullOrEmpty (toolName)) { + throw new InvalidOperationException ($"Unsupported NDK tool '{kind}'"); + } + + return toolName; + } + + protected static string GetArchTriple (AndroidTargetArch arch) + { + if (archTriples.TryGetValue (arch, out string? triple) && !String.IsNullOrEmpty (triple)) { + return triple; + } + + throw new InvalidOperationException ($"Unsupported NDK architecture '{arch}'"); + } + + protected static string GetPlatformArch (AndroidTargetArch arch) + { + if (archPlatforms.TryGetValue (arch, out string? name) && !String.IsNullOrEmpty (name)) { + return name; + } + + throw new InvalidOperationException ($"Unsupported NDK architecture '{arch}'"); + } + + protected string EnsureDirectoryExists (string path) + { + if (Directory.Exists (path)) { + return path; + } + + throw new InvalidOperationException ($"Required directory '{path}' not found"); + } + + // This is an ugly compromise to support unified headers with minimum code duplication, because C# doesn't + // support multiple inheritance :( + // + // Unified headers are supported by both clang and non-clang NDKs, so the clang+unified headers NDK class + // (NdkToolsNoClangWithUnifiedHeaders) would have to derive from two classes - one to implement the "with clang" + // option and another to implement the "with unified headers" option. + protected string GetUnifiedHeadersDirPath (string androidNdkPath) + { + return EnsureDirectoryExists (MakeUnifiedHeadersDirPath (androidNdkPath)); + } + + protected virtual string MakeUnifiedHeadersDirPath (string androidNdkPath) + { + return Path.Combine (androidNdkPath, "sysroot", "usr", "include"); + } + + protected string UnifiedHeaders_GetAsmIncludeDirPath (AndroidTargetArch arch, int apiLevel) + { + return Path.Combine (GetUnifiedHeadersDirPath (NdkRootDirectory), GetArchTriple (arch)); + } + + protected string UnifiedHeaders_GetPlatformIncludeDirPath (AndroidTargetArch arch, int apiLevel) + { + return GetUnifiedHeadersDirPath (NdkRootDirectory); + } + + const string platformLinux64 = "linux-x86_64"; + const string platformLinux32 = "linux-x86"; + const string platformMac64 = "darwin-x86_64"; + const string platformMac32 = "darwin-x86"; + const string platformWindows64 = "windows-x86_64"; + const string platformWindows32 = "windows-x86"; + + string GetNdkHostPlatform () + { + bool cannotDeterminePlatform = false; + + if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) { + if (HasPrebuiltDir (platformLinux64)) { + return platformLinux64; + } + + if (HasPrebuiltDir (platformLinux32)) { + return platformLinux32; + } + + cannotDeterminePlatform = true; + } + + if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) { + if (HasPrebuiltDir (platformMac64)) { + return platformMac64; + } + + if (HasPrebuiltDir (platformMac32)) { + return platformMac32; + } + + cannotDeterminePlatform = true; + } + + if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) { + if (HasPrebuiltDir (platformWindows64)) { + return platformWindows64; + } + + if (HasPrebuiltDir (platformWindows32)) { + return platformWindows32; + } + + cannotDeterminePlatform = true; + } + + if (cannotDeterminePlatform) { + throw new InvalidOperationException ($"Unable to determine host NDK platform"); + } + + throw new InvalidOperationException ($"Unsupported OS"); + + bool HasPrebuiltDir (string name) + { + return Directory.Exists (Path.Combine (NdkRootDirectory, "prebuilt", name)); + } + } + + static NdkVersion? ReadVersion (string androidNdkPath, TaskLoggingHelper? log = null) + { + string sourcePropertiesPath = Path.Combine (androidNdkPath, "source.properties"); + if (!File.Exists (sourcePropertiesPath)) { + if (log != null) { + log.LogCodedError ("XA5104", Properties.Resources.XA5104); + log.LogDebugMessage ("Could not read NDK version information, '{sourcePropertiesPath}' not found."); + } + return null; + } + + var splitChars = new char[] {'='}; + string? ver = null; + foreach (string l in File.ReadAllLines (sourcePropertiesPath)) { + string line = l.Trim (); + if (!line.StartsWith ("Pkg.Revision", StringComparison.Ordinal)) { + continue; + } + + string[] parts = line.Split (splitChars, 2); + if (parts.Length != 2) { + if (log != null) { + log.LogCodedError ("XA5104", Properties.Resources.XA5104); + log.LogDebugMessage ($"Invalid NDK version format in '{sourcePropertiesPath}'."); + } + return null; + } + + ver = parts [1].Trim (); + } + + return new NdkVersion (ver); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NdkVersion.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NdkVersion.cs new file mode 100644 index 00000000000..c704d2cceda --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NdkVersion.cs @@ -0,0 +1,39 @@ +using System; + +namespace Xamarin.Android.Tasks +{ + public class NdkVersion + { + public Version Main { get; } + public string Tag { get; } = String.Empty; + + public NdkVersion (string? version) + { + string? ver = version?.Trim (); + if (String.IsNullOrEmpty (ver)) { + throw new ArgumentException ("must be a non-empty string", nameof (version)); + } + + int tagIdx = ver.IndexOf ('-'); + if (tagIdx >= 0) { + Tag = ver.Substring (tagIdx + 1); + ver = ver.Substring (0, tagIdx - 1); + } + + if (!Version.TryParse (ver, out Version? ndkVersion) || ndkVersion == null) { + throw new InvalidOperationException ($"Failed to parse '{ver}' as a valid NDK version."); + } + + Main = ndkVersion; + } + + public override string ToString () + { + if (!String.IsNullOrEmpty (Tag)) { + return $"{Main}-{Tag}"; + } + + return Main.ToString (); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NoClang.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NoClang.cs new file mode 100644 index 00000000000..ed915a6614b --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NoClang.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using Microsoft.Build.Utilities; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks +{ + abstract class NdkToolsNoClang : NdkTools + { + protected NdkToolsNoClang (string androidNdkPath, NdkVersion version, TaskLoggingHelper? log) + : base (androidNdkPath, version, log) + { + NdkToolNames[NdkToolKind.CompilerC] = "gcc"; + NdkToolNames[NdkToolKind.CompilerCPlusPlus] = "g++"; + } + + public override bool ValidateNdkPlatform (Action logMessage, Action logError, AndroidTargetArch arch, bool enableLLVM) + { + // Check that we have a compatible NDK version for the targeted ABIs. + if (IsNdk64BitArch (arch) && Version.Main.Major < 10) { + logMessage ( + "The detected Android NDK version is incompatible with the targeted 64-bit architecture, " + + "please upgrade to NDK r14 or newer."); + } + + // NDK r10d is buggy and cannot link x86_64 ABI shared libraries because they are 32-bits. + // See https://code.google.com/p/android/issues/detail?id=161421 + if (enableLLVM && Version.Main.Major == 10 && Version.Main.Minor == 4 && arch == AndroidTargetArch.X86_64) { + logError ("XA3004", Properties.Resources.XA3004); + return false; + } + + if (enableLLVM && (Version.Main.Major < 10 || (Version.Main.Major == 10 && Version.Main.Minor < 4))) { + logError ("XA3005", Properties.Resources.XA3005); + } + + return true; + } + + public override int GetMinimumApiLevelFor (AndroidTargetArch arch) + { + var minValue = IsNdk64BitArch (arch) ? 21 : 14; + var platforms = GetSupportedPlatforms ().OrderBy (x => x).Where (x => x >= minValue); + return platforms.First (x => Directory.Exists (Path.Combine (NdkRootDirectory, "platforms", $"android-{x}", $"arch-{GetPlatformArch (arch)}"))); + } + + public override string GetToolPath (NdkToolKind kind, AndroidTargetArch arch, int apiLevel) + { + return GetToolPath (GetToolName (kind), arch, apiLevel); + } + + public override string GetToolPath (string name, AndroidTargetArch arch, int apiLevel) + { + string triple = GetArchTriple (arch); + string toolPath = Path.Combine (NdkRootDirectory, "toolchains", GetArchDirectoryName (arch), "prebuilt", HostPlatform, "bin", $"{triple}-{name}"); + return GetExecutablePath (toolPath, mustExist: true)!; + } + + protected string GetArchDirectoryName (AndroidTargetArch arch) + { + // All toolchains before clang were version 4.9 + string archDir; + + switch (arch) { + case AndroidTargetArch.X86: + archDir = "x86"; + break; + + case AndroidTargetArch.X86_64: + archDir = "x86_64"; + break; + + case AndroidTargetArch.Arm: + case AndroidTargetArch.Arm64: + archDir = GetArchTriple (arch); + break; + + default: + throw new InvalidOperationException ($"Unsupported architecture {arch}"); + } + + return $"{archDir}-4.9"; + } + + public override IEnumerable GetSupportedPlatforms () + { + foreach (var platform in Directory.EnumerateDirectories (Path.Combine (NdkRootDirectory, "platforms"))) { + var androidApi = Path.GetFileName (platform); + int api = -1; + if (int.TryParse (androidApi.Replace ("android-", String.Empty), out api)) { + yield return api; + } + } + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NoClangNoUnifiedHeaders.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NoClangNoUnifiedHeaders.cs new file mode 100644 index 00000000000..150bbbfe14e --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NoClangNoUnifiedHeaders.cs @@ -0,0 +1,21 @@ +using System.IO; + +using Microsoft.Build.Utilities; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks +{ + // No unified headers + // No clang + class NdkToolsNoClangNoUnifiedHeaders : NdkToolsNoClang + { + public NdkToolsNoClangNoUnifiedHeaders (string androidNdkPath, NdkVersion version, TaskLoggingHelper? log) + : base (androidNdkPath, version, log) + {} + + protected override string GetPlatformIncludeDirPath (AndroidTargetArch arch, int apiLevel) + { + return Path.Combine (NdkRootDirectory, "platforms", $"android-{apiLevel}", $"arch-{GetPlatformArch (arch)}", "usr", "include"); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NoClangWithUnifiedHeaders.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NoClangWithUnifiedHeaders.cs new file mode 100644 index 00000000000..a3ea01bf6e3 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NoClangWithUnifiedHeaders.cs @@ -0,0 +1,24 @@ +using Microsoft.Build.Utilities; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks +{ + // Unified headers + // No clang + class NdkToolsNoClangWithUnifiedHeaders : NdkToolsNoClang + { + public NdkToolsNoClangWithUnifiedHeaders (string androidNdkPath, NdkVersion version, TaskLoggingHelper? log) + : base (androidNdkPath, version, log) + {} + + protected override string GetAsmIncludeDirPath (AndroidTargetArch arch, int apiLevel) + { + return UnifiedHeaders_GetAsmIncludeDirPath (arch, apiLevel); + } + + protected override string GetPlatformIncludeDirPath (AndroidTargetArch arch, int apiLevel) + { + return UnifiedHeaders_GetPlatformIncludeDirPath (arch, apiLevel); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/WithClang.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/WithClang.cs new file mode 100644 index 00000000000..08c81ccab26 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/WithClang.cs @@ -0,0 +1,124 @@ +using System; +using System.IO; + +using Microsoft.Build.Utilities; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks +{ + abstract class NdkToolsWithClang : NdkTools + { + protected string UnifiedHeadersDirPath { get; } + + // See NdkToolsWithClangWithPlatforms.ctor for explanation + protected bool NeedClangWorkaround { get; set; } + + protected NdkToolsWithClang (string androidNdkPath, NdkVersion version, TaskLoggingHelper? log) + : base (androidNdkPath, version, log) + { + NdkToolNames[NdkToolKind.CompilerC] = "clang"; + NdkToolNames[NdkToolKind.CompilerCPlusPlus] = "clang++"; + UnifiedHeadersDirPath = GetUnifiedHeadersDirPath (androidNdkPath); + UsesClang = true; + } + + public override bool ValidateNdkPlatform (Action logMessage, Action logError, AndroidTargetArch arch, bool enableLLVM) + { + // Check that we have a compatible NDK version for the targeted ABIs. + if (Version.Main.Major < 19) { + logMessage ( + "The detected Android NDK version is incompatible with this version of Xamarin.Android, " + + "please upgrade to NDK r19 or newer."); + } + + return true; + } + + public override int GetMinimumApiLevelFor (AndroidTargetArch arch) + { + int minValue = 0; + string archName = GetPlatformArch (arch); + if (!XABuildConfig.ArchAPILevels.TryGetValue (archName, out minValue)) + throw new InvalidOperationException ($"Unable to determine minimum API level for architecture {arch}"); + + return minValue; + } + + public override string GetToolPath (NdkToolKind kind, AndroidTargetArch arch, int apiLevel) + { + string toolName = GetToolName (kind); + + if (kind == NdkToolKind.CompilerC || kind == NdkToolKind.CompilerCPlusPlus) { + if (!NeedClangWorkaround) { + // See NdkToolsWithClangWithPlatforms.ctor for explanation + toolName = $"{GetCompilerTriple (arch)}{apiLevel}-{toolName}"; + } + } else { + toolName = $"{GetArchTriple (arch)}-{toolName}"; + } + + return MakeToolPath (toolName); + } + + public override string GetToolPath (string name, AndroidTargetArch arch, int apiLevel) + { + return MakeToolPath ($"{GetArchTriple (arch)}-{name}"); + } + + public override string GetClangDeviceLibraryPath () + { + string toolchainDir = GetToolchainDir (); + string clangBaseDir = Path.Combine (toolchainDir, "lib64", "clang"); + + if (!Directory.Exists (clangBaseDir)) { + throw new InvalidOperationException ($"Clang toolchain directory '{clangBaseDir}' not found"); + } + + // There should be just one subdir - clang version - but it's better to be safe than sorry... + foreach (string dir in Directory.EnumerateDirectories (clangBaseDir)) { + string libDir = Path.Combine (dir, "lib", "linux"); + if (Directory.Exists (libDir)) { + return libDir; + } + } + + throw new InvalidOperationException ("Unable to locate clang device library path"); + } + + public override string GetNdkToolchainPrefix (AndroidTargetArch arch) + { + if (arch == AndroidTargetArch.Arm) { + return "armv7a-linux-androideabi-"; + } + + return base.GetNdkToolchainPrefix (arch); + } + + protected string GetCompilerTriple (AndroidTargetArch arch) + { + return arch == AndroidTargetArch.Arm ? "armv7a-linux-androideabi" : GetArchTriple (arch); + } + + protected string MakeToolPath (string toolName) + { + string toolPath = Path.Combine (GetToolchainDir (), "bin", toolName); + + return GetExecutablePath (toolPath, mustExist: true)!; + } + + protected string GetToolchainDir () + { + return Path.Combine (NdkRootDirectory, "toolchains", "llvm", "prebuilt", HostPlatform); + } + + protected override string GetAsmIncludeDirPath (AndroidTargetArch arch, int apiLevel) + { + return UnifiedHeaders_GetAsmIncludeDirPath (arch, apiLevel); + } + + protected override string GetPlatformIncludeDirPath (AndroidTargetArch arch, int apiLevel) + { + return UnifiedHeaders_GetPlatformIncludeDirPath (arch, apiLevel); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/WithClangNoBinutils.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/WithClangNoBinutils.cs new file mode 100644 index 00000000000..c4b0bb98607 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/WithClangNoBinutils.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; +using System.Collections.Generic; + +using Microsoft.Build.Utilities; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks +{ + class NdkToolsWithClangNoBinutils : NdkToolsWithClangNoPlatforms + { + public NdkToolsWithClangNoBinutils (string androidNdkPath, NdkVersion version, TaskLoggingHelper? log) + : base (androidNdkPath, version, log) + { + NdkToolNames[NdkToolKind.Linker] = "ld"; + NoBinutils = true; + + throw new NotSupportedException ($"NDK {Version} is not supported by this version of Xamarin.Android"); + } + + public override string GetToolPath (NdkToolKind kind, AndroidTargetArch arch, int apiLevel) + { + switch (kind) { + case NdkToolKind.Assembler: + case NdkToolKind.Linker: + case NdkToolKind.Strip: + return GetEmbeddedToolPath (kind, arch); + + default: + return base.GetToolPath (kind, arch, apiLevel); + } + } + + string GetEmbeddedToolPath (NdkToolKind kind, AndroidTargetArch arch) + { + string toolName = GetToolName (kind); + string triple = GetArchTriple (arch); + + return $"[TODO]/{triple}-{toolName}"; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/WithClangNoPlatforms.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/WithClangNoPlatforms.cs new file mode 100644 index 00000000000..48a5f58a81b --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/WithClangNoPlatforms.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using Microsoft.Build.Utilities; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks +{ + class NdkToolsWithClangNoPlatforms : NdkToolsWithClang + { + public NdkToolsWithClangNoPlatforms (string androidNdkPath, NdkVersion version, TaskLoggingHelper? log) + : base (androidNdkPath, version, log) + { + // NDK r22 removed the arch-prefixed `ld` executable. It provides instead `ld.gold` and `ld.bfd` + // We're going to use the former + + NdkToolNames[NdkToolKind.Linker] = "ld.gold"; + } + + public override IEnumerable GetSupportedPlatforms () + { + // NDK r22 and newer no longer have a single platforms dir. The API level directories are now found in per-arch + // subdirectories under the toolchain directory. We need to examine all of them and compose a list of unique + // API levels (since they are repeated in each per-arch subdirectory, but not all architectures have the + // same set of API levels) + var apiLevels = new HashSet (); + string sysrootLibDir = GetToolchainLibDir (); + var supportedArchitectures = new []{ + AndroidTargetArch.Arm, + AndroidTargetArch.Arm64, + AndroidTargetArch.X86, + AndroidTargetArch.X86_64, + }; + + foreach (AndroidTargetArch targetArch in supportedArchitectures) { + string archDirName = GetArchDirName (targetArch); + if (String.IsNullOrEmpty (archDirName)) { + Log?.LogWarning ($"NDK architecture {targetArch} unknown?"); + continue; + } + + string archDir = Path.Combine (sysrootLibDir, archDirName); + if (!Directory.Exists (archDir)) { + Log?.LogWarning ($"Architecture {targetArch} toolchain directory '{archDir}' not found"); + continue; + } + + foreach (string platform in Directory.EnumerateDirectories (archDir, "*", SearchOption.TopDirectoryOnly)) { + string plibc = Path.Combine (platform, "libc.so"); + if (!File.Exists (plibc)) { + continue; + } + + string pdir = Path.GetFileName (platform); + int api; + if (!Int32.TryParse (pdir, out api) || apiLevels.Contains (api)) { + continue; + } + apiLevels.Add (api); + } + } + + return apiLevels; + } + + protected override string MakeUnifiedHeadersDirPath (string androidNdkPath) + { + return Path.Combine (GetSysrootDir (androidNdkPath), "usr", "include"); + } + + protected override string GetPlatformLibPath (AndroidTargetArch arch, int apiLevel) + { + return Path.Combine (GetToolchainLibDir (), GetArchTriple (arch), apiLevel.ToString ()); + } + + protected string GetSysrootDir (string androidNdkPath) + { + return Path.Combine (androidNdkPath, "toolchains", "llvm", "prebuilt", HostPlatform, "sysroot"); + } + + protected string GetToolchainLibDir () + { + return Path.Combine (GetSysrootDir (NdkRootDirectory), "usr", "lib"); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/WithClangWithPlatforms.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/WithClangWithPlatforms.cs new file mode 100644 index 00000000000..7c56dc2ebd7 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/WithClangWithPlatforms.cs @@ -0,0 +1,70 @@ +using System.Text; + +using Microsoft.Build.Utilities; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks +{ + class NdkToolsWithClangWithPlatforms : NdkToolsWithClang + { + public NdkToolsWithClangWithPlatforms (string androidNdkPath, NdkVersion version, TaskLoggingHelper? log) + : base (androidNdkPath, version, log) + { + NeedClangWorkaround = IsWindows && Version.Main.Major >= 19; + if (NeedClangWorkaround) { + // + // NDK r19 bug (fixed in r19c). + // + // The llvm toolchain directory contains a selection of shell scripts (both Unix and Windows) + // which call `clang/clang++` with different `-target` parameters depending on both the target + // architecture and API level. For instance, the clang/clang++ compilers targetting aarch64 on API level + // 28 will have the following Unix shell scripts present in the toolchain `bin` directory: + // + // aarch64-linux-android28-clang + // aarch64-linux-android28-clang++ + // + // However, the Windows version of the NDK has a bug where there is only one Windows + // counterpart to the above Unix scripts: + // + // aarch64-linux-android28-clang.cmd + // + // This script, despite its name suggesting that it calls `clang.exe` in fact calls + // `clang++.exe` which breaks compilation of some C programs (including the code generated by + // Mono's mkbundle utility) because `clang++` treats the input as C++. There is no corresponding + // `aarch64-linux-android28-clang++.cmd` and so invocation of `clang.exe` becomes harder and, + // most certainly, non-standard as far as cross-platform NDK compatibility is concerned. + // + // The code below tries to rectify the situation by special-casing the compiler tool handling to + // return path to the actual .exe instead of the CMD. + // + // Despite the above issue having been fixed in NDK r19c, the CMD scripts in question have another issue - + // they don't work well with paths that have spaces in them. That means we either need to quote the paths + // ourselves, or invoke the compilers directly. The latter option seems to be the better one. + // + NdkToolNames[NdkToolKind.CompilerC] = "clang.exe"; + NdkToolNames[NdkToolKind.CompilerCPlusPlus] = "clang++.exe"; + } + } + + public override string GetCompilerTargetParameters (AndroidTargetArch arch, int apiLevel, bool forCPlusPlus = false) + { + if (!NeedClangWorkaround) { + return base.GetCompilerTargetParameters (arch, apiLevel, forCPlusPlus); + } + + var sb = new StringBuilder (); + sb.Append ("--target=").Append (GetCompilerTriple (arch)).Append (apiLevel); + sb.Append (" -fno-addrsig"); + + if (arch == AndroidTargetArch.X86) { + sb.Append (" -mstackrealign"); + } + + if (forCPlusPlus) { + sb.Append (" -stdlib=libc++"); + } + + return sb.ToString (); + } + } +}