From 90d7621256f58e560a51f81730a938cdc11c3469 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 4 Jun 2021 15:58:29 -0500 Subject: [PATCH] [BaseTasks] add ABI detection for RIDs (#121) Context: https://github.com/xamarin/xamarin-android/issues/5432 Two cases currently do not work in .NET 6: 1. When an Android App project includes native libraries which are in directory names consisting of .NET runtime identifiers instead of Android ABI names, e.g. android-arm/libfoo.so android-arm64/libfoo.so android-x86/libfoo.so android-x64/libfoo.so 2. When a NuGet package places native libraries into a `native` directory *between* the `$(RuntimeIdentifier)` directory and the native library, a'la [`SQLitePCLRaw.lib.e_sqlite3.linux`][0]: runtimes/linux-arm/native/libe_sqlite3.so Fix case (1) by checking using `AndroidRidAbiHelper.RuntimeIdentifierToAbi()` on the directory name to determine the Android ABI of the library. Fix case (2) by also checking the native library's parent parent directory name against Android ABI names and Runtime Identifiers. This allows us to correctly associate runtimes/android-arm64/native/libe_sqlite3.so as an arm64-v8a native library. I implemented these two cases as fallbacks to the existing logic. I think this will be fine for the behavior to be in "legacy" Xamarin.Android as well as .NET 6. I added tests for `AndroidRidAbiHelper`, since we had none before. [0]: https://www.nuget.org/packages/SQLitePCLRaw.lib.e_sqlite3.linux/1.1.14 --- .../AndroidRidAbiHelper.cs | 27 +++- .../AndroidRidAbiHelperTests.cs | 142 ++++++++++++++++++ 2 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidRidAbiHelperTests.cs diff --git a/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs b/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs index 229d069..8e6cce4 100644 --- a/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs +++ b/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs @@ -19,13 +19,27 @@ public static class AndroidRidAbiHelper public static string GetNativeLibraryAbi (string lib) { // The topmost directory the .so file is contained within - var dir = Path.GetFileName (Path.GetDirectoryName (lib)).ToLowerInvariant (); - if (dir.StartsWith ("interpreter-", StringComparison.Ordinal)) { - dir = dir.Substring (12); + var dir = Directory.GetParent (lib); + var dirName = dir.Name.ToLowerInvariant (); + if (dirName.StartsWith ("interpreter-", StringComparison.Ordinal)) { + dirName = dirName.Substring ("interpreter-".Length); } - if (ValidAbis.Contains (dir)) { - return dir; + if (ValidAbis.Contains (dirName)) { + return dirName; } + + // Look for a directory with a RID as a name, such as: + // android-arm64/libfoo.so + var abi = RuntimeIdentifierToAbi (dirName); + if (!string.IsNullOrEmpty (abi)) + return abi; + + // Try one directory higher, such as: + // packages/sqlitepclraw.lib.e_sqlite3.android/1.1.11/runtimes/android-arm64/native/libe_sqlite3.so + abi = RuntimeIdentifierToAbi (dir.Parent.Name.ToLowerInvariant ()); + if (!string.IsNullOrEmpty (abi)) + return abi; + return null; } @@ -41,8 +55,7 @@ public static string GetNativeLibraryAbi (ITaskItem lib) // First, try nominal "Link" path. var link = lib.GetMetadata ("Link"); if (!string.IsNullOrWhiteSpace (link)) { - var linkdirs = link.ToLowerInvariant ().Split ('/', '\\'); - lib_abi = ValidAbis.Where (p => linkdirs.Contains (p)).FirstOrDefault (); + lib_abi = GetNativeLibraryAbi (link); } // Check for a RuntimeIdentifier diff --git a/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidRidAbiHelperTests.cs b/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidRidAbiHelperTests.cs new file mode 100644 index 0000000..bb241cb --- /dev/null +++ b/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidRidAbiHelperTests.cs @@ -0,0 +1,142 @@ +using System.Collections.Generic; +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using NUnit.Framework; + +namespace Microsoft.Android.Build.BaseTasks.Tests +{ + [TestFixture] + public class AndroidRidAbiHelperTests + { + static object [] StringValueSource = new object [] { + new[] { + /* input */ "armeabi-v7a/libfoo.so", + /* expected */ "armeabi-v7a" + }, + new[] { + /* input */ "arm64-v8a/libfoo.so", + /* expected */ "arm64-v8a" + }, + new[] { + /* input */ "x86/libfoo.so", + /* expected */ "x86" + }, + new[] { + /* input */ "x86_64/libfoo.so", + /* expected */ "x86_64" + }, + new[] { + /* input */ "android-arm/libfoo.so", + /* expected */ "armeabi-v7a" + }, + new[] { + /* input */ "android-arm64/libfoo.so", + /* expected */ "arm64-v8a" + }, + new[] { + /* input */ "android-x86/libfoo.so", + /* expected */ "x86" + }, + new[] { + /* input */ "android-x64/libfoo.so", + /* expected */ "x86_64" + }, + new[] { + /* input */ "android-arm/native/libfoo.so", + /* expected */ "armeabi-v7a" + }, + new[] { + /* input */ "android-arm64/native/libfoo.so", + /* expected */ "arm64-v8a" + }, + new[] { + /* input */ "android-x86/native/libfoo.so", + /* expected */ "x86" + }, + new[] { + /* input */ "android-x64/native/libfoo.so", + /* expected */ "x86_64" + }, + new[] { + /* input */ "android.21-x64/native/libfoo.so", + /* expected */ "x86_64" + }, + new[] { + /* input */ "packages/sqlitepclraw.lib.e_sqlite3.android/1.1.11/runtimes/android-arm64/native/libe_sqlite3.so", + /* expected */ "arm64-v8a" + } + }; + + [Test] + [TestCaseSource (nameof (StringValueSource))] + public void StringValue (string input, string expected) + { + Assert.AreEqual (expected, AndroidRidAbiHelper.GetNativeLibraryAbi (input)); + } + + static object [] ITaskItemValueSource = new object [] { + new object [] { + /* input */ + new TaskItem("armeabi-v7a/libfoo.so"), + /* expected */ + "armeabi-v7a" + }, + new object [] { + /* input */ + new TaskItem("libabi.so", new Dictionary { + { "Abi", "armeabi-v7a" } + }), + /* expected */ + "armeabi-v7a" + }, + new object [] { + /* input */ + new TaskItem("librid.so", new Dictionary { + { "RuntimeIdentifier", "android-arm" } + }), + /* expected */ + "armeabi-v7a" + }, + new object [] { + /* input */ + new TaskItem("liblink.so", new Dictionary { + { "Link", "armeabi-v7a/libfoo.so" } + }), + /* expected */ + "armeabi-v7a" + }, + new object [] { + /* input */ + new TaskItem("liblink.so", new Dictionary { + { "Link", "x86/libfoo.so" } + }), + /* expected */ + "x86" + }, + new object [] { + /* input */ + new TaskItem("liblink.so", new Dictionary { + { "Link", "x86_64/libfoo.so" } + }), + /* expected */ + "x86_64" + }, + new object [] { + /* input */ + new TaskItem("libridlink.so", new Dictionary { + { "Link", "android-arm/libfoo.so" } + }), + /* expected */ + "armeabi-v7a" + }, + }; + + [Test] + [TestCaseSource (nameof (ITaskItemValueSource))] + public void ITaskItemValue (ITaskItem input, string expected) + { + Assert.AreEqual (expected, AndroidRidAbiHelper.GetNativeLibraryAbi (input)); + } + } +}