Bug 1219807 - Support 'mach run --debug' for Android; r=jmaher
authorGeoff Brown <gbrown@mozilla.com>
Thu, 19 Nov 2015 13:15:34 -0700
changeset 307528 dbced6916906e46e6794aa98ee75d05da94a257a
parent 307527 367d328e4cd7ad407a2cff21b2b09d2dee51781b
child 307529 7862ba5b03b02e6b43809e0a5ed4c9e568d8df09
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjmaher
bugs1219807
milestone45.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 1219807 - Support 'mach run --debug' for Android; r=jmaher
python/mozbuild/mozbuild/mach_commands.py
testing/config/tooltool-manifests/linux32/jimdb-arm.manifest
testing/config/tooltool-manifests/linux32/jimdb-x86.manifest
testing/config/tooltool-manifests/linux64/jimdb-arm.manifest
testing/config/tooltool-manifests/linux64/jimdb-x86.manifest
testing/config/tooltool-manifests/macosx64/jimdb-arm.manifest
testing/config/tooltool-manifests/macosx64/jimdb-x86.manifest
testing/mozbase/mozdebug/mozdebug/mozdebug.py
testing/mozbase/mozrunner/mozrunner/devices/android_device.py
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -1041,51 +1041,55 @@ class RunProgram(MachCommandBase):
     @CommandArgument('--show-dump-stats', action='store_true', group='DMD',
         help='Show stats when doing dumps.')
     def run(self, params, remote, background, noprofile, debug, debugger,
         debugparams, slowscript, dmd, mode, sample_below, max_frames,
         show_dump_stats):
 
         if conditions.is_android(self):
             # Running Firefox for Android is completely different
-            if debug or debugger or debugparams:
-                print("Debugging Firefox for Android is not yet supported")
-                return 1
             if dmd:
                 print("DMD is not supported for Firefox for Android")
                 return 1
-            return self._run_android(params)
+            from mozrunner.devices.android_device import verify_android_device, run_firefox_for_android
+            if not (debug or debugger or debugparams):
+                verify_android_device(self, install=True)
+                return run_firefox_for_android(self, params)
+            verify_android_device(self, install=True, debugger=True)
+            args = ['']
 
-        try:
-            binpath = self.get_binary_path('app')
-        except Exception as e:
-            print("It looks like your program isn't built.",
-                "You can run |mach build| to build it.")
-            print(e)
-            return 1
+        else:
 
-        args = [binpath]
-
-        if params:
-            args.extend(params)
+            try:
+                binpath = self.get_binary_path('app')
+            except Exception as e:
+                print("It looks like your program isn't built.",
+                    "You can run |mach build| to build it.")
+                print(e)
+                return 1
 
-        if not remote:
-            args.append('-no-remote')
+            args = [binpath]
 
-        if not background and sys.platform == 'darwin':
-            args.append('-foreground')
+            if params:
+                args.extend(params)
+
+            if not remote:
+                args.append('-no-remote')
 
-        no_profile_option_given = \
-            all(p not in params for p in ['-profile', '--profile', '-P'])
-        if no_profile_option_given and not noprofile:
-            path = os.path.join(self.topobjdir, 'tmp', 'scratch_user')
-            if not os.path.isdir(path):
-                os.makedirs(path)
-            args.append('-profile')
-            args.append(path)
+            if not background and sys.platform == 'darwin':
+                args.append('-foreground')
+
+            no_profile_option_given = \
+                all(p not in params for p in ['-profile', '--profile', '-P'])
+            if no_profile_option_given and not noprofile:
+                path = os.path.join(self.topobjdir, 'tmp', 'scratch_user')
+                if not os.path.isdir(path):
+                    os.makedirs(path)
+                args.append('-profile')
+                args.append(path)
 
         extra_env = {}
 
         if debug or debugger or debugparams:
             import mozdebug
             if not debugger:
                 # No debugger name was provided. Look for the default ones on
                 # current OS.
@@ -1154,21 +1158,16 @@ class RunProgram(MachCommandBase):
             if dmd_params:
                 env_vars[arch]["DMD"] = " ".join(dmd_params)
 
             extra_env.update(env_vars.get(arch, {}))
 
         return self.run_process(args=args, ensure_exit_code=False,
             pass_thru=True, append_env=extra_env)
 
-    def _run_android(self, params):
-        from mozrunner.devices.android_device import verify_android_device, run_firefox_for_android
-        verify_android_device(self, install=True)
-        return run_firefox_for_android(self, params)
-
 @CommandProvider
 class Buildsymbols(MachCommandBase):
     """Produce a package of debug symbols suitable for use with Breakpad."""
 
     @Command('buildsymbols', category='post-build',
         description='Produce a package of Breakpad-format symbols.')
     def buildsymbols(self):
         return self._run_make(directory=".", target='buildsymbols', ensure_exit_code=False)
