Bug 1485045 - Make Java parts of GeckoView independent from build ABI r=jchen
☠☠ backed out by debe7b5d5bbc ☠ ☠
authorJames Willcox <snorp@snorp.net>
Wed, 14 Nov 2018 17:48:58 +0000
changeset 446211 7174015fbbd584f9afa8783c1ac119ab3ac8bbf4
parent 446210 2bc6db0a2574e735c0ab3ffa3bd583b4200a12e7
child 446212 6f209bee42c5a07163c89d21beb7e7062e2c75b1
push id35038
push userrmaries@mozilla.com
push dateWed, 14 Nov 2018 22:12:17 +0000
treeherdermozilla-central@4e1b2b7e0c37 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjchen
bugs1485045
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1485045 - Make Java parts of GeckoView independent from build ABI r=jchen This allows us to use the same Java code for any native platform, enabling a "fat" AAR. Differential Revision: https://phabricator.services.mozilla.com/D11497
mobile/android/geckoview/build.gradle
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoSessionTestRuleTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/crash/CrashTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Environment.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareUtils.java
--- a/mobile/android/geckoview/build.gradle
+++ b/mobile/android/geckoview/build.gradle
@@ -64,18 +64,16 @@ android {
 
         versionCode computeVersionCode()
         versionName "${mozconfig.substs.MOZ_APP_VERSION}-${mozconfig.substs.MOZ_UPDATE_CHANNEL}"
         consumerProguardFiles 'proguard-rules.txt'
 
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
 
         buildConfigField 'String', "GRE_MILESTONE", "\"${mozconfig.substs.GRE_MILESTONE}\""
-        // This should really come from the included binaries, but that's not easy.
-        buildConfigField 'String', "MOZ_APP_ABI", mozconfig.substs['COMPILE_ENVIRONMENT'] ? "\"${ mozconfig.substs.TARGET_XPCOM_ABI}\"" : '"arm-eabi-gcc3"';
         buildConfigField 'String', "MOZ_APP_BASENAME", "\"${mozconfig.substs.MOZ_APP_BASENAME}\"";
 
         // For the benefit of future archaeologists:
         // GRE_BUILDID is exactly the same as MOZ_APP_BUILDID unless you're running
         // on XULRunner, which is never the case on Android.
         buildConfigField 'String', "MOZ_APP_BUILDID", "\"${getBuildId()}\"";
         buildConfigField 'String', "MOZ_APP_ID", "\"${mozconfig.substs.MOZ_APP_ID}\"";
         buildConfigField 'String', "MOZ_APP_NAME", "\"${mozconfig.substs.MOZ_APP_NAME}\"";
@@ -87,18 +85,16 @@ android {
 
         // MOZILLA_VERSION is oddly quoted from autoconf, but we don't have to handle it specially in Gradle.
         buildConfigField 'String', "MOZILLA_VERSION", "\"${mozconfig.substs.MOZILLA_VERSION}\"";
         buildConfigField 'String', "OMNIJAR_NAME", "\"${mozconfig.substs.OMNIJAR_NAME}\"";
 
         buildConfigField 'String', "USER_AGENT_GECKOVIEW_MOBILE", "\"Mozilla/5.0 (Android \" + android.os.Build.VERSION.RELEASE + \"; Mobile; rv: ${mozconfig.substs.MOZ_APP_VERSION}) Gecko/${mozconfig.substs.MOZ_APP_VERSION} GeckoView/${mozconfig.substs.MOZ_APP_VERSION}\"";
         buildConfigField 'String', "USER_AGENT_GECKOVIEW_TABLET", "\"Mozilla/5.0 (Android \" + android.os.Build.VERSION.RELEASE + \"; Tablet; rv: ${mozconfig.substs.MOZ_APP_VERSION}) Gecko/${mozconfig.substs.MOZ_APP_VERSION} GeckoView/${mozconfig.substs.MOZ_APP_VERSION}\"";
 
-        buildConfigField 'String', "ANDROID_CPU_ARCH", "\"${mozconfig.substs.ANDROID_CPU_ARCH}\"";
-
         buildConfigField 'int', 'MIN_SDK_VERSION', mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION;
 
         // Is the underlying compiled C/C++ code compiled with --enable-debug?
         buildConfigField 'boolean', 'DEBUG_BUILD', mozconfig.substs.MOZ_DEBUG ? 'true' : 'false';
 
         // See this wiki page for more details about channel specific build defines:
         // https://wiki.mozilla.org/Platform/Channel-specific_build_defines
         // This makes no sense for GeckoView and should be removed as soon as possible.
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
@@ -79,17 +79,17 @@ class ContentDelegateTest : BaseSessionT
 
     @IgnoreCrash
     @ReuseSession(false)
     @Test fun crashContent() {
         // This test doesn't make sense without multiprocess
         assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
         // Cannot test x86 debug builds due to Gecko's "ah_crap_handler"
         // that waits for debugger to attach during a SIGSEGV.
-        assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.cpuArch == "x86",
+        assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.isX86,
                    equalTo(false))
 
         mainSession.loadUri(CONTENT_CRASH_URL)
         mainSession.waitUntilCalled(object : Callbacks.ContentDelegate {
             @AssertCalled(count = 1)
             override fun onCrash(session: GeckoSession) {
                 assertThat("Session should be closed after a crash",
                            session.isOpen, equalTo(false))
@@ -110,17 +110,17 @@ class ContentDelegateTest : BaseSessionT
     @IgnoreCrash
     @ReuseSession(false)
     @WithDisplay(width = 10, height = 10)
     @Test fun crashContent_tapAfterCrash() {
         // This test doesn't make sense without multiprocess
         assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
         // Cannot test x86 debug builds due to Gecko's "ah_crap_handler"
         // that waits for debugger to attach during a SIGSEGV.
-        assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.cpuArch == "x86",
+        assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.isX86,
                    equalTo(false))
 
         mainSession.delegateUntilTestEnd(object : Callbacks.ContentDelegate {
             override fun onCrash(session: GeckoSession) {
                 mainSession.open()
                 mainSession.loadTestPath(HELLO_HTML_PATH)
             }
         })
@@ -136,17 +136,17 @@ class ContentDelegateTest : BaseSessionT
 
     @IgnoreCrash
     @ReuseSession(false)
     @Test fun crashContentMultipleSessions() {
         // This test doesn't make sense without multiprocess
         assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
         // Cannot test x86 debug builds due to Gecko's "ah_crap_handler"
         // that waits for debugger to attach during a SIGSEGV.
-        assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.cpuArch == "x86",
+        assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.isX86,
                    equalTo(false))
 
         // XXX we need to make sure all sessions in a given content process receive onCrash().
         // If we add multiple content processes, this test will need to be fixed to ensure the
         // test sessions go into the same one.
         val newSession = sessionRule.createOpenSession()
         mainSession.loadUri(CONTENT_CRASH_URL)
 
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoSessionTestRuleTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoSessionTestRuleTest.kt
@@ -1656,34 +1656,34 @@ class GeckoSessionTestRuleTest : BaseSes
     }
 
     @IgnoreCrash
     @ReuseSession(false)
     @Test fun contentCrashIgnored() {
         assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
         // Cannot test x86 debug builds due to Gecko's "ah_crap_handler"
         // that waits for debugger to attach during a SIGSEGV.
-        assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.cpuArch == "x86",
+        assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.isX86,
                    equalTo(false))
 
         mainSession.loadUri(CONTENT_CRASH_URL)
         mainSession.waitUntilCalled(object : Callbacks.ContentDelegate {
             @AssertCalled(count = 1)
             override fun onCrash(session: GeckoSession) = Unit
         })
     }
 
     @Test(expected = ChildCrashedException::class)
     @ReuseSession(false)
     fun contentCrashFails() {
         assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
         assumeThat(sessionRule.env.shouldShutdownOnCrash(), equalTo(false))
         // Cannot test x86 debug builds due to Gecko's "ah_crap_handler"
         // that waits for debugger to attach during a SIGSEGV.
-        assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.cpuArch == "x86",
+        assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.isX86,
                    equalTo(false))
 
         sessionRule.session.loadUri(CONTENT_CRASH_URL)
         sessionRule.waitForPageStop()
     }
 
     @Test fun waitForResult() {
         val handler = Handler(Looper.getMainLooper())
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/crash/CrashTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/crash/CrashTest.kt
@@ -32,17 +32,17 @@ class CrashTest {
     lateinit var messenger: Messenger
 
     @get:Rule val rule = ServiceTestRule()
 
     @get:Rule val timeoutRule = Timeout.millis(getTimeoutMillis())
 
     fun getTimeoutMillis(): Long {
         val env = Environment()
-        if ("x86" == env.cpuArch) {
+        if (env.isX86) {
             return if (env.isEmulator)
                 CrashTest.DEFAULT_X86_EMULATOR_TIMEOUT_MILLIS
             else
                 CrashTest.DEFAULT_X86_DEVICE_TIMEOUT_MILLIS
         }
         return if (env.isEmulator)
             CrashTest.DEFAULT_ARM_EMULATOR_TIMEOUT_MILLIS
         else
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Environment.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Environment.java
@@ -51,22 +51,29 @@ public class Environment {
     public boolean isEmulator() {
         return "generic".equals(Build.DEVICE) || Build.DEVICE.startsWith("generic_");
     }
 
     public boolean isDebugBuild() {
         return BuildConfig.DEBUG_BUILD;
     }
 
-    public String getCPUArch() {
-        return BuildConfig.ANDROID_CPU_ARCH;
+    public boolean isX86() {
+        final String abi;
+        if (Build.VERSION.SDK_INT >= 21) {
+            abi = Build.SUPPORTED_ABIS[0];
+        } else {
+            abi = Build.CPU_ABI;
+        }
+
+        return abi.startsWith("x86");
     }
 
     public long getScaledTimeoutMillis() {
-        if ("x86".equals(getCPUArch())) {
+        if (isX86()) {
             return isEmulator() ? DEFAULT_X86_EMULATOR_TIMEOUT_MILLIS
                                 : DEFAULT_X86_DEVICE_TIMEOUT_MILLIS;
         }
         return isEmulator() ? DEFAULT_ARM_EMULATOR_TIMEOUT_MILLIS
                             : DEFAULT_ARM_DEVICE_TIMEOUT_MILLIS;
     }
 
     public long getDefaultTimeoutMillis() {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.mozglue;
 
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.annotation.JNITarget;
 import org.mozilla.gecko.annotation.RobocopTarget;
+import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.geckoview.BuildConfig;
 
 import android.content.Context;
 import android.os.Build;
 import android.os.Environment;
 import android.util.Log;
 
 import java.io.File;
@@ -309,18 +310,20 @@ public final class GeckoLoader {
 
     private static String getLoadDiagnostics(final Context context, final String lib) {
         final String androidPackageName = context.getPackageName();
 
         final StringBuilder message = new StringBuilder("LOAD ");
         message.append(lib);
 
         // These might differ. If so, we know why the library won't load!
-        message.append(": ABI: " + BuildConfig.MOZ_APP_ABI + ", " + getCPUABI());
+        HardwareUtils.init(context);
+        message.append(": ABI: " + HardwareUtils.getLibrariesABI() + ", " + getCPUABI());
         message.append(": Data: " + context.getApplicationInfo().dataDir);
+
         try {
             final boolean appLibExists = new File("/data/app-lib/" + androidPackageName + "/lib" + lib + ".so").exists();
             final boolean dataDataExists = new File("/data/data/" + androidPackageName + "/lib/lib" + lib + ".so").exists();
             message.append(", ax=" + appLibExists);
             message.append(", ddx=" + dataDataExists);
         } catch (Throwable e) {
             message.append(": ax/ddx fail, ");
         }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareUtils.java
@@ -10,54 +10,61 @@ import android.content.pm.PackageManager
 import android.content.res.Configuration;
 import android.os.Build;
 import android.system.Os;
 import android.util.Log;
 
 import org.mozilla.gecko.SysInfo;
 import org.mozilla.geckoview.BuildConfig;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
 public final class HardwareUtils {
     private static final String LOGTAG = "GeckoHardwareUtils";
 
     private static final boolean IS_AMAZON_DEVICE = Build.MANUFACTURER.equalsIgnoreCase("Amazon");
     public static final boolean IS_KINDLE_DEVICE = IS_AMAZON_DEVICE &&
                                                    (Build.MODEL.equals("Kindle Fire") ||
                                                     Build.MODEL.startsWith("KF"));
 
     private static volatile boolean sInited;
 
     // These are all set once, during init.
     private static volatile boolean sIsLargeTablet;
     private static volatile boolean sIsSmallTablet;
     private static volatile boolean sIsTelevision;
 
+    private static volatile File sLibDir;
+    private static volatile int sMachineType = -1;
+
     private HardwareUtils() {
     }
 
     public static void init(Context context) {
         if (sInited) {
-            // This is unavoidable, given that HardwareUtils is called from background services.
-            Log.d(LOGTAG, "HardwareUtils already inited.");
             return;
         }
 
         // Pre-populate common flags from the context.
         final int screenLayoutSize = context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
         if (screenLayoutSize == Configuration.SCREENLAYOUT_SIZE_XLARGE) {
             sIsLargeTablet = true;
         } else if (screenLayoutSize == Configuration.SCREENLAYOUT_SIZE_LARGE) {
             sIsSmallTablet = true;
         }
         if (Build.VERSION.SDK_INT >= 16) {
             if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION)) {
                 sIsTelevision = true;
             }
         }
 
+        sLibDir = new File(context.getApplicationInfo().dataDir, "lib");
         sInited = true;
     }
 
     public static boolean isTablet() {
         return sIsLargeTablet || sIsSmallTablet;
     }
 
     public static boolean isLargeTablet() {
@@ -115,42 +122,101 @@ public final class HardwareUtils {
         if (isX86System() && isARMSystem()) {
             // Some x86 devices try to make us believe we're ARM,
             // in which case CPU_ABI is not reliable.
             return "x86";
         }
         return getPreferredAbi();
     }
 
+    private static final int ELF_MACHINE_UNKNOWN = 0;
+    private static final int ELF_MACHINE_X86 = 0x03;
+    private static final int ELF_MACHINE_X86_64 = 0x3e;
+    private static final int ELF_MACHINE_ARM = 0x28;
+    private static final int ELF_MACHINE_AARCH64 = 0xb7;
+
+    private static int readElfMachineType(final File file) {
+        try (final FileInputStream is = new FileInputStream(file)) {
+            final byte[] buf = new byte[17];
+            int count = 0;
+            while (count != buf.length) {
+                count += is.read(buf, count, buf.length - count);
+            }
+
+            return buf[16];
+        } catch (FileNotFoundException e) {
+            Log.w(LOGTAG, String.format("Failed to open %s", file.getAbsolutePath()));
+            return ELF_MACHINE_UNKNOWN;
+        } catch (IOException e) {
+            Log.w(LOGTAG, "Failed to read library", e);
+            return ELF_MACHINE_UNKNOWN;
+        }
+    }
+
+    private static String machineTypeToString(int machineType) {
+        switch (machineType) {
+            case ELF_MACHINE_X86:
+                return "x86";
+            case ELF_MACHINE_X86_64:
+                return "x86_64";
+            case ELF_MACHINE_ARM:
+                return "arm";
+            case ELF_MACHINE_AARCH64:
+                return "aarch64";
+            case ELF_MACHINE_UNKNOWN:
+            default:
+                return "unknown";
+        }
+    }
+
+    private static void initMachineType() {
+        if (sMachineType >= 0) {
+            return;
+        }
+
+        sMachineType = readElfMachineType(new File(sLibDir, System.mapLibraryName("mozglue")));
+    }
+
+    /**
+     * @return The ABI of the libraries installed for this app.
+     */
+    public static String getLibrariesABI() {
+        initMachineType();
+
+        return machineTypeToString(sMachineType);
+    }
+
     /**
      * @return false if the current system is not supported (e.g. APK/system ABI mismatch).
      */
     public static boolean isSupportedSystem() {
         // We've had crash reports from users on API 10 (with minSDK==15). That shouldn't even install,
         // but since it does we need to protect against it:
         if (Build.VERSION.SDK_INT < BuildConfig.MIN_SDK_VERSION) {
             return false;
         }
 
+        initMachineType();
+
         // See http://developer.android.com/ndk/guides/abis.html
         final boolean isSystemX86 = isX86System();
         final boolean isSystemARM = !isSystemX86 && isARMSystem();
         final boolean isSystemARM64 = isARM64System();
 
-        boolean isAppARM = BuildConfig.ANDROID_CPU_ARCH.startsWith("armeabi-v7a");
-        boolean isAppARM64 = BuildConfig.ANDROID_CPU_ARCH.startsWith("arm64-v8a");
-        boolean isAppX86 = BuildConfig.ANDROID_CPU_ARCH.startsWith("x86");
+        final boolean isAppARM = sMachineType == ELF_MACHINE_ARM;
+        final boolean isAppARM64 = sMachineType == ELF_MACHINE_AARCH64;
+        final boolean isAppX86 = sMachineType == ELF_MACHINE_X86;
 
         // Only reject known incompatible ABIs. Better safe than sorry.
         if ((isSystemX86 && isAppARM) || (isSystemARM && isAppX86)) {
             return false;
         }
 
         if ((isSystemX86 && isAppX86) || (isSystemARM && isAppARM) ||
             (isSystemARM64 && (isAppARM64 || isAppARM))) {
             return true;
         }
 
         Log.w(LOGTAG, "Unknown app/system ABI combination: " +
-                      BuildConfig.MOZ_APP_ABI + " / " + getRealAbi());
+                      machineTypeToString(sMachineType) + " / " + getRealAbi());
         return true;
     }
 }