Bug 1557805 - Support `mach run {--app,--profile,--setenv}` on Android. r=gbrown
authorNick Alexander <nalexander@mozilla.com>
Fri, 14 Jun 2019 20:14:23 +0000
changeset 478942 d03b95b35c350dca85269a9e8395292ed4b1fb3e
parent 478941 f6da94d9035047b44f1521bfd2d37960263a2f36
child 478943 a5c1755178d94134fa11b24227bf3cb964978ede
push id87990
push usernalexander@mozilla.com
push dateFri, 14 Jun 2019 20:15:49 +0000
treeherderautoland@d03b95b35c35 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgbrown
bugs1557805, 1552519
milestone69.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 1557805 - Support `mach run {--app,--profile,--setenv}` on Android. r=gbrown This is just a small step on the path towards support for more vehicles via the configuration files introduced by Bug 1552519. It's enough to invoke GeckoView example. Differential Revision: https://phabricator.services.mozilla.com/D34952
mobile/android/mach_commands.py
--- a/mobile/android/mach_commands.py
+++ b/mobile/android/mach_commands.py
@@ -605,40 +605,124 @@ class MachCommands(MachCommandBase):
         ret = self._run_make(directory='.', target='install', ensure_exit_code=False)
         if ret == 0:
             self.notify('Install complete')
         return ret
 
     @Command('run-android', category='post-build',
              conditional_name='run',
              conditions=[conditions.is_android],
-             description='Run Fennec on an Android device or an emulator.')
+             description='Run an application on an Android device or an emulator.')
+    @CommandArgument('--app', help='Android package to run '
+                     '(default: org.mozilla.geckoview_example)',
+                     default='org.mozilla.geckoview_example')
+    @CommandArgument('--intent', help='Android intent action to launch with '
+                     '(default: android.intent.action.VIEW)',
+                     default='android.intent.action.VIEW')
+    @CommandArgument('--setenv', dest='env', action='append',
+                     help='Set target environment variable, like FOO=BAR',
+                     default=[])
+    @CommandArgument('--profile', '-P', help='Path to Gecko profile, like /path/to/host/profile '
+                     'or /path/to/target/profile',
+                     default=None)
     @CommandArgument('--url', help='URL to open',
                      default=None)
     @CommandArgument('--no-install', help='Do not try to install application on device before ' +
                      'running (default: False)',
                      action='store_true',
                      default=False)
     @CommandArgument('--no-wait', help='Do not wait for application to start before returning ' +
                      '(default: False)',
                      action='store_true',
                      default=False)
     @CommandArgument('--fail-if-running', help='Fail if application is already running ' +
                      '(default: False)',
                      action='store_true',
                      default=False)
-    def run(self, url=None, no_install=None, no_wait=None, fail_if_running=None):
-        from mozrunner.devices.android_device import verify_android_device, run_firefox_for_android
+    @CommandArgument('--restart', help='Stop the application if it is already running ' +
+                     '(default: False)',
+                     action='store_true',
+                     default=False)
+    def run(self, app='org.mozilla.geckoview_example', intent=None,
+            env=[], profile=None,
+            url=None, no_install=None, no_wait=None, fail_if_running=None, restart=None):
+        from mozrunner.devices.android_device import verify_android_device, _get_device
+        from six.moves import shlex_quote
+
+        if app == 'org.mozilla.geckoview_example':
+            activity_name = 'org.mozilla.geckoview_example.GeckoViewActivity'
+        elif app == 'org.mozilla.geckoview.test':
+            activity_name = 'org.mozilla.geckoview.test.TestRunnerActivity'
+        elif 'fennec' in app or 'firefox' in app:
+            activity_name = 'org.mozilla.gecko.BrowserApp'
+        else:
+            raise RuntimeError('Application not recognized: {}'.format(app))
+
+        # `verify_android_device` respects `DEVICE_SERIAL` if it is set and sets it otherwise.
+        verify_android_device(self, app=app, install=not no_install)
+        device_serial = os.environ.get('DEVICE_SERIAL')
+        if not device_serial:
+            print('No ADB devices connected.')
+            return 1
+
+        device = _get_device(self.substs, device_serial=device_serial)
 
-        verify_android_device(self, install=not no_install)
-        return run_firefox_for_android(self,
-                                       [],
-                                       url=url,
-                                       wait=not no_wait,
-                                       fail_if_running=fail_if_running)
+        args = []
+        if profile:
+            if os.path.isdir(profile):
+                host_profile = profile
+                # Always /data/local/tmp, rather than `device.test_root`, because GeckoView only
+                # takes its configuration file from /data/local/tmp, and we want to follow suit.
+                target_profile = '/data/local/tmp/{}-profile'.format(app)
+                device.rm(target_profile, recursive=True, force=True)
+                device.push(host_profile, target_profile)
+                self.log(logging.INFO, "run",
+                         {'host_profile': host_profile, 'target_profile': target_profile},
+                         'Pushed profile from host "{host_profile}" to target "{target_profile}"')
+            else:
+                target_profile = profile
+                self.log(logging.INFO, "run",
+                         {'target_profile': target_profile},
+                         'Using profile from target "{target_profile}"')
+
+            args = ['--profile', shlex_quote(target_profile)]
+
+        extras = {}
+        for i, e in enumerate(env):
+            extras['env{}'.format(i)] = e
+        if args:
+            extras['args'] = " ".join(args)
+        extras['use_multiprocess'] = True  # Only GVE and TRA process this extra.
+
+        if env or args:
+            restart = True
+
+        if restart:
+            fail_if_running = False
+            self.log(logging.INFO, "run",
+                     {'app': app},
+                     'Stopping {app} to ensure clean restart.')
+            device.stop_application(app)
+
+        # We'd prefer to log the actual `am start ...` command, but it's not trivial to wire the
+        # device's logger to mach's logger.
+        self.log(logging.INFO, "run",
+                 {'app': app, 'activity_name': activity_name},
+                 'Starting {app}/{activity_name}.')
+
+        device.launch_application(
+            app_name=app,
+            activity_name=activity_name,
+            intent=intent,
+            extras=extras,
+            url=url,
+            wait=not no_wait,
+            fail_if_running=fail_if_running)
+
+        return 0
 
 
 def _get_maven_archive_abs_and_relative_paths(maven_folder):
     for subdir, _, files in os.walk(maven_folder):
         for file in files:
             full_path = os.path.join(subdir, file)
             relative_path = os.path.relpath(full_path, maven_folder)