--- a/testing/config/tooltool-manifests/linux32/jimdb-arm.manifest
+++ b/testing/config/tooltool-manifests/linux32/jimdb-arm.manifest
@@ -1,9 +1,10 @@
 [
 {
 "size": 1947653,
 "visibility": "public",
 "digest": "37810f1f10a2535f1d609ab3fe6b029e14465361c1daa664a9511f46dd56e10aad881a69aa9b69ccb1e483add5030086610bec3f5b7f9409152a0abdf0a98f36",
 "algorithm": "sha512",
-"filename": "jimdb-arm-linux_x86.tar.bz2"
+"filename": "jimdb-arm-linux_x86.tar.bz2",
+"unpack": true
 }
 ]
--- a/testing/config/tooltool-manifests/linux32/jimdb-x86.manifest
+++ b/testing/config/tooltool-manifests/linux32/jimdb-x86.manifest
@@ -1,9 +1,10 @@
 [
 {
 "size": 2021932,
 "visibility": "public",
 "digest": "022e9a0585e9df44e28e8c9f2ce7a46fc07df8e137f5b0badd7b6b59f2d886b91813efdaf1d7d2dd4764b07a9ebc97f94456f1f6b17eabc5dd2c12b1c1c7c11c",
 "algorithm": "sha512",
-"filename": "jimdb-x86-linux_x86.tar.bz2"
+"filename": "jimdb-x86-linux_x86.tar.bz2",
+"unpack": true
 }
 ]
--- a/testing/config/tooltool-manifests/linux64/jimdb-arm.manifest
+++ b/testing/config/tooltool-manifests/linux64/jimdb-arm.manifest
@@ -1,9 +1,10 @@
 [
 {
 "size": 2008279,
 "visibility": "public",
 "digest": "929cb2192b0fdfeecd3d1cf210c10bfe54cf5a617420751a69815d390a0a646cb7240d1326c80fc6bee5d913f851a398a64ecb7a604c06f02799d62cd8117e3b",
 "algorithm": "sha512",
-"filename": "jimdb-arm-linux_x64.tar.bz2"
+"filename": "jimdb-arm-linux_x64.tar.bz2",
+"unpack": true
 }
 ]
--- a/testing/config/tooltool-manifests/linux64/jimdb-x86.manifest
+++ b/testing/config/tooltool-manifests/linux64/jimdb-x86.manifest
@@ -1,9 +1,10 @@
 [
 {
 "size": 2097036,
 "visibility": "public",
 "digest": "46064b4e0526d7bc78bb5d0ca1595738013663e43d8673f4b868ec1a549a8012f2759cbed9e0e15a11b38a3dea4ea7292a58b05a5b919cf64894760dd27e50a2",
 "algorithm": "sha512",
-"filename": "jimdb-x86-linux_x64.tar.bz2"
+"filename": "jimdb-x86-linux_x64.tar.bz2",
+"unpack": true
 }
 ]
--- a/testing/config/tooltool-manifests/macosx64/jimdb-arm.manifest
+++ b/testing/config/tooltool-manifests/macosx64/jimdb-arm.manifest
@@ -1,9 +1,10 @@
 [
 {
 "size": 1870546,
 "visibility": "public",
 "digest": "57082b39169e23746db762b10e3a606d86511096724783b3ae747c4570cf469ab7e6d7ae636c3454113c1d68c3b6554a93217908ce141e76f877d84011f0098a",
 "algorithm": "sha512",
-"filename": "jimdb-arm-mac_x64.tar.bz2"
+"filename": "jimdb-arm-mac_x64.tar.bz2",
+"unpack": true
 }
 ]
--- a/testing/config/tooltool-manifests/macosx64/jimdb-x86.manifest
+++ b/testing/config/tooltool-manifests/macosx64/jimdb-x86.manifest
@@ -1,9 +1,10 @@
 [
 {
 "size": 1958623,
 "visibility": "public",
 "digest": "0f2d4837eb25412800438dbe87a87f6ef38d4d92379f987e5f1e524ff9ec0c783386789f6a0e15b99e6a443326170cca1bf71e80d016971af3e1371e5b1e9d2d",
 "algorithm": "sha512",
-"filename": "jimdb-x86-mac_x64.tar.bz2"
+"filename": "jimdb-x86-mac_x64.tar.bz2",
+"unpack": true
 }
 ]
