diff options
Diffstat (limited to 'mobile/android/mach_commands.py')
-rw-r--r-- | mobile/android/mach_commands.py | 300 |
1 files changed, 297 insertions, 3 deletions
diff --git a/mobile/android/mach_commands.py b/mobile/android/mach_commands.py index 68271bd0c0..fac483902c 100644 --- a/mobile/android/mach_commands.py +++ b/mobile/android/mach_commands.py @@ -5,6 +5,8 @@ import argparse import logging import os +import re +import subprocess import sys import tarfile @@ -12,6 +14,7 @@ import mozpack.path as mozpath from mach.decorators import Command, CommandArgument, SubCommand from mozbuild.base import MachCommandConditions as conditions from mozbuild.shellutil import split as shell_split +from mozfile import which # Mach's conditions facility doesn't support subcommands. Print a # deprecation message ourselves instead. @@ -189,9 +192,13 @@ def create_maven_archive(topobjdir): gradle_folder = os.path.join(topobjdir, "gradle") maven_folder = os.path.join(gradle_folder, "maven") - with tarfile.open( - os.path.join(gradle_folder, "target.maven.tar.xz"), "w|xz" - ) as tar: + # Create the archive, with no compression: The archive contents are large + # files which cannot be significantly compressed; attempting to compress + # the archive is usually expensive in time and results in minimal + # reduction in size. + # Even though the archive is not compressed, use the .xz file extension + # so that the taskcluster worker also skips compression. + with tarfile.open(os.path.join(gradle_folder, "target.maven.tar.xz"), "w") as tar: for abs_path in get_maven_archive_paths(maven_folder): tar.add( abs_path, @@ -694,3 +701,290 @@ def emulator( "Unable to retrieve Android emulator return code.", ) return 0 + + +@SubCommand( + "android", + "uplift", + description="Uplift patch to https://github.com/mozilla-mobile/firefox-android.", +) +@CommandArgument( + "--revset", + "-r", + default=None, + help="Revision or revisions to uplift. Supported values are the same as what your " + "VCS provides. Defaults to the current HEAD revision.", +) +@CommandArgument( + "firefox_android_clone_dir", + help="The directory where your local clone of " + "https://github.com/mozilla-mobile/firefox-android repo is.", +) +def uplift( + command_context, + revset, + firefox_android_clone_dir, +): + revset = _get_default_revset_if_needed(command_context, revset) + major_version = _get_major_version(command_context, revset) + uplift_version = major_version - 1 + bug_number = _get_bug_number(command_context, revset) + new_branch_name = ( + f"{uplift_version}-bug{bug_number}" if bug_number else f"{uplift_version}-nobug" + ) + command_context.log( + logging.INFO, + "uplift", + { + "new_branch_name": new_branch_name, + "firefox_android_clone_dir": firefox_android_clone_dir, + }, + "Creating branch {new_branch_name} in {firefox_android_clone_dir}...", + ) + + try: + _checkout_new_branch_updated_to_the_latest_remote( + command_context, firefox_android_clone_dir, uplift_version, new_branch_name + ) + _export_and_apply_revset(command_context, revset, firefox_android_clone_dir) + except subprocess.CalledProcessError: + return 1 + + command_context.log( + logging.INFO, + "uplift", + {"revset": revset, "firefox_android_clone_dir": firefox_android_clone_dir}, + "Revision(s) {revset} now applied to {firefox_android_clone_dir}. Please go to " + "this directory, inspect the commit(s), and push.", + ) + return 0 + + +def _get_default_revset_if_needed(command_context, revset): + if revset is not None: + return revset + if conditions.is_hg(command_context): + return "." + if conditions.is_git(command_context): + return "HEAD" + raise NotImplementedError() + + +def _get_major_version(command_context, revset): + milestone_txt = _get_milestone_txt(command_context, revset) + version = _extract_version_from_milestone_txt(milestone_txt) + return _extract_major_version(version) + + +def _get_bug_number(command_context, revision): + revision_message = _extract_revision_message(command_context, revision) + return _extract_bug_number(revision_message) + + +def _get_milestone_txt(command_context, revset): + if conditions.is_hg(command_context): + args = [ + str(which("hg")), + "cat", + "-r", + f"first({revset})", + "config/milestone.txt", + ] + elif conditions.is_git(command_context): + revision = revset.split("..")[-1] + args = [ + str(which("git")), + "show", + f"{revision}:config/milestone.txt", + ] + else: + raise NotImplementedError() + + return subprocess.check_output(args, text=True) + + +def _extract_version_from_milestone_txt(milestone_txt): + return milestone_txt.splitlines()[-1] + + +def _extract_major_version(version): + return int(version.split(".")[0]) + + +def _extract_revision_message(command_context, revision): + if conditions.is_hg(command_context): + args = [ + str(which("hg")), + "log", + "--rev", + f"first({revision})", + "--template", + "{desc}", + ] + elif conditions.is_git(command_context): + args = [ + str(which("git")), + "log", + "--format=%s", + "-n", + "1", + revision, + ] + else: + raise NotImplementedError() + + return subprocess.check_output(args, text=True) + + +# Source: https://hg.mozilla.org/hgcustom/version-control-tools/file/cef43d3d676e9f9e9668a50a5d90c012e4025e5b/pylib/mozautomation/mozautomation/commitparser.py#l12 +_BUG_TEMPLATE = re.compile( + r"""# bug followed by any sequence of numbers, or + # a standalone sequence of numbers + ( + (?: + bug | + b= | + # a sequence of 5+ numbers preceded by whitespace + (?=\b\#?\d{5,}) | + # numbers at the very beginning + ^(?=\d) + ) + (?:\s*\#?)(\d+)(?=\b) + )""", + re.I | re.X, +) + + +def _extract_bug_number(revision_message): + try: + return _BUG_TEMPLATE.match(revision_message).group(2) + except AttributeError: + return "" + + +_FIREFOX_ANDROID_URL = "https://github.com/mozilla-mobile/firefox-android" + + +def _checkout_new_branch_updated_to_the_latest_remote( + command_context, firefox_android_clone_dir, uplift_version, new_branch_name +): + args = [ + str(which("git")), + "fetch", + _FIREFOX_ANDROID_URL, + f"+releases_v{uplift_version}:{new_branch_name}", + ] + + try: + subprocess.check_call(args, cwd=firefox_android_clone_dir) + except subprocess.CalledProcessError: + command_context.log( + logging.CRITICAL, + "uplift", + { + "firefox_android_clone_dir": firefox_android_clone_dir, + "new_branch_name": new_branch_name, + }, + "Could not fetch branch {new_branch_name}. This may be a network issue. If " + "not, please go to {firefox_android_clone_dir}, inspect the branch and " + "delete it (with `git branch -D {new_branch_name}`) if you don't have any " + "use for it anymore", + ) + raise + + args = [ + str(which("git")), + "checkout", + new_branch_name, + ] + subprocess.check_call(args, cwd=firefox_android_clone_dir) + + +_MERCURIAL_REVISION_TO_GIT_COMMIT_TEMPLATE = """ +From 1234567890abcdef1234567890abcdef12345678 Sat Jan 1 00:00:00 2000 +From: {user} +Date: {date|rfc822date} +Subject: {desc} + +""" + + +def _export_and_apply_revset(command_context, revset, firefox_android_clone_dir): + export_command, import_command = _get_export_import_commands( + command_context, revset + ) + + export_process = subprocess.Popen(export_command, stdout=subprocess.PIPE) + try: + subprocess.check_call( + import_command, stdin=export_process.stdout, cwd=firefox_android_clone_dir + ) + except subprocess.CalledProcessError: + command_context.log( + logging.CRITICAL, + "uplift", + {"firefox_android_clone_dir": firefox_android_clone_dir}, + "Could not run `git am`. Please go to {firefox_android_clone_dir} and fix " + "the conflicts. Then run `git am --continue`.", + ) + raise + export_process.wait() + + +def _get_export_import_commands(command_context, revset): + if conditions.is_hg(command_context): + export_command = [ + str(which("hg")), + "log", + "--rev", + revset, + "--patch", + "--template", + _MERCURIAL_REVISION_TO_GIT_COMMIT_TEMPLATE, + "--include", + "mobile/android", + ] + import_command = [str(which("git")), "am", "-p3"] + elif conditions.is_git(command_context): + export_command = [ + str(which("git")), + "format-patch", + "--relative=mobile/android", + "--stdout", + ] + # From the git man page: + # > If you want to format only <commit> itself, you can do this with + # > git format-patch -1 <commit>." + # + # https://git-scm.com/docs/git-format-patch#_description + if _is_single_revision(command_context, revset): + export_command.append("-1") + + export_command.extend( + [ + revset, + "--", + ] + ) + import_command = [str(which("git")), "am"] + else: + raise NotImplementedError() + + return export_command, import_command + + +def _is_single_revision(command_context, revset): + if conditions.is_git(command_context): + command = [ + str(which("git")), + "show", + "--no-patch", + "--format='%H'", + revset, + "--", + ] + else: + raise NotImplementedError() + + revisions = subprocess.check_output(command, text=True) + return len(revisions.splitlines()) == 1 |