1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 import mozpack
.path
as mozpath
10 from mach
.decorators
import Command
, CommandArgument
, SubCommand
11 from mozbuild
.base
import MachCommandConditions
as conditions
12 from mozbuild
.shellutil
import split
as shell_split
14 # Mach's conditions facility doesn't support subcommands. Print a
15 # deprecation message ourselves instead.
16 LINT_DEPRECATION_MESSAGE
= """
17 Android lints are now integrated with mozlint. Instead of
18 `mach android {api-lint,checkstyle,lint,test}`, run
19 `mach lint --linter android-{api-lint,checkstyle,lint,test}`.
24 # NOTE python/mach/mach/commands/commandinfo.py references this function
25 # by name. If this function is renamed or removed, that file should
26 # be updated accordingly as well.
28 """Command no longer exists! Use the Gradle configuration rooted in the top source directory
31 See https://developer.mozilla.org/en-US/docs/Simple_Firefox_for_Android_build#Developing_Firefox_for_Android_in_Android_Studio_or_IDEA_IntelliJ. # NOQA: E501
39 description
="Run Android-specific commands.",
40 conditions
=[conditions
.is_android
],
42 def android(command_context
):
49 """Assemble Firefox for Android.
50 See http://firefox-source-docs.mozilla.org/build/buildsystem/toolchains.html#firefox-for-android-with-gradle""", # NOQA: E501
52 @CommandArgument("args", nargs
=argparse
.REMAINDER
)
53 def android_assemble_app(command_context
, args
):
56 command_context
.substs
["GRADLE_ANDROID_APP_TASKS"] + ["-x", "lint"] + args
,
65 "generate-sdk-bindings",
66 """Generate SDK bindings used when building GeckoView.""",
71 help="config files, like [/path/to/ClassName-classes.txt]+",
73 @CommandArgument("args", nargs
=argparse
.REMAINDER
)
74 def android_generate_sdk_bindings(command_context
, inputs
, args
):
78 # Turn "/path/to/ClassName-classes.txt" into "ClassName".
79 return os
.path
.basename(input).rsplit("-classes.txt", 1)[0]
81 bindings_inputs
= list(itertools
.chain(*((input, stem(input)) for input in inputs
)))
82 bindings_args
= "-Pgenerate_sdk_bindings_args={}".format(";".join(bindings_inputs
))
86 command_context
.substs
["GRADLE_ANDROID_GENERATE_SDK_BINDINGS_TASKS"]
97 "generate-generated-jni-wrappers",
98 """Generate GeckoView JNI wrappers used when building GeckoView.""",
100 @CommandArgument("args", nargs
=argparse
.REMAINDER
)
101 def android_generate_generated_jni_wrappers(command_context
, args
):
104 command_context
.substs
["GRADLE_ANDROID_GENERATE_GENERATED_JNI_WRAPPERS_TASKS"]
115 """Run Android api-lint.
116 REMOVED/DEPRECATED: Use 'mach lint --linter android-api-lint'.""",
118 def android_apilint_REMOVED(command_context
):
119 print(LINT_DEPRECATION_MESSAGE
)
127 REMOVED/DEPRECATED: Use 'mach lint --linter android-test'.""",
129 def android_test_REMOVED(command_context
):
130 print(LINT_DEPRECATION_MESSAGE
)
138 REMOVED/DEPRECATED: Use 'mach lint --linter android-lint'.""",
140 def android_lint_REMOVED(command_context
):
141 print(LINT_DEPRECATION_MESSAGE
)
148 """Run Android checkstyle.
149 REMOVED/DEPRECATED: Use 'mach lint --linter android-checkstyle'.""",
151 def android_checkstyle_REMOVED(command_context
):
152 print(LINT_DEPRECATION_MESSAGE
)
158 "gradle-dependencies",
159 """Collect Android Gradle dependencies.
160 See http://firefox-source-docs.mozilla.org/build/buildsystem/toolchains.html#firefox-for-android-with-gradle""", # NOQA: E501
162 @CommandArgument("args", nargs
=argparse
.REMAINDER
)
163 def android_gradle_dependencies(command_context
, args
):
164 # We don't want to gate producing dependency archives on clean
165 # lint or checkstyle, particularly because toolchain versions
166 # can change the outputs for those processes.
169 command_context
.substs
["GRADLE_ANDROID_DEPENDENCIES_TASKS"]
181 """Create GeckoView archives.
182 See http://firefox-source-docs.mozilla.org/build/buildsystem/toolchains.html#firefox-for-android-with-gradle""", # NOQA: E501
184 @CommandArgument("args", nargs
=argparse
.REMAINDER
)
185 def android_archive_geckoview(command_context
, args
):
188 command_context
.substs
["GRADLE_ANDROID_ARCHIVE_GECKOVIEW_TASKS"] + args
,
195 @SubCommand("android", "build-geckoview_example", """Build geckoview_example """)
196 @CommandArgument("args", nargs
=argparse
.REMAINDER
)
197 def android_build_geckoview_example(command_context
, args
):
200 command_context
.substs
["GRADLE_ANDROID_BUILD_GECKOVIEW_EXAMPLE_TASKS"] + args
,
205 "Execute `mach android install-geckoview_example` "
206 "to push the geckoview_example and test APKs to a device."
212 def install_app_bundle(command_context
, bundle
):
213 from mozdevice
import ADBDeviceFactory
215 bundletool
= mozpath
.join(command_context
._mach
_context
.state_dir
, "bundletool.jar")
216 device
= ADBDeviceFactory(verbose
=True)
217 bundle_path
= mozpath
.join(command_context
.topobjdir
, bundle
)
218 java_home
= java_home
= os
.path
.dirname(
219 os
.path
.dirname(command_context
.substs
["JAVA"])
221 device
.install_app_bundle(bundletool
, bundle_path
, java_home
, timeout
=120)
224 @SubCommand("android", "install-geckoview_example", """Install geckoview_example """)
225 @CommandArgument("args", nargs
=argparse
.REMAINDER
)
226 def android_install_geckoview_example(command_context
, args
):
229 command_context
.substs
["GRADLE_ANDROID_INSTALL_GECKOVIEW_EXAMPLE_TASKS"] + args
,
234 "Execute `mach android build-geckoview_example` "
235 "to just build the geckoview_example and test APKs."
242 "android", "install-geckoview-test_runner", """Install geckoview.test_runner """
244 @CommandArgument("args", nargs
=argparse
.REMAINDER
)
245 def android_install_geckoview_test_runner(command_context
, args
):
248 command_context
.substs
["GRADLE_ANDROID_INSTALL_GECKOVIEW_TEST_RUNNER_TASKS"]
257 "install-geckoview-test_runner-aab",
258 """Install geckoview.test_runner with AAB""",
260 @CommandArgument("args", nargs
=argparse
.REMAINDER
)
261 def android_install_geckoview_test_runner_aab(command_context
, args
):
264 command_context
.substs
["GRADLE_ANDROID_GECKOVIEW_TEST_RUNNER_BUNDLE"],
271 "install-geckoview_example-aab",
272 """Install geckoview_example with AAB""",
274 @CommandArgument("args", nargs
=argparse
.REMAINDER
)
275 def android_install_geckoview_example_aab(command_context
, args
):
278 command_context
.substs
["GRADLE_ANDROID_GECKOVIEW_EXAMPLE_BUNDLE"],
283 @SubCommand("android", "install-geckoview-test", """Install geckoview.test """)
284 @CommandArgument("args", nargs
=argparse
.REMAINDER
)
285 def android_install_geckoview_test(command_context
, args
):
288 command_context
.substs
["GRADLE_ANDROID_INSTALL_GECKOVIEW_TEST_TASKS"] + args
,
297 """Create GeckoView javadoc and optionally upload to Github""",
299 @CommandArgument("--archive", action
="store_true", help="Generate a javadoc archive.")
303 help="Upload geckoview documentation to Github, using the specified USER/REPO.",
307 metavar
="BRANCH[/PATH]",
309 help="Use the specified branch/path for documentation commits.",
315 help="Use the specified path for javadoc commits.",
320 default
="GeckoView docs upload",
321 help="Use the specified message for commits.",
323 def android_geckoview_docs(
333 command_context
.substs
["GRADLE_ANDROID_GECKOVIEW_DOCS_ARCHIVE_TASKS"]
335 else command_context
.substs
["GRADLE_ANDROID_GECKOVIEW_DOCS_TASKS"]
338 ret
= gradle(command_context
, tasks
, verbose
=True)
339 if ret
or not upload
:
344 "level": os
.environ
.get("MOZ_SCM_LEVEL", "0"),
345 "project": os
.environ
.get("MH_BRANCH", "unknown"),
346 "revision": os
.environ
.get("GECKO_HEAD_REV", "tip"),
350 # In order to push to GitHub from TaskCluster, we store a private key
351 # in the TaskCluster secrets store in the format {"content": "<KEY>"},
352 # and the corresponding public key as a writable deploy key for the
353 # destination repo on GitHub.
354 secret
= os
.environ
.get("GECKOVIEW_DOCS_UPLOAD_SECRET", "").format(**fmt
)
356 # Set up a private key from the secrets store if applicable.
359 req
= requests
.get("http://taskcluster/secrets/v1/secret/" + secret
)
360 req
.raise_for_status()
362 keyfile
= mozpath
.abspath("gv-docs-upload-key")
363 with
open(keyfile
, "w") as f
:
364 os
.chmod(keyfile
, 0o600)
365 f
.write(req
.json()["secret"]["content"])
367 # Turn off strict host key checking so ssh does not complain about
368 # unknown github.com host. We're not pushing anything sensitive, so
369 # it's okay to not check GitHub's host keys.
370 env
["GIT_SSH_COMMAND"] = 'ssh -i "%s" -o StrictHostKeyChecking=no' % keyfile
373 branch
= upload_branch
.format(**fmt
)
374 repo_url
= "git@github.com:%s.git" % upload
375 repo_path
= mozpath
.abspath("gv-docs-repo")
376 command_context
.run_process(
390 env
["GIT_DIR"] = mozpath
.join(repo_path
, ".git")
391 env
["GIT_WORK_TREE"] = repo_path
392 env
["GIT_AUTHOR_NAME"] = env
["GIT_COMMITTER_NAME"] = "GeckoView Docs Bot"
393 env
["GIT_AUTHOR_EMAIL"] = env
["GIT_COMMITTER_EMAIL"] = "nobody@mozilla.com"
395 # Copy over user documentation.
398 # Extract new javadoc to specified directory inside repo.
399 src_tar
= mozpath
.join(
400 command_context
.topobjdir
,
407 "geckoview-javadoc.jar",
409 dst_path
= mozpath
.join(repo_path
, javadoc_path
.format(**fmt
))
410 mozfile
.remove(dst_path
)
411 mozfile
.extract_zip(src_tar
, dst_path
)
414 command_context
.run_process(["git", "add", "--all"], append_env
=env
, pass_thru
=True)
416 command_context
.run_process(
417 ["git", "diff", "--cached", "--quiet"],
420 ensure_exit_code
=False,
424 # We have something to commit.
425 command_context
.run_process(
426 ["git", "commit", "--message", upload_message
.format(**fmt
)],
430 command_context
.run_process(
431 ["git", "push", "origin", branch
], append_env
=env
, pass_thru
=True
434 mozfile
.remove(repo_path
)
436 mozfile
.remove(keyfile
)
443 description
="Run gradle.",
444 conditions
=[conditions
.is_android
],
450 help="Verbose output for what commands the build is running.",
452 @CommandArgument("args", nargs
=argparse
.REMAINDER
)
453 def gradle(command_context
, args
, verbose
=False):
455 # Avoid logging the command
456 command_context
.log_manager
.terminal_handler
.setLevel(logging
.CRITICAL
)
458 # In automation, JAVA_HOME is set via mozconfig, which needs
459 # to be specially handled in each mach command. This turns
460 # $JAVA_HOME/bin/java into $JAVA_HOME.
461 java_home
= os
.path
.dirname(os
.path
.dirname(command_context
.substs
["JAVA"]))
463 gradle_flags
= command_context
.substs
.get("GRADLE_FLAGS", "") or os
.environ
.get(
466 gradle_flags
= shell_split(gradle_flags
)
468 # We force the Gradle JVM to run with the UTF-8 encoding, since we
469 # filter strings.xml, which is really UTF-8; the ellipsis character is
470 # replaced with ??? in some encodings (including ASCII). It's not yet
471 # possible to filter with encodings in Gradle
472 # (https://github.com/gradle/gradle/pull/520) and it's challenging to
473 # do our filtering with Gradle's Ant support. Moreover, all of the
474 # Android tools expect UTF-8: see
475 # http://tools.android.com/knownissues/encoding. See
476 # http://stackoverflow.com/a/21267635 for discussion of this approach.
478 # It's not even enough to set the encoding just for Gradle; it
479 # needs to be for JVMs spawned by Gradle as well. This
480 # happens during the maven deployment generating the GeckoView
481 # documents; this works around "error: unmappable character
482 # for encoding ASCII" in exoplayer2. See
483 # https://discuss.gradle.org/t/unmappable-character-for-encoding-ascii-when-building-a-utf-8-project/10692/11 # NOQA: E501
484 # and especially https://stackoverflow.com/a/21755671.
486 if command_context
.substs
.get("MOZ_AUTOMATION"):
487 gradle_flags
+= ["--console=plain"]
489 env
= os
.environ
.copy()
492 "GRADLE_OPTS": "-Dfile.encoding=utf-8",
493 "JAVA_HOME": java_home
,
494 "JAVA_TOOL_OPTIONS": "-Dfile.encoding=utf-8",
497 # Set ANDROID_SDK_ROOT if --with-android-sdk was set.
498 # See https://bugzilla.mozilla.org/show_bug.cgi?id=1576471
499 android_sdk_root
= command_context
.substs
.get("ANDROID_SDK_ROOT", "")
501 env
["ANDROID_SDK_ROOT"] = android_sdk_root
503 return command_context
.run_process(
504 [command_context
.substs
["GRADLE"]] + gradle_flags
+ args
,
506 pass_thru
=True, # Allow user to run gradle interactively.
507 ensure_exit_code
=False, # Don't throw on non-zero exit code.
508 cwd
=mozpath
.join(command_context
.topsrcdir
),
512 @Command("gradle-install", category
="devenv", conditions
=[REMOVED
])
513 def gradle_install_REMOVED(command_context
):
521 description
="Run the Android emulator with an AVD from test automation. "
522 "Environment variable MOZ_EMULATOR_COMMAND_ARGS, if present, will "
523 "over-ride the command line arguments used to launch the emulator.",
528 choices
=["arm", "arm64", "x86_64"],
529 help="Specify which AVD to run in emulator. "
530 'One of "arm" (Android supporting armv7 binaries), '
531 '"arm64" (for Apple Silicon), or '
532 '"x86_64" (Android supporting x86 or x86_64 binaries, '
533 "recommended for most applications). "
534 "By default, the value will match the current build environment.",
536 @CommandArgument("--wait", action
="store_true", help="Wait for emulator to be closed.")
537 @CommandArgument("--gpu", help="Over-ride the emulator -gpu argument.")
539 "--verbose", action
="store_true", help="Log informative status messages."
549 Run the Android emulator with one of the AVDs used in the Mozilla
550 automated test environment. If necessary, the AVD is fetched from
551 the taskcluster server and installed.
553 from mozrunner
.devices
.android_device
import AndroidEmulator
555 emulator
= AndroidEmulator(
558 substs
=command_context
.substs
,
559 device_serial
="emulator-5554",
561 if emulator
.is_running():
562 # It is possible to run multiple emulators simultaneously, but:
563 # - if more than one emulator is using the same avd, errors may
564 # occur due to locked resources;
565 # - additional parameters must be specified when running tests,
566 # to select a specific device.
567 # To avoid these complications, allow just one emulator at a time.
572 "An Android emulator is already running.\n"
573 "Close the existing emulator and re-run this command.",
577 if not emulator
.check_avd():
582 "AVD not found. Please run |mach bootstrap|.",
586 if not emulator
.is_available():
591 "Emulator binary not found.\n"
592 "Install the Android SDK and make sure 'emulator' is in your PATH.",
600 "Starting Android emulator running %s..." % emulator
.get_avd_description(),
603 if emulator
.wait_for_start():
605 logging
.INFO
, "emulator", {}, "Android emulator is running."
608 # This is unusual but the emulator may still function.
613 "Unable to verify that emulator is running.",
616 if conditions
.is_android(command_context
):
621 "Use 'mach install' to install or update Firefox on your emulator.",
628 "No Firefox for Android build detected.\n"
629 "Switch to a Firefox for Android build context or use 'mach bootstrap'\n"
630 "to setup an Android build environment.",
635 logging
.INFO
, "emulator", {}, "Waiting for Android emulator to close..."
643 "Android emulator completed with return code %d." % rc
,
650 "Unable to retrieve Android emulator return code.",