--- a/testing/mozbase/mozdebug/mozdebug/mozdebug.py
+++ b/testing/mozbase/mozdebug/mozdebug/mozdebug.py
@@ -53,16 +53,17 @@ To add support for a new debugger, simpl
     }
 }
 
 # Maps each OS platform to the preferred debugger programs found in _DEBUGGER_INFO.
 _DEBUGGER_PRIORITIES = {
       'win': ['devenv.exe', 'wdexpress.exe'],
       'linux': ['gdb', 'cgdb', 'lldb'],
       'mac': ['lldb', 'gdb'],
+      'android': ['gdb'],
       'unknown': ['gdb']
 }
 
 def get_debugger_info(debugger, debuggerArgs = None, debuggerInteractive = False):
     '''
     Get the information about the requested debugger.
 
     Returns a dictionary containing the |path| of the debugger executable,
--- a/testing/mozbase/mozrunner/mozrunner/devices/android_device.py
+++ b/testing/mozbase/mozrunner/mozrunner/devices/android_device.py
@@ -1,12 +1,13 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # 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/.
 
+import fileinput
 import glob
 import os
 import platform
 import psutil
 import re
 import shutil
 import signal
 import sys
@@ -75,26 +76,28 @@ AVD_DICT = {
                    ['-debug',
                     'init,console,gles,memcheck,adbserver,adbclient,adb,avd_config,socket',
                     '-qemu', '-m', '1024', '-enable-kvm'],
                    5554,
                    True,
                    20701, 20700)
 }
 
-def verify_android_device(build_obj, install=False, xre=False):
+def verify_android_device(build_obj, install=False, xre=False, debugger=False):
     """
        Determine if any Android device is connected via adb.
        If no device is found, prompt to start an emulator.
        If a device is found or an emulator started and 'install' is
        specified, also check whether Firefox is installed on the
        device; if not, prompt to install Firefox.
        If 'xre' is specified, also check with MOZ_HOST_BIN is set
        to a valid xre/host-utils directory; if not, prompt to set
        one up.
+       If 'debugger' is specified, also check that JimDB is installed;
+       if JimDB is not found, prompt to set up JimDB.
        Returns True if the emulator was started or another device was
        already connected.
     """
     device_verified = False
     emulator = AndroidEmulator('*', substs=build_obj.substs)
     devices = emulator.dm.devices()
     if len(devices) > 0:
         device_verified = True
@@ -155,38 +158,78 @@ def verify_android_device(build_obj, ins
                     break
         if err:
             _log_info("Host utilities not found: %s" % err)
             response = raw_input(
                 "Download and setup your host utilities? (Y/n) ").strip()
             if response.lower().startswith('y') or response == '':
                 _log_info("Installing host utilities. This may take a while...")
                 _download_file(TOOLTOOL_URL, 'tooltool.py', EMULATOR_HOME_DIR)
-                if 'darwin' in str(sys.platform).lower():
-                    plat = 'macosx64'
-                elif 'linux' in str(sys.platform).lower():
-                    if '64' in platform.architecture()[0]:
-                        plat = 'linux64'
-                    else:
-                        plat = 'linux32'
-                else:
-                    plat = None
-                    _log_warning("Unable to install host utilities -- your platform is not supported!")
-                if plat:
-                    path = os.path.join(MANIFEST_PATH, plat, 'hostutils.manifest')
+                host_platform = _get_host_platform()
+                if host_platform:
+                    path = os.path.join(MANIFEST_PATH, host_platform, 'hostutils.manifest')
                     _get_tooltool_manifest(build_obj.substs, path, EMULATOR_HOME_DIR, 'releng.manifest')
                     _tooltool_fetch()
                     xre_path = glob.glob(os.path.join(EMULATOR_HOME_DIR, 'host-utils*'))
                     for path in xre_path:
                         if os.path.isdir(path) and os.path.isfile(os.path.join(path, 'xpcshell')):
                             os.environ['MOZ_HOST_BIN'] = path
                             err = None
                             break
                     if err:
                         _log_warning("Unable to install host utilities.")
+                else:
+                    _log_warning("Unable to install host utilities -- your platform is not supported!")
+
+    if debugger:
+        # Optionally set up JimDB. See https://wiki.mozilla.org/Mobile/Fennec/Android/GDB.
+        build_platform = _get_build_platform(build_obj.substs)
+        jimdb_path = os.path.join(EMULATOR_HOME_DIR, 'jimdb-%s' % build_platform)
+        jimdb_utils_path = os.path.join(jimdb_path, 'utils')
+        gdb_path = os.path.join(jimdb_path, 'bin', 'gdb')
+        err = None
+        if not os.path.isdir(jimdb_path):
+            err = '%s does not exist' % jimdb_path
+        elif not os.path.isfile(gdb_path):
+            err = '%s not found' % gdb_path
+        if err:
+            _log_info("JimDB (%s) not found: %s" % (build_platform, err))
+            response = raw_input(
+                "Download and setup JimDB (%s)? (Y/n) " % build_platform).strip()
+            if response.lower().startswith('y') or response == '':
+                host_platform = _get_host_platform()
+                if host_platform:
+                    _log_info("Installing JimDB (%s/%s). This may take a while..." % (host_platform, build_platform))
+                    path = os.path.join(MANIFEST_PATH, host_platform, 'jimdb-%s.manifest' % build_platform)
+                    _get_tooltool_manifest(build_obj.substs, path, EMULATOR_HOME_DIR, 'releng.manifest')
+                    _tooltool_fetch()
+                    if os.path.isfile(gdb_path):
+                        # Get JimDB utilities from git repository
+                        proc = ProcessHandler(['git', 'pull'], cwd=jimdb_utils_path)
+                        proc.run()
+                        git_pull_complete = False
+                        try:
+                            proc.wait()
+                            if proc.proc.returncode == 0:
+                                git_pull_complete = False
+                        except:
+                            if proc.poll() is None:
+                                proc.kill(signal.SIGTERM)
+                        if not git_pull_complete:
+                            _log_warning("Unable to update JimDB utils from git -- some JimDB features may be unavailable.")
+                    else:
+                        _log_warning("Unable to install JimDB -- unable to fetch from tooltool.")
+                else:
+                    _log_warning("Unable to install JimDB -- your platform is not supported!")
+        if os.path.isfile(gdb_path):
+            # sync gdbinit.local with build settings
+            _update_gdbinit(build_obj.substs, os.path.join(jimdb_utils_path, "gdbinit.local"))
+            # ensure JimDB is in system path, so that mozdebug can find it
+            bin_path = os.path.join(jimdb_path, 'bin')
+            os.environ['PATH'] = "%s:%s" % (bin_path, os.environ['PATH'])
 
     return device_verified
 
 def run_firefox_for_android(build_obj, params):
     """
        Launch Firefox for Android on the connected device.
        Optional 'params' allow parameters to be passed to Firefox.
     """
@@ -620,8 +663,53 @@ def _tooltool_fetch():
         command, processOutputLine=outputHandler, storeOutput=False,
         cwd=EMULATOR_HOME_DIR)
     proc.run()
     try:
         proc.wait()
     except:
         if proc.poll() is None:
             proc.kill(signal.SIGTERM)
+
+def _get_host_platform():
+    plat = None
+    if 'darwin' in str(sys.platform).lower():
+        plat = 'macosx64'
+    elif 'linux' in str(sys.platform).lower():
+        if '64' in platform.architecture()[0]:
+            plat = 'linux64'
+        else:
+            plat = 'linux32'
+    return plat
+
+def _get_build_platform(substs):
+    if substs['TARGET_CPU'].startswith('arm'):
+        return 'arm'
+    return 'x86'
+
+def _update_gdbinit(substs, path):
+    if os.path.exists(path):
+        obj_replaced = False
+        src_replaced = False
+        # update existing objdir/srcroot in place
+        for line in fileinput.input(path, inplace=True):
+            if "feninit.default.objdir" in line and substs and 'MOZ_BUILD_ROOT' in substs:
+                print("python feninit.default.objdir = '%s'" % substs['MOZ_BUILD_ROOT'])
+                obj_replaced = True
+            elif "feninit.default.srcroot" in line and substs and 'top_srcdir' in substs:
+                print("python feninit.default.srcroot = '%s'" % substs['top_srcdir'])
+                src_replaced = True
+            else:
+                print(line.strip())
+        # append objdir/srcroot if not updated
+        if (not obj_replaced) and substs and 'MOZ_BUILD_ROOT' in substs:
+            with open(path, "a") as f:
+                f.write("\npython feninit.default.objdir = '%s'\n" % substs['MOZ_BUILD_ROOT'])
+        if (not src_replaced) and substs and 'top_srcdir' in substs:
+            with open(path, "a") as f:
+                f.write("python feninit.default.srcroot = '%s'\n" % substs['top_srcdir'])
+    else:
+        # write objdir/srcroot to new gdbinit file
+        with open(path, "w") as f:
+            if substs and 'MOZ_BUILD_ROOT' in substs:
+                f.write("python feninit.default.objdir = '%s'\n" % substs['MOZ_BUILD_ROOT'])
+            if substs and 'top_srcdir' in substs:
+                f.write("python feninit.default.srcroot = '%s'\n" % substs['top_srcdir